diff --git a/.circleci/local_publish_helpers_codebuild.sh b/.circleci/local_publish_helpers_codebuild.sh index 108f1cbd314..25db6229a8b 100644 --- a/.circleci/local_publish_helpers_codebuild.sh +++ b/.circleci/local_publish_helpers_codebuild.sh @@ -297,6 +297,29 @@ function runE2eTestCb { fi } +function runGen2MigrationsE2ETestCb { + _setupCoverage + FAILED_TEST_REGEX_FILE="./amplify-migration-e2e-reports/amplify-migration-e2e-failed-test.txt" + + if [ -f $FAILED_TEST_REGEX_FILE ]; then + # read the content of failed tests + failedTests=$(<$FAILED_TEST_REGEX_FILE) + if [[ ! -z "$DISABLE_COVERAGE" ]]; then + echo Running WITHOUT coverage + yarn e2e-migration --forceExit --no-cache --maxWorkers=4 $TEST_SUITE -t "$failedTests" + else + NODE_V8_COVERAGE=$E2E_TEST_COVERAGE_DIR yarn e2e-migration --forceExit --no-cache --maxWorkers=4 $TEST_SUITE -t "$failedTests" + fi + else + if [[ ! -z "$DISABLE_COVERAGE" ]]; then + echo Running WITHOUT coverage + yarn e2e-migration --forceExit --no-cache --maxWorkers=4 $TEST_SUITE + else + NODE_V8_COVERAGE=$E2E_TEST_COVERAGE_DIR yarn e2e-migration --forceExit --no-cache --maxWorkers=4 $TEST_SUITE + fi + fi +} + function _setupCoverage { _teardownCoverage echo "Setup Coverage ($E2E_TEST_COVERAGE_DIR)" diff --git a/.eslint-dictionary.json b/.eslint-dictionary.json index f6324edd864..437e33e1dc4 100644 --- a/.eslint-dictionary.json +++ b/.eslint-dictionary.json @@ -21,6 +21,7 @@ "amplifymeta", "amplifyrc", "amplifytools", + "ampx", "ansi", "apigateway", "apigw", @@ -49,6 +50,7 @@ "axios", "babelrc", "backend", + "backends", "balancer", "basedir", "birthdate", @@ -60,6 +62,7 @@ "bugfix", "buildable", "buildspec", + "bundler", "callout", "camelcase", "cancellable", @@ -85,6 +88,7 @@ "cname", "codebase", "codegen", + "codegentest", "codepipeline", "codesuite", "cognito", @@ -125,6 +129,7 @@ "doctype", "dotenv", "dotnet", + "downloader", "durations", "dynamodb", "ecluster", @@ -134,6 +139,7 @@ "ejs", "elasticsearch", "emacs", + "enablegen2migration", "endian", "enode", "entrypoint", @@ -142,7 +148,9 @@ "enums", "epath", "errno", + "esbuild", "esri", + "esModuleInterop", "etag", "execa", "executables", @@ -160,6 +168,7 @@ "frontend", "frontends", "fsext", + "fullname", "func", "funcs", "generatable", @@ -256,6 +265,7 @@ "multifactor", "multipart", "mutex", + "mygen2app", "namespace", "netcoreapp", "netmask", @@ -329,6 +339,8 @@ "regenerator", "regexes", "rekognition", + "renderer", + "renderers", "repo", "reqheaders", "resolvers", @@ -340,6 +352,7 @@ "runnable", "runtimes", "sagemaker", + "saml", "scannable", "schemadeployer", "schemas", @@ -375,6 +388,7 @@ "syncable", "tablename", "tailwindcss", + "templategen", "testother", "testschemadeployer", "textract", @@ -398,6 +412,7 @@ "ulid", "unauth", "uncompiled", + "uncommented", "unicode", "unix", "unlink", @@ -410,10 +425,12 @@ "unstaged", "unsubstituted", "untyped", + "unzipper", "updateamplify", "uploader", "upsert", "upvotes", + "uris", "urlencoded", "urls", "userpool", @@ -426,6 +443,7 @@ "venv", "verificationbucket", "versioned", + "versioning", "vert", "virtualenv", "vpc", @@ -449,5 +467,6 @@ "yarnrc", "yesno", "yyyymmddhhmmss", - "zoneinfo" + "zoneinfo", + "yargs" ] diff --git a/codebuild_specs/e2e_workflow_base.yml b/codebuild_specs/e2e_workflow_base.yml index 5d1e46626b1..6cc06308fec 100644 --- a/codebuild_specs/e2e_workflow_base.yml +++ b/codebuild_specs/e2e_workflow_base.yml @@ -136,6 +136,12 @@ batch: compute-type: BUILD_GENERAL1_LARGE depend-on: - upb + - identifier: gen2_migrations_e2e_test + buildspec: codebuild_specs/run_gen2_migrations_e2e_tests_linux.yml + depend-on: + - upb + env: + compute-type: BUILD_GENERAL1_LARGE - identifier: cleanup_resources buildspec: codebuild_specs/cleanup_resources.yml depend-on: diff --git a/codebuild_specs/e2e_workflow_generated.yml b/codebuild_specs/e2e_workflow_generated.yml index e3d6a0179ec..302aa205991 100644 --- a/codebuild_specs/e2e_workflow_generated.yml +++ b/codebuild_specs/e2e_workflow_generated.yml @@ -136,6 +136,12 @@ batch: compute-type: BUILD_GENERAL1_LARGE depend-on: - upb + - identifier: gen2_migrations_e2e_test + buildspec: codebuild_specs/run_gen2_migrations_e2e_tests_linux.yml + depend-on: + - upb + env: + compute-type: BUILD_GENERAL1_LARGE - identifier: cleanup_resources buildspec: codebuild_specs/cleanup_resources.yml depend-on: diff --git a/codebuild_specs/run_gen2_migrations_e2e_tests_linux.yml b/codebuild_specs/run_gen2_migrations_e2e_tests_linux.yml new file mode 100644 index 00000000000..983fed42330 --- /dev/null +++ b/codebuild_specs/run_gen2_migrations_e2e_tests_linux.yml @@ -0,0 +1,55 @@ +version: 0.2 +env: + shell: bash + variables: + E2E_TEST_COVERAGE_DIR: node_v8_coverage + CI: true + CIRCLECI: true + IS_AMPLIFY_CI: true + # mock values to test artifact scanning + ENV_VAR_WITH_SECRETS: 'MOCK_ENV_VAR_FOR_SCANNING_SECRETS' + MOCK_ENV_VAR_FOR_SCANNING_SECRETS: 'abc123xyz' + + # mock values for credentials below + FACEBOOK_APP_ID: 'fbAppId' + FACEBOOK_APP_SECRET: 'fbAppSecret' + GOOGLE_APP_ID: 'gglAppID' + GOOGLE_APP_SECRET: 'gglAppSecret' + AMAZON_APP_ID: 'amaznAppID' + AMAZON_APP_SECRET: 'amaznAppID' + APPLE_APP_ID: 'com.fake.app' + APPLE_TEAM_ID: '2QLEWNDK6K' + APPLE_KEY_ID: '2QLZXKYJ8J' + # mock value, Cognito validates the private key, this is an invalidated key. + APPLE_PRIVATE_KEY_2: '----BEGIN PRIVATE KEY-----MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgIltgNsTgTfSzUadYiCS0VYtDDMFln/J8i1yJsSIw5g+gCgYIKoZIzj0DAQehRANCAASI8E0L/DhR/mIfTT07v3VwQu6q8I76lgn7kFhT0HvWoLuHKGQFcFkXXCgztgBrprzd419mUChAnKE6y89bWcNw----END PRIVATE KEY----' +phases: + build: + commands: + # you can provide a codebuild source version to use old cache and skip all other jobs :) + - export NODE_OPTIONS=--max-old-space-size=4096 + - export AMPLIFY_DIR=$CODEBUILD_SRC_DIR/out + - export AMPLIFY_PATH=$CODEBUILD_SRC_DIR/out/amplify-pkg-linux-x64 + - echo $AMPLIFY_DIR + - echo $AMPLIFY_PATH + - npm install -g ts-node + - source ./shared-scripts.sh && _runGen2MigrationE2ETestsLinux + post_build: + commands: + - source ./shared-scripts.sh && _convertCoverage # && _uploadCoverageLinux (disabled while troubleshooting E2E test failures during initial CodeBuild setup) + - source ./shared-scripts.sh && _scanArtifacts + - source ./shared-scripts.sh && _uploadReportsToS3 $CODEBUILD_SOURCE_VERSION $CODEBUILD_BATCH_BUILD_IDENTIFIER amplify-migration-e2e +artifacts: + files: + - '$E2E_TEST_COVERAGE_DIR/*' + - amplify-migration-e2e-reports/* + base-directory: packages/amplify-migration-e2e/ +reports: + e2e-reports: + files: + - '*.xml' + file-format: 'JUNITXML' + base-directory: '$CODEBUILD_SRC_DIR/packages/amplify-migration-e2e/junit' + e2e-coverage-report: + files: + - 'packages/amplify-migration-e2e/coverage/clover.xml' + file-format: CLOVERXML diff --git a/jest.config.js b/jest.config.js index 7fb1e4d9616..ddd14754af7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,7 @@ module.exports = { 'packages/amplify-e2e-core/', 'packages/amplify-e2e-tests/', 'packages/amplify-console-integration-tests/', + 'packages/amplify-migration-e2e/src/__tests__', 'packages/graphql-transformers-e2e-tests/', 'packages/amplify-util-mock/src/__e2e__/', 'packages/amplify-ui-tests/', diff --git a/package.json b/package.json index 06fef5a5452..507364566f2 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "coverage": "codecov || exit 0", "deep-clean-install": "yarn verdaccio-clean && yarn cache clean && yarn clean && git clean -fdx -e .vscode -e .env && yarn", "e2e": "lerna run e2e", + "e2e-migration": "lerna run e2e-migration", "extract-api": "lerna run extract-api --concurrency 4", "extract-formatting-changes": "yarn ts-node ./scripts/extract-formatting-changes.ts", "finish-release": "ts-node ./scripts/finish-release.ts", diff --git a/packages/amplify-app/CHANGELOG.md b/packages/amplify-app/CHANGELOG.md index a40adffe40c..d866558953e 100644 --- a/packages/amplify-app/CHANGELOG.md +++ b/packages/amplify-app/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.0.43-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-app@5.0.42-next-7.0...@aws-amplify/amplify-app@5.0.43-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-app + + + + + ## [5.0.42](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-app@5.0.41...@aws-amplify/amplify-app@5.0.42) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-app diff --git a/packages/amplify-app/package.json b/packages/amplify-app/package.json index ac9e718e81e..8cfb0059749 100644 --- a/packages/amplify-app/package.json +++ b/packages/amplify-app/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-app", - "version": "5.0.42", + "version": "5.0.43-next-11.0", "description": "Amplify CLI", "repository": { "type": "git", @@ -28,8 +28,8 @@ "dependencies": { "@aws-amplify/amplify-frontend-android": "3.5.8", "@aws-amplify/amplify-frontend-flutter": "1.4.7", - "@aws-amplify/amplify-frontend-ios": "3.7.12", - "@aws-amplify/amplify-frontend-javascript": "3.10.22", + "@aws-amplify/amplify-frontend-ios": "3.7.13-next-11.0", + "@aws-amplify/amplify-frontend-javascript": "3.10.23-next-11.0", "chalk": "^4.1.1", "execa": "^5.1.1", "fs-extra": "^8.1.0", diff --git a/packages/amplify-appsync-simulator/CHANGELOG.md b/packages/amplify-appsync-simulator/CHANGELOG.md index bc881dafb4f..cd54a7b7e33 100644 --- a/packages/amplify-appsync-simulator/CHANGELOG.md +++ b/packages/amplify-appsync-simulator/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.16.13-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-appsync-simulator@2.16.12-next-7.0...@aws-amplify/amplify-appsync-simulator@2.16.13-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-appsync-simulator + + + + + ## [2.16.12](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-appsync-simulator@2.16.11...@aws-amplify/amplify-appsync-simulator@2.16.12) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-appsync-simulator diff --git a/packages/amplify-appsync-simulator/package.json b/packages/amplify-appsync-simulator/package.json index 8e8ca3a115a..f4e61d0c977 100644 --- a/packages/amplify-appsync-simulator/package.json +++ b/packages/amplify-appsync-simulator/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-appsync-simulator", - "version": "2.16.12", + "version": "2.16.13-next-11.0", "description": "An AppSync Simulator to test AppSync API.", "repository": { "type": "git", @@ -30,11 +30,11 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "@graphql-tools/schema": "^8.3.1", "@graphql-tools/utils": "^8.5.1", - "amplify-velocity-template": "1.4.15", + "amplify-velocity-template": "1.4.16-next-11.0", "aws-sdk": "^2.1464.0", "chalk": "^4.1.1", "cors": "^2.8.5", @@ -58,7 +58,7 @@ "ws": "^8.5.0" }, "devDependencies": { - "@aws-amplify/amplify-graphiql-explorer": "2.6.1", + "@aws-amplify/amplify-graphiql-explorer": "2.6.2-next-11.0", "@types/cors": "^2.8.6", "@types/express": "^4.17.3", "@types/node": "^12.12.6", diff --git a/packages/amplify-category-analytics/CHANGELOG.md b/packages/amplify-category-analytics/CHANGELOG.md index 087fd82eadd..aa12fae57f7 100644 --- a/packages/amplify-category-analytics/CHANGELOG.md +++ b/packages/amplify-category-analytics/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.0.42-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-analytics@5.0.41-next-7.0...@aws-amplify/amplify-category-analytics@5.0.42-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-category-analytics + + + + + ## [5.0.41](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-analytics@5.0.40...@aws-amplify/amplify-category-analytics@5.0.41) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-analytics diff --git a/packages/amplify-category-analytics/package.json b/packages/amplify-category-analytics/package.json index 828ec7f4064..e4334692e4b 100644 --- a/packages/amplify-category-analytics/package.json +++ b/packages/amplify-category-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-analytics", - "version": "5.0.41", + "version": "5.0.42-next-11.0", "description": "amplify-cli analytics plugin", "repository": { "type": "git", @@ -25,8 +25,8 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "fs-extra": "^8.1.0", "uuid": "^8.3.2" diff --git a/packages/amplify-category-auth/CHANGELOG.md b/packages/amplify-category-auth/CHANGELOG.md index 1c7f6a1be80..b29dc732fc2 100644 --- a/packages/amplify-category-auth/CHANGELOG.md +++ b/packages/amplify-category-auth/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.7.22-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-auth@3.7.21-next-7.0...@aws-amplify/amplify-category-auth@3.7.22-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [3.7.21](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-auth@3.7.20...@aws-amplify/amplify-category-auth@3.7.21) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-auth diff --git a/packages/amplify-category-auth/package.json b/packages/amplify-category-auth/package.json index 28ddbe54aac..2a5a25969c6 100644 --- a/packages/amplify-category-auth/package.json +++ b/packages/amplify-category-auth/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-auth", - "version": "3.7.21", + "version": "3.7.22-next-11.0", "description": "amplify-cli authentication plugin", "repository": { "type": "git", @@ -28,12 +28,12 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "@aws-amplify/amplify-prompts": "2.8.6", "@aws-amplify/amplify-util-import": "2.8.3", - "@aws-amplify/cli-extensibility-helper": "3.0.38", + "@aws-amplify/cli-extensibility-helper": "3.0.39-next-11.0", "amplify-headless-interface": "1.17.7", "amplify-util-headless-input": "1.9.18", "aws-cdk-lib": "~2.189.1", diff --git a/packages/amplify-category-custom/CHANGELOG.md b/packages/amplify-category-custom/CHANGELOG.md index b6e4041cccd..09a9a424bab 100644 --- a/packages/amplify-category-custom/CHANGELOG.md +++ b/packages/amplify-category-custom/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.1.29-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-custom@3.1.28-next-7.0...@aws-amplify/amplify-category-custom@3.1.29-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [3.1.28](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-custom@3.1.27...@aws-amplify/amplify-category-custom@3.1.28) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-custom diff --git a/packages/amplify-category-custom/package.json b/packages/amplify-category-custom/package.json index 9028fc853cb..537afc06a36 100644 --- a/packages/amplify-category-custom/package.json +++ b/packages/amplify-category-custom/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-custom", - "version": "3.1.28", + "version": "3.1.29-next-11.0", "description": "amplify-cli custom resources plugin", "repository": { "type": "git", @@ -26,7 +26,7 @@ "access": "public" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "aws-cdk-lib": "~2.189.1", "execa": "^5.1.1", diff --git a/packages/amplify-category-function/CHANGELOG.md b/packages/amplify-category-function/CHANGELOG.md index 794a4351339..2e1b81a1585 100644 --- a/packages/amplify-category-function/CHANGELOG.md +++ b/packages/amplify-category-function/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.7.15-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-function@5.7.14-next-7.0...@aws-amplify/amplify-category-function@5.7.15-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-category-function + + + + + ## [5.7.14](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-function@5.7.13...@aws-amplify/amplify-category-function@5.7.14) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-function diff --git a/packages/amplify-category-function/package.json b/packages/amplify-category-function/package.json index 11e2fb88f01..e9702c67423 100644 --- a/packages/amplify-category-function/package.json +++ b/packages/amplify-category-function/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-function", - "version": "5.7.14", + "version": "5.7.15-next-11.0", "description": "amplify-cli function plugin", "repository": { "type": "git", @@ -26,8 +26,8 @@ "access": "public" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "@aws-amplify/amplify-prompts": "2.8.6", "archiver": "^5.3.0", diff --git a/packages/amplify-category-geo/CHANGELOG.md b/packages/amplify-category-geo/CHANGELOG.md index 49b70a177f4..7a11e557ae7 100644 --- a/packages/amplify-category-geo/CHANGELOG.md +++ b/packages/amplify-category-geo/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.5.22-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-geo@3.5.21-next-7.0...@aws-amplify/amplify-category-geo@3.5.22-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [3.5.21](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-geo@3.5.20...@aws-amplify/amplify-category-geo@3.5.21) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-geo diff --git a/packages/amplify-category-geo/package.json b/packages/amplify-category-geo/package.json index c0aaa5072fb..a1a0752480e 100644 --- a/packages/amplify-category-geo/package.json +++ b/packages/amplify-category-geo/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-geo", - "version": "3.5.21", + "version": "3.5.22-next-11.0", "description": "Amplify CLI plugin to manage the Geo resources for the project", "repository": { "type": "git", @@ -26,7 +26,7 @@ "access": "public" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "ajv": "^6.12.6", "amplify-headless-interface": "1.17.7", diff --git a/packages/amplify-category-hosting/CHANGELOG.md b/packages/amplify-category-hosting/CHANGELOG.md index b852dfb1020..acd51bcbd73 100644 --- a/packages/amplify-category-hosting/CHANGELOG.md +++ b/packages/amplify-category-hosting/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.5.42-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-hosting@3.5.41-next-7.0...@aws-amplify/amplify-category-hosting@3.5.42-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-category-hosting + + + + + ## [3.5.41](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-hosting@3.5.40...@aws-amplify/amplify-category-hosting@3.5.41) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-hosting diff --git a/packages/amplify-category-hosting/package.json b/packages/amplify-category-hosting/package.json index 7cfb10181e8..042bdbed352 100644 --- a/packages/amplify-category-hosting/package.json +++ b/packages/amplify-category-hosting/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-hosting", - "version": "3.5.41", + "version": "3.5.42-next-11.0", "description": "amplify-cli hosting plugin", "repository": { "type": "git", @@ -21,7 +21,7 @@ "test": "jest --logHeapUsage --coverage" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "chalk": "^4.1.1", "fs-extra": "^8.1.0", diff --git a/packages/amplify-category-interactions/CHANGELOG.md b/packages/amplify-category-interactions/CHANGELOG.md index 1b50e0ae257..bf96187b7f9 100644 --- a/packages/amplify-category-interactions/CHANGELOG.md +++ b/packages/amplify-category-interactions/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.1.35-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-interactions@5.1.34-next-7.0...@aws-amplify/amplify-category-interactions@5.1.35-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-category-interactions + + + + + ## [5.1.34](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-interactions@5.1.33...@aws-amplify/amplify-category-interactions@5.1.34) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-interactions diff --git a/packages/amplify-category-interactions/package.json b/packages/amplify-category-interactions/package.json index c785d166fbc..ca59944dc1a 100644 --- a/packages/amplify-category-interactions/package.json +++ b/packages/amplify-category-interactions/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-interactions", - "version": "5.1.34", + "version": "5.1.35-next-11.0", "description": "amplify-cli interactions plugin", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "fs-extra": "^8.1.0", "fuzzy": "^0.1.3", diff --git a/packages/amplify-category-notifications/CHANGELOG.md b/packages/amplify-category-notifications/CHANGELOG.md index 9158729ec37..b7c57527923 100644 --- a/packages/amplify-category-notifications/CHANGELOG.md +++ b/packages/amplify-category-notifications/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.26.32-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-notifications@2.26.31-next-7.0...@aws-amplify/amplify-category-notifications@2.26.32-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-category-notifications + + + + + ## [2.26.31](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-notifications@2.26.30...@aws-amplify/amplify-category-notifications@2.26.31) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-notifications diff --git a/packages/amplify-category-notifications/package.json b/packages/amplify-category-notifications/package.json index a189928aed4..b8f422fd031 100644 --- a/packages/amplify-category-notifications/package.json +++ b/packages/amplify-category-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-notifications", - "version": "2.26.31", + "version": "2.26.32-next-11.0", "description": "amplify-cli notifications plugin", "repository": { "type": "git", @@ -26,10 +26,10 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", - "@aws-amplify/amplify-provider-awscloudformation": "8.11.7", + "@aws-amplify/amplify-provider-awscloudformation": "8.11.8-next-11.0", "aws-sdk": "^2.1464.0", "chalk": "^4.1.1", "fs-extra": "^8.1.0", diff --git a/packages/amplify-category-predictions/CHANGELOG.md b/packages/amplify-category-predictions/CHANGELOG.md index 57e7b39ad3f..224ffc7b625 100644 --- a/packages/amplify-category-predictions/CHANGELOG.md +++ b/packages/amplify-category-predictions/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.5.22-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-predictions@5.5.21-next-7.0...@aws-amplify/amplify-category-predictions@5.5.22-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-category-predictions + + + + + ## [5.5.21](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-predictions@5.5.20...@aws-amplify/amplify-category-predictions@5.5.21) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-predictions diff --git a/packages/amplify-category-predictions/package.json b/packages/amplify-category-predictions/package.json index d21b19c2ba5..f0e1fc93470 100644 --- a/packages/amplify-category-predictions/package.json +++ b/packages/amplify-category-predictions/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-predictions", - "version": "5.5.21", + "version": "5.5.22-next-11.0", "description": "amplify-cli predictions plugin", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "aws-sdk": "^2.1464.0", "chalk": "^4.1.1", diff --git a/packages/amplify-category-storage/CHANGELOG.md b/packages/amplify-category-storage/CHANGELOG.md index bcbbed6b806..640fae8c1d4 100644 --- a/packages/amplify-category-storage/CHANGELOG.md +++ b/packages/amplify-category-storage/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.5.21-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-storage@5.5.20-next-7.0...@aws-amplify/amplify-category-storage@5.5.21-next-11.0) (2025-05-01) + + +### Bug Fixes + +* migration test file extension ([74b58eb](https://github.com/aws-amplify/amplify-cli/commit/74b58ebf250b9a0ffbf7469fae76ca758a7b077a)) +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [5.5.20](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-storage@5.5.19...@aws-amplify/amplify-category-storage@5.5.20) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-category-storage diff --git a/packages/amplify-category-storage/package.json b/packages/amplify-category-storage/package.json index dd4acdf5c8b..9863a927e5b 100644 --- a/packages/amplify-category-storage/package.json +++ b/packages/amplify-category-storage/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-category-storage", - "version": "5.5.20", + "version": "5.5.21-next-11.0", "description": "amplify-cli storage plugin", "repository": { "type": "git", @@ -27,11 +27,11 @@ "access": "public" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "@aws-amplify/amplify-util-import": "2.8.3", - "@aws-amplify/cli-extensibility-helper": "3.0.38", + "@aws-amplify/cli-extensibility-helper": "3.0.39-next-11.0", "amplify-headless-interface": "1.17.7", "amplify-util-headless-input": "1.9.18", "aws-cdk-lib": "~2.189.1", diff --git a/packages/amplify-cli-core/CHANGELOG.md b/packages/amplify-cli-core/CHANGELOG.md index 17078d94ece..c179c36772e 100644 --- a/packages/amplify-cli-core/CHANGELOG.md +++ b/packages/amplify-cli-core/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [4.4.2-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-cli-core@4.4.1-next-7.0...@aws-amplify/amplify-cli-core@4.4.2-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [4.4.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-cli-core@4.4.0...@aws-amplify/amplify-cli-core@4.4.1) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-cli-core diff --git a/packages/amplify-cli-core/package.json b/packages/amplify-cli-core/package.json index 9715180656f..01971d200c6 100644 --- a/packages/amplify-cli-core/package.json +++ b/packages/amplify-cli-core/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-cli-core", - "version": "4.4.1", + "version": "4.4.2-next-11.0", "description": "Amplify CLI Core", "repository": { "type": "git", diff --git a/packages/amplify-cli-extensibility-helper/CHANGELOG.md b/packages/amplify-cli-extensibility-helper/CHANGELOG.md index ad40d97651e..60615821d0c 100644 --- a/packages/amplify-cli-extensibility-helper/CHANGELOG.md +++ b/packages/amplify-cli-extensibility-helper/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.0.39-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/cli-extensibility-helper@3.0.38-next-7.0...@aws-amplify/cli-extensibility-helper@3.0.39-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [3.0.38](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/cli-extensibility-helper@3.0.37...@aws-amplify/cli-extensibility-helper@3.0.38) (2025-04-17) **Note:** Version bump only for package @aws-amplify/cli-extensibility-helper diff --git a/packages/amplify-cli-extensibility-helper/package.json b/packages/amplify-cli-extensibility-helper/package.json index 8fb1437a4f2..ac5c4b70962 100644 --- a/packages/amplify-cli-extensibility-helper/package.json +++ b/packages/amplify-cli-extensibility-helper/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/cli-extensibility-helper", - "version": "3.0.38", + "version": "3.0.39-next-11.0", "description": "Amplify CLI Extensibility Helper utility package", "repository": { "type": "git", @@ -28,8 +28,8 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-category-custom": "3.1.28", - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-category-custom": "3.1.29-next-11.0", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "aws-cdk-lib": "~2.189.1" }, "jest": { diff --git a/packages/amplify-cli-npm/CHANGELOG.md b/packages/amplify-cli-npm/CHANGELOG.md index b067d058a32..9eb58fc3d8e 100644 --- a/packages/amplify-cli-npm/CHANGELOG.md +++ b/packages/amplify-cli-npm/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [13.0.2-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/cli@14.0.0-next-10.0...@aws-amplify/cli@13.0.2-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/cli + + + + + ## [13.0.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/cli@13.0.0...@aws-amplify/cli@13.0.1) (2025-04-17) **Note:** Version bump only for package @aws-amplify/cli diff --git a/packages/amplify-cli-npm/package.json b/packages/amplify-cli-npm/package.json index f2fa6646a43..26833017ba5 100644 --- a/packages/amplify-cli-npm/package.json +++ b/packages/amplify-cli-npm/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/cli", - "version": "13.0.1", + "version": "13.0.2-next-11.0", "description": "Amplify CLI", "repository": { "type": "git", @@ -36,7 +36,7 @@ "tar-stream": "^2.2.0" }, "devDependencies": { - "@aws-amplify/cli-internal": "13.0.1", + "@aws-amplify/cli-internal": "13.0.2-next-11.0", "@types/tar": "^6.1.1", "rimraf": "^3.0.2" }, diff --git a/packages/amplify-cli/CHANGELOG.md b/packages/amplify-cli/CHANGELOG.md index 096b0cb4eef..9c4bc98f491 100644 --- a/packages/amplify-cli/CHANGELOG.md +++ b/packages/amplify-cli/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [13.0.2-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/cli-internal@14.0.0-next-10.0...@aws-amplify/cli-internal@13.0.2-next-11.0) (2025-05-01) + + +### Bug Fixes + +* migration test file extension ([74b58eb](https://github.com/aws-amplify/amplify-cli/commit/74b58ebf250b9a0ffbf7469fae76ca758a7b077a)) +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [13.0.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/cli-internal@13.0.0...@aws-amplify/cli-internal@13.0.1) (2025-04-17) **Note:** Version bump only for package @aws-amplify/cli-internal diff --git a/packages/amplify-cli/package.json b/packages/amplify-cli/package.json index 5e4574fdc72..412c179e420 100644 --- a/packages/amplify-cli/package.json +++ b/packages/amplify-cli/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/cli-internal", - "version": "13.0.1", + "version": "13.0.2-next-11.0", "description": "Amplify CLI", "repository": { "type": "git", @@ -34,45 +34,45 @@ "node": ">=12.0.0" }, "dependencies": { - "@aws-amplify/amplify-app": "5.0.42", - "@aws-amplify/amplify-category-analytics": "5.0.41", + "@aws-amplify/amplify-app": "5.0.43-next-11.0", + "@aws-amplify/amplify-category-analytics": "5.0.42-next-11.0", "@aws-amplify/amplify-category-api": "^5.15.0", - "@aws-amplify/amplify-category-auth": "3.7.21", - "@aws-amplify/amplify-category-custom": "3.1.28", - "@aws-amplify/amplify-category-function": "5.7.14", - "@aws-amplify/amplify-category-geo": "3.5.21", - "@aws-amplify/amplify-category-hosting": "3.5.41", - "@aws-amplify/amplify-category-interactions": "5.1.34", - "@aws-amplify/amplify-category-notifications": "2.26.31", - "@aws-amplify/amplify-category-predictions": "5.5.21", - "@aws-amplify/amplify-category-storage": "5.5.20", - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-category-auth": "3.7.22-next-11.0", + "@aws-amplify/amplify-category-custom": "3.1.29-next-11.0", + "@aws-amplify/amplify-category-function": "5.7.15-next-11.0", + "@aws-amplify/amplify-category-geo": "3.5.22-next-11.0", + "@aws-amplify/amplify-category-hosting": "3.5.42-next-11.0", + "@aws-amplify/amplify-category-interactions": "5.1.35-next-11.0", + "@aws-amplify/amplify-category-notifications": "2.26.32-next-11.0", + "@aws-amplify/amplify-category-predictions": "5.5.22-next-11.0", + "@aws-amplify/amplify-category-storage": "5.5.21-next-11.0", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-cli-logger": "1.3.8", "@aws-amplify/amplify-cli-shared-interfaces": "1.2.5", - "@aws-amplify/amplify-console-hosting": "2.5.38", - "@aws-amplify/amplify-container-hosting": "2.8.18", - "@aws-amplify/amplify-dotnet-function-template-provider": "2.7.4", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-console-hosting": "2.5.39-next-11.0", + "@aws-amplify/amplify-container-hosting": "2.8.19-next-11.0", + "@aws-amplify/amplify-dotnet-function-template-provider": "2.7.5-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "@aws-amplify/amplify-frontend-android": "3.5.8", "@aws-amplify/amplify-frontend-flutter": "1.4.7", - "@aws-amplify/amplify-frontend-ios": "3.7.12", - "@aws-amplify/amplify-frontend-javascript": "3.10.22", + "@aws-amplify/amplify-frontend-ios": "3.7.13-next-11.0", + "@aws-amplify/amplify-frontend-javascript": "3.10.23-next-11.0", "@aws-amplify/amplify-go-function-template-provider": "1.4.8", - "@aws-amplify/amplify-nodejs-function-template-provider": "2.10.14", + "@aws-amplify/amplify-nodejs-function-template-provider": "2.10.15-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", - "@aws-amplify/amplify-provider-awscloudformation": "8.11.7", + "@aws-amplify/amplify-provider-awscloudformation": "8.11.8-next-11.0", "@aws-amplify/amplify-python-function-template-provider": "1.4.7", "@aws-amplify/amplify-util-import": "2.8.3", - "@aws-amplify/amplify-util-mock": "5.10.15", - "@aws-amplify/amplify-util-uibuilder": "1.14.19", + "@aws-amplify/amplify-util-mock": "5.10.16-next-11.0", + "@aws-amplify/amplify-util-uibuilder": "1.14.20-next-11.0", "@aws-cdk/cloudformation-diff": "~2.68.0", "amplify-codegen": "^4.10.3", - "amplify-dotnet-function-runtime-provider": "2.1.4", - "amplify-go-function-runtime-provider": "2.3.51", - "amplify-java-function-runtime-provider": "2.3.51", + "amplify-dotnet-function-runtime-provider": "2.1.5-next-11.0", + "amplify-go-function-runtime-provider": "2.3.52-next-11.0", + "amplify-java-function-runtime-provider": "2.3.52-next-11.0", "amplify-java-function-template-provider": "1.5.24", - "amplify-nodejs-function-runtime-provider": "2.5.29", - "amplify-python-function-runtime-provider": "2.4.51", + "amplify-nodejs-function-runtime-provider": "2.5.30-next-11.0", + "amplify-python-function-runtime-provider": "2.4.52-next-11.0", "aws-cdk-lib": "~2.189.1", "aws-sdk": "^2.1464.0", "chalk": "^4.1.1", diff --git a/packages/amplify-console-hosting/CHANGELOG.md b/packages/amplify-console-hosting/CHANGELOG.md index 319981fb9b7..e6d1e2f518f 100644 --- a/packages/amplify-console-hosting/CHANGELOG.md +++ b/packages/amplify-console-hosting/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.5.39-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-console-hosting@2.5.38-next-7.0...@aws-amplify/amplify-console-hosting@2.5.39-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-console-hosting + + + + + ## [2.5.38](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-console-hosting@2.5.37...@aws-amplify/amplify-console-hosting@2.5.38) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-console-hosting diff --git a/packages/amplify-console-hosting/package.json b/packages/amplify-console-hosting/package.json index 75e8ea170c2..94bc28182f7 100644 --- a/packages/amplify-console-hosting/package.json +++ b/packages/amplify-console-hosting/package.json @@ -1,14 +1,14 @@ { "name": "@aws-amplify/amplify-console-hosting", - "version": "2.5.38", + "version": "2.5.39-next-11.0", "description": "cli plugin for AWS Amplify Console hosting", "main": "lib/index.js", "types": "lib/index.d.ts", "author": "Amazon Web Services", "license": "Apache-2.0", "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "archiver": "^5.3.0", "chalk": "^4.1.1", "cli-table3": "^0.6.0", diff --git a/packages/amplify-console-integration-tests/CHANGELOG.md b/packages/amplify-console-integration-tests/CHANGELOG.md index 1998aeef06c..ea40f6bbd46 100644 --- a/packages/amplify-console-integration-tests/CHANGELOG.md +++ b/packages/amplify-console-integration-tests/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.11.23-next-11.0](https://github.com/aws-amplify/amplify-console-integration-tests/compare/@aws-amplify/amplify-console-integration-tests@2.11.22-next-7.0...@aws-amplify/amplify-console-integration-tests@2.11.23-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-console-integration-tests + + + + + ## [2.11.22](https://github.com/aws-amplify/amplify-console-integration-tests/compare/@aws-amplify/amplify-console-integration-tests@2.11.21...@aws-amplify/amplify-console-integration-tests@2.11.22) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-console-integration-tests diff --git a/packages/amplify-console-integration-tests/package.json b/packages/amplify-console-integration-tests/package.json index 1aa8f3e0dc2..28bbb1abf80 100644 --- a/packages/amplify-console-integration-tests/package.json +++ b/packages/amplify-console-integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-console-integration-tests", - "version": "2.11.22", + "version": "2.11.23-next-11.0", "description": "", "repository": { "type": "git", @@ -21,8 +21,8 @@ "setup-profile": "ts-node ./src/setup-profile.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-e2e-core": "5.7.4", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-e2e-core": "5.7.5-next-11.0", "@types/ini": "^1.3.30", "aws-sdk": "^2.1464.0", "dotenv": "^8.2.0", diff --git a/packages/amplify-container-hosting/CHANGELOG.md b/packages/amplify-container-hosting/CHANGELOG.md index 040c5c70930..3196a7732cb 100644 --- a/packages/amplify-container-hosting/CHANGELOG.md +++ b/packages/amplify-container-hosting/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.8.19-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-container-hosting@2.8.18-next-7.0...@aws-amplify/amplify-container-hosting@2.8.19-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-container-hosting + + + + + ## [2.8.18](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-container-hosting@2.8.17...@aws-amplify/amplify-container-hosting@2.8.18) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-container-hosting diff --git a/packages/amplify-container-hosting/package.json b/packages/amplify-container-hosting/package.json index bb986dd3d9a..462319c0a7e 100644 --- a/packages/amplify-container-hosting/package.json +++ b/packages/amplify-container-hosting/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-container-hosting", - "version": "2.8.18", + "version": "2.8.19-next-11.0", "description": "amplify-cli hosting plugin for containers", "repository": { "type": "git", @@ -27,8 +27,8 @@ }, "dependencies": { "@aws-amplify/amplify-category-api": "^5.15.0", - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "fs-extra": "^8.1.0", "inquirer": "^7.3.3", "mime-types": "^2.1.26", diff --git a/packages/amplify-dotnet-function-runtime-provider/CHANGELOG.md b/packages/amplify-dotnet-function-runtime-provider/CHANGELOG.md index 329975925c0..9ead262d0d0 100644 --- a/packages/amplify-dotnet-function-runtime-provider/CHANGELOG.md +++ b/packages/amplify-dotnet-function-runtime-provider/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.5-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-dotnet-function-runtime-provider@2.1.4-next-7.0...amplify-dotnet-function-runtime-provider@2.1.5-next-11.0) (2025-05-01) + +**Note:** Version bump only for package amplify-dotnet-function-runtime-provider + + + + + ## [2.1.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-dotnet-function-runtime-provider@2.1.3...amplify-dotnet-function-runtime-provider@2.1.4) (2025-04-17) **Note:** Version bump only for package amplify-dotnet-function-runtime-provider diff --git a/packages/amplify-dotnet-function-runtime-provider/package.json b/packages/amplify-dotnet-function-runtime-provider/package.json index 5f8056d963e..9fc9b0b4c07 100644 --- a/packages/amplify-dotnet-function-runtime-provider/package.json +++ b/packages/amplify-dotnet-function-runtime-provider/package.json @@ -1,6 +1,6 @@ { "name": "amplify-dotnet-function-runtime-provider", - "version": "2.1.4", + "version": "2.1.5-next-11.0", "description": "Provides functionality related to functions in .NET on AWS", "repository": { "type": "git", @@ -22,7 +22,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "@aws-amplify/amplify-prompts": "2.8.6", "execa": "^5.1.1", diff --git a/packages/amplify-dotnet-function-template-provider/CHANGELOG.md b/packages/amplify-dotnet-function-template-provider/CHANGELOG.md index 452fd95c860..b0b5cd84234 100644 --- a/packages/amplify-dotnet-function-template-provider/CHANGELOG.md +++ b/packages/amplify-dotnet-function-template-provider/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.7.5-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-dotnet-function-template-provider@2.7.4-next-7.0...@aws-amplify/amplify-dotnet-function-template-provider@2.7.5-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-dotnet-function-template-provider + + + + + ## [2.7.4](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-dotnet-function-template-provider@2.7.3...@aws-amplify/amplify-dotnet-function-template-provider@2.7.4) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-dotnet-function-template-provider diff --git a/packages/amplify-dotnet-function-template-provider/package.json b/packages/amplify-dotnet-function-template-provider/package.json index 11d2d3be1f7..992647acdae 100644 --- a/packages/amplify-dotnet-function-template-provider/package.json +++ b/packages/amplify-dotnet-function-template-provider/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-dotnet-function-template-provider", - "version": "2.7.4", + "version": "2.7.5-next-11.0", "description": ".NET templates supplied by the Amplify Team", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "graphql-transformer-core": "^8.2.17" }, diff --git a/packages/amplify-dynamodb-simulator/CHANGELOG.md b/packages/amplify-dynamodb-simulator/CHANGELOG.md index c98aa1c4087..9624dbd8434 100644 --- a/packages/amplify-dynamodb-simulator/CHANGELOG.md +++ b/packages/amplify-dynamodb-simulator/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.9.24-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-dynamodb-simulator@2.9.23-next-7.0...amplify-dynamodb-simulator@2.9.24-next-11.0) (2025-05-01) + +**Note:** Version bump only for package amplify-dynamodb-simulator + + + + + ## [2.9.23](https://github.com/aws-amplify/amplify-cli/compare/amplify-dynamodb-simulator@2.9.22...amplify-dynamodb-simulator@2.9.23) (2025-04-17) **Note:** Version bump only for package amplify-dynamodb-simulator diff --git a/packages/amplify-dynamodb-simulator/package.json b/packages/amplify-dynamodb-simulator/package.json index ccf92792917..31437cff039 100644 --- a/packages/amplify-dynamodb-simulator/package.json +++ b/packages/amplify-dynamodb-simulator/package.json @@ -1,6 +1,6 @@ { "name": "amplify-dynamodb-simulator", - "version": "2.9.23", + "version": "2.9.24-next-11.0", "description": "DynamoDB emulator nodejs wrapper", "repository": { "type": "git", @@ -21,7 +21,7 @@ "test": "jest --logHeapUsage --passWithNoTests" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "aws-sdk": "^2.1464.0", "detect-port": "^1.3.0", "execa": "^5.1.1", diff --git a/packages/amplify-e2e-core/CHANGELOG.md b/packages/amplify-e2e-core/CHANGELOG.md index 6af35fc646a..689971db90e 100644 --- a/packages/amplify-e2e-core/CHANGELOG.md +++ b/packages/amplify-e2e-core/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.7.5-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-e2e-core@5.7.4-next-7.0...@aws-amplify/amplify-e2e-core@5.7.5-next-11.0) (2025-05-01) + + +### Bug Fixes + +* migration test file extension ([74b58eb](https://github.com/aws-amplify/amplify-cli/commit/74b58ebf250b9a0ffbf7469fae76ca758a7b077a)) + + + + + ## [5.7.4](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-e2e-core@5.7.3...@aws-amplify/amplify-e2e-core@5.7.4) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-e2e-core diff --git a/packages/amplify-e2e-core/package.json b/packages/amplify-e2e-core/package.json index 764e3b77b21..2488e3a426c 100644 --- a/packages/amplify-e2e-core/package.json +++ b/packages/amplify-e2e-core/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-e2e-core", - "version": "5.7.4", + "version": "5.7.5-next-11.0", "description": "", "repository": { "type": "git", @@ -22,7 +22,7 @@ "clean": "rimraf ./lib tsconfig.tsbuildinfo" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-sdk/client-sts": "3.624.0", "@aws-sdk/credential-providers": "3.624.0", "amplify-headless-interface": "1.17.7", diff --git a/packages/amplify-e2e-core/src/init/deleteProject.ts b/packages/amplify-e2e-core/src/init/deleteProject.ts index ef9594065f0..9019953d490 100644 --- a/packages/amplify-e2e-core/src/init/deleteProject.ts +++ b/packages/amplify-e2e-core/src/init/deleteProject.ts @@ -27,6 +27,6 @@ export const deleteProject = async ( .wait('Project deleted locally.') .runAsync(); } catch (e) { - console.log('Error on deleting project at:', cwd); + console.log('Error on deleting project at:', cwd, e); } }; diff --git a/packages/amplify-e2e-tests/CHANGELOG.md b/packages/amplify-e2e-tests/CHANGELOG.md index e7cf0b1923c..913ad1dfe72 100644 --- a/packages/amplify-e2e-tests/CHANGELOG.md +++ b/packages/amplify-e2e-tests/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [4.11.7-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-e2e-tests@4.11.6-next-7.0...amplify-e2e-tests@4.11.7-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [4.11.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-e2e-tests@4.11.5...amplify-e2e-tests@4.11.6) (2025-04-17) **Note:** Version bump only for package amplify-e2e-tests diff --git a/packages/amplify-e2e-tests/package.json b/packages/amplify-e2e-tests/package.json index 6eeadec656f..e271af04b8a 100644 --- a/packages/amplify-e2e-tests/package.json +++ b/packages/amplify-e2e-tests/package.json @@ -1,6 +1,6 @@ { "name": "amplify-e2e-tests", - "version": "4.11.6", + "version": "4.11.7-next-11.0", "description": "", "repository": { "type": "git", @@ -25,10 +25,10 @@ "smoketest": "yarn e2e --testPathPattern='src/__tests__/smoke-tests/.*.test.ts'" }, "dependencies": { - "@aws-amplify/amplify-category-auth": "3.7.21", - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-e2e-core": "5.7.4", - "@aws-amplify/amplify-opensearch-simulator": "1.7.19", + "@aws-amplify/amplify-category-auth": "3.7.22-next-11.0", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-e2e-core": "5.7.5-next-11.0", + "@aws-amplify/amplify-opensearch-simulator": "1.7.20-next-11.0", "@aws-amplify/graphql-transformer-core": "^2.11.1", "@aws-sdk/client-appsync": "3.624.0", "@aws-sdk/client-dynamodb": "3.624.0", @@ -36,9 +36,9 @@ "@aws-sdk/client-ssm": "3.624.0", "@babel/core": "^7.23.2", "@babel/plugin-transform-modules-commonjs": "7.10.4", - "amplify-dynamodb-simulator": "2.9.23", + "amplify-dynamodb-simulator": "2.9.24-next-11.0", "amplify-headless-interface": "1.17.7", - "amplify-storage-simulator": "1.11.6", + "amplify-storage-simulator": "1.11.7-next-11.0", "aws-amplify": "^5.3.16", "aws-appsync": "^4.1.1", "aws-cdk-lib": "~2.189.1", diff --git a/packages/amplify-environment-parameters/CHANGELOG.md b/packages/amplify-environment-parameters/CHANGELOG.md index 6cc248eb736..776ca9129f8 100644 --- a/packages/amplify-environment-parameters/CHANGELOG.md +++ b/packages/amplify-environment-parameters/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.9.20-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-environment-parameters@1.9.19-next-7.0...@aws-amplify/amplify-environment-parameters@1.9.20-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-environment-parameters + + + + + ## [1.9.19](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-environment-parameters@1.9.18...@aws-amplify/amplify-environment-parameters@1.9.19) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-environment-parameters diff --git a/packages/amplify-environment-parameters/package.json b/packages/amplify-environment-parameters/package.json index ecbe297c06f..ae6efcbbb1d 100644 --- a/packages/amplify-environment-parameters/package.json +++ b/packages/amplify-environment-parameters/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-environment-parameters", - "version": "1.9.19", + "version": "1.9.20-next-11.0", "description": "Amplify CLI environment parameter manager", "repository": { "type": "git", @@ -27,7 +27,7 @@ "generate-schemas": "mkdirp lib/schemas src/schemas && ts-json-schema-generator --path src/backend-parameters.d.ts --type BackendParameters --no-type-check --out lib/schemas/BackendParameters.schema.json && copyfiles --flat lib/schemas/BackendParameters.schema.json src/schemas" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "ajv": "^6.12.6", "lodash": "^4.17.21" }, diff --git a/packages/amplify-frontend-ios/CHANGELOG.md b/packages/amplify-frontend-ios/CHANGELOG.md index 74094e4f0d3..f5b67e41a0d 100644 --- a/packages/amplify-frontend-ios/CHANGELOG.md +++ b/packages/amplify-frontend-ios/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.7.13-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-frontend-ios@3.7.12-next-7.0...@aws-amplify/amplify-frontend-ios@3.7.13-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-frontend-ios + + + + + ## [3.7.12](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-frontend-ios@3.7.11...@aws-amplify/amplify-frontend-ios@3.7.12) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-frontend-ios diff --git a/packages/amplify-frontend-ios/package.json b/packages/amplify-frontend-ios/package.json index 2d718d35710..24f81a77e84 100644 --- a/packages/amplify-frontend-ios/package.json +++ b/packages/amplify-frontend-ios/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-frontend-ios", - "version": "3.7.12", + "version": "3.7.13-next-11.0", "description": "amplify-cli front-end plugin for xcode projects", "repository": { "type": "git", @@ -24,7 +24,7 @@ "test-watch": "jest --watch" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "execa": "^5.1.1", "fs-extra": "^8.1.0", "graphql-config": "^2.2.1", diff --git a/packages/amplify-frontend-javascript/CHANGELOG.md b/packages/amplify-frontend-javascript/CHANGELOG.md index e235a40534b..97c6655af34 100644 --- a/packages/amplify-frontend-javascript/CHANGELOG.md +++ b/packages/amplify-frontend-javascript/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [3.10.23-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-frontend-javascript@3.10.22-next-7.0...@aws-amplify/amplify-frontend-javascript@3.10.23-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-frontend-javascript + + + + + ## [3.10.22](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-frontend-javascript@3.10.21...@aws-amplify/amplify-frontend-javascript@3.10.22) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-frontend-javascript diff --git a/packages/amplify-frontend-javascript/package.json b/packages/amplify-frontend-javascript/package.json index bb82e99efcf..ef7cbf17e3e 100644 --- a/packages/amplify-frontend-javascript/package.json +++ b/packages/amplify-frontend-javascript/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-frontend-javascript", - "version": "3.10.22", + "version": "3.10.23-next-11.0", "description": "amplify-cli front-end plugin for JavaScript projects", "scripts": { "test": "jest --logHeapUsage", @@ -23,7 +23,7 @@ "access": "public" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@babel/core": "^7.23.2", "@babel/plugin-transform-modules-commonjs": "7.10.4", "chalk": "^4.1.1", diff --git a/packages/amplify-gen1-codegen-auth-adapter/.gitignore b/packages/amplify-gen1-codegen-auth-adapter/.gitignore new file mode 100644 index 00000000000..c3af857904e --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/amplify-gen1-codegen-auth-adapter/API.md b/packages/amplify-gen1-codegen-auth-adapter/API.md new file mode 100644 index 00000000000..1898eb0efd4 --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/API.md @@ -0,0 +1,62 @@ +## API Report File for "@aws-amplify/amplify-gen1-codegen-auth-adapter" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AuthDefinition } from '@aws-amplify/amplify-gen2-codegen'; +import { GroupType } from '@aws-sdk/client-cognito-identity-provider'; +import { IdentityProviderType } from '@aws-sdk/client-cognito-identity-provider'; +import { LambdaConfigType } from '@aws-sdk/client-cognito-identity-provider'; +import { ProviderDescription } from '@aws-sdk/client-cognito-identity-provider'; +import { ReferenceAuth } from '@aws-amplify/amplify-gen2-codegen/src/auth/source_builder'; +import { SoftwareTokenMfaConfigType } from '@aws-sdk/client-cognito-identity-provider'; +import { UserPoolClientType } from '@aws-sdk/client-cognito-identity-provider'; +import { UserPoolMfaType } from '@aws-sdk/client-cognito-identity-provider'; +import { UserPoolType } from '@aws-sdk/client-cognito-identity-provider'; + +// @public (undocumented) +export interface AuthSynthesizerOptions { + // (undocumented) + authTriggerConnections?: AuthTriggerConnectionSourceMap; + // (undocumented) + guestLogin?: boolean; + // (undocumented) + identityGroups?: GroupType[]; + // (undocumented) + identityPoolName?: string; + // (undocumented) + identityProviders?: ProviderDescription[]; + // (undocumented) + identityProvidersDetails?: IdentityProviderType[]; + // (undocumented) + mfaConfig?: UserPoolMfaType; + // (undocumented) + referenceAuth?: ReferenceAuth; + // (undocumented) + totpConfig?: SoftwareTokenMfaConfigType; + // (undocumented) + userPool: UserPoolType; + // (undocumented) + userPoolClient?: UserPoolClientType; + // (undocumented) + webClient?: UserPoolClientType; +} + +// @public (undocumented) +export interface AuthTriggerConnection { + // (undocumented) + lambdaFunctionName: string; + // (undocumented) + triggerType: keyof LambdaConfigType; +} + +// @public (undocumented) +export type AuthTriggerConnectionSourceMap = Partial>; + +// @public (undocumented) +export const getAuthDefinition: ({ userPool, identityPoolName, identityProviders, identityProvidersDetails, identityGroups, webClient, authTriggerConnections, guestLogin, referenceAuth, mfaConfig, totpConfig, userPoolClient, }: AuthSynthesizerOptions) => AuthDefinition; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/amplify-gen1-codegen-auth-adapter/CHANGELOG.md b/packages/amplify-gen1-codegen-auth-adapter/CHANGELOG.md new file mode 100644 index 00000000000..e3695e92c23 --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/CHANGELOG.md @@ -0,0 +1,152 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-9.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-7.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-9.0) (2025-04-22) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-auth-adapter + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-6.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-7.0) (2025-04-19) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-auth-adapter + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-5.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-6.0) (2025-03-21) + + +### Bug Fixes + +* migration qa feedback ([0f50d1c](https://github.com/aws-amplify/amplify-cli/commit/0f50d1c0ff2607c63cd3bcdd1589d38940a2b6d4)) + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-4.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-5.0) (2025-03-19) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-auth-adapter + + + + + +# [0.1.0-next-4.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-4.0) (2025-03-12) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-auth-adapter + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-beta-latest.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next.0) (2025-02-14) + + +### Bug Fixes + +* conditional data codegen, phone attribute ([0d5c59f](https://github.com/aws-amplify/amplify-cli/commit/0d5c59fae2849277bced4e4f0c0529d916c0e165)) + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-alpha.1...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-beta-latest.0) (2025-02-12) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-auth-adapter + + + + + +# [0.1.0-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-alpha.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-alpha.1) (2024-12-05) + + +### Bug Fixes + +* added user pool client codegen ([29a7b5e](https://github.com/aws-amplify/amplify-cli/commit/29a7b5eed227b1fa3e5df670cd527477fe5df321)) +* fixed ESLint ([3d61929](https://github.com/aws-amplify/amplify-cli/commit/3d61929695d38c6642bcd9f6fb01677a7c86be4a)) + + + + + +# [0.1.0-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-gen2-migrations-alpha.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-alpha.0) (2024-11-21) + + +### Bug Fixes + +* include only required userAttributes and generate identityPoolName in backend file ([76f1bf8](https://github.com/aws-amplify/amplify-cli/commit/76f1bf8bdbc9135bf0f9c983fd2f5448a169af42)) +* resolve api extract errors ([1ee4481](https://github.com/aws-amplify/amplify-cli/commit/1ee4481b45ee1ce24b1f0c521459095888e0b59e)) +* update API.md file for gen1-gen2 codegen ([2531475](https://github.com/aws-amplify/amplify-cli/commit/2531475bb5b65ab3d2a9cdf63b97f81a0916069b)) + + +### Features + +* ref auth codegen ([d6b1f28](https://github.com/aws-amplify/amplify-cli/commit/d6b1f288299c03d8809ccb3bcf8b74129c850e56)) + + + + + +# [0.1.0-gen2-migrations-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-gen2-migration-test-alpha.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-gen2-migrations-alpha.0) (2024-10-10) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-auth-adapter + + + + + +# [0.1.0-gen2-migration-test-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-gen2-migrations-test.0...@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-gen2-migration-test-alpha.0) (2024-09-26) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-auth-adapter + + + + + +# 0.1.0-gen2-migrations-test.0 (2024-09-23) + + +### Bug Fixes + +* add attribute mapping for external providers ([4f4d9fd](https://github.com/aws-amplify/amplify-cli/commit/4f4d9fd261eefbaca6bd3a563b03e59573869e91)) +* add checks for no min/max values provided for custom attributes ([ce55cb3](https://github.com/aws-amplify/amplify-cli/commit/ce55cb37d99e4e0f4f7325fd9626fa950eb34a9e)) +* add relevant removed code due to incorrect merge ([fe1ab64](https://github.com/aws-amplify/amplify-cli/commit/fe1ab6430a668fb55e280552cb358ae97503d002)) +* add test cases for source builder and synthesizer ([c7bb106](https://github.com/aws-amplify/amplify-cli/commit/c7bb10681a1cbdd1e92eebcc81357399cf681362)) +* correct package versions; remove unused import ([2855e28](https://github.com/aws-amplify/amplify-cli/commit/2855e28744bc0d319ff85d7a7a1a36d5fbdad253)) +* extract api ([6f4c58b](https://github.com/aws-amplify/amplify-cli/commit/6f4c58b947fa3be4c2c7c200484fa46b6823bb30)) +* lint spellcheck and unexpected any error ([5b85e96](https://github.com/aws-amplify/amplify-cli/commit/5b85e96ae87ab3278313010a8b0837b61cac37d7)) +* make gen2 migration packages public ([a7832cb](https://github.com/aws-amplify/amplify-cli/commit/a7832cb622cabf3eec3f770393477256117ea47d)) +* resolve incorrect mfaconifg option ([5f1dd79](https://github.com/aws-amplify/amplify-cli/commit/5f1dd79bbebab1616a5752524d2ecb0ec255fd1a)) +* resolve lint and extract-api errors ([e924e3f](https://github.com/aws-amplify/amplify-cli/commit/e924e3f871e1c58767c2088c0fa8b9dc1cbfb7ec)) +* resolve policies generated in backend.ts ([12c923a](https://github.com/aws-amplify/amplify-cli/commit/12c923a2f7f513641623befdfd3ebca97429919c)) +* resolve test errors ([b6795bd](https://github.com/aws-amplify/amplify-cli/commit/b6795bd6b4918bdb3e0ad3e5ec5e9c9e642f5b56)) + + +### Features + +* add comments to gen1 triggers ([#13866](https://github.com/aws-amplify/amplify-cli/issues/13866)) ([2ec9470](https://github.com/aws-amplify/amplify-cli/commit/2ec947084a89bb000f2b34cc2662121e8cf04fb6)) +* added custom attributes codegen ([0b44538](https://github.com/aws-amplify/amplify-cli/commit/0b445387e45faaa851df93d76cdcdddb6b55f8fe)) +* **cli:** initial migration merge ([f803827](https://github.com/aws-amplify/amplify-cli/commit/f8038278b95d321aef4ff75b1bd5a604815fc821)) +* **cli:** initial migration merge ([#13856](https://github.com/aws-amplify/amplify-cli/issues/13856)) ([ebe5cd0](https://github.com/aws-amplify/amplify-cli/commit/ebe5cd046cfb18c38ffdce17610ed3a133cc9d44)) +* configure username codegen ([f032b76](https://github.com/aws-amplify/amplify-cli/commit/f032b762c870b8d50729ab044eeae87be880606e)) +* fixed lint and ran yarn extract-api ([b4f256c](https://github.com/aws-amplify/amplify-cli/commit/b4f256c3b433a38974f7a8612505d1c7c21befeb)) +* friendly userpool name codegen ([b03e5b0](https://github.com/aws-amplify/amplify-cli/commit/b03e5b03ab7fc0a70ff3981b1232c61edc0fc3a3)) +* oauth flows codegen ([8858ef9](https://github.com/aws-amplify/amplify-cli/commit/8858ef92d2f005d6ebe5363e8bb8696a9a72e8ed)) +* oauth scopes codegen ([a0edbc1](https://github.com/aws-amplify/amplify-cli/commit/a0edbc1af025ed6058ed9098da240a05f68384f2)) +* oidc/saml external providers codegen ([f248955](https://github.com/aws-amplify/amplify-cli/commit/f2489550925e2f90a53a7d0f833d53571a546ae1)) +* read/write permissions for attributes codegen ([36021a3](https://github.com/aws-amplify/amplify-cli/commit/36021a35ec554682c4aca0b32d5a82d85c04f749)) +* signup user attributes/groups auth codegen ([bacb17b](https://github.com/aws-amplify/amplify-cli/commit/bacb17b29f3bd55ac9d28b55903d4091a5786b15)) +* social auth codegen ([96cc8d5](https://github.com/aws-amplify/amplify-cli/commit/96cc8d580b39ba80745fd235bd00f2b724962adc)) +* storage triggers ([#13869](https://github.com/aws-amplify/amplify-cli/issues/13869)) ([3847399](https://github.com/aws-amplify/amplify-cli/commit/38473994e563cd90452ecc50639ea056bb8dd039)) +* unauthenticated logins codegen ([2d0b700](https://github.com/aws-amplify/amplify-cli/commit/2d0b700f099ceb36b70ab0745a562bcdd5f5ce4b)) +* update functions codegen ([a2acb75](https://github.com/aws-amplify/amplify-cli/commit/a2acb756e09ea22c59a61f05cbfbb4beec7adfbd)) +* update functions codegen ([1ef8938](https://github.com/aws-amplify/amplify-cli/commit/1ef89380028856e39cfcb2b55e8fd1bd7f6e41ed)) +* userpool groups precedence based sorting ([184c19c](https://github.com/aws-amplify/amplify-cli/commit/184c19c54c22f263f9baa734fde2d60f6c9e8663)) diff --git a/packages/amplify-gen1-codegen-auth-adapter/package.json b/packages/amplify-gen1-codegen-auth-adapter/package.json new file mode 100644 index 00000000000..ce0b9153bda --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/package.json @@ -0,0 +1,53 @@ +{ + "name": "@aws-amplify/amplify-gen1-codegen-auth-adapter", + "version": "0.1.0-next-9.0", + "type": "commonjs", + "main": "lib/index.js", + "devDependencies": { + "jest": "^29.5.0", + "typescript": "^5.4.5" + }, + "dependencies": { + "@aws-amplify/amplify-gen2-codegen": "0.1.0-next-9.0", + "@aws-amplify/auth-construct": "^1.1.5", + "@aws-sdk/client-amplify": "^3.592.0", + "@aws-sdk/client-cognito-identity": "^3.592.0", + "@aws-sdk/client-cognito-identity-provider": "^3.592.0" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "pretest": "mkdir -p coverage", + "test": "jest --logHeapUsage", + "build": "tsc", + "watch": "tsc -w", + "extract-api": "ts-node ../../scripts/extract-api.ts" + }, + "author": "", + "license": "Apache-2.0", + "description": "" +} diff --git a/packages/amplify-gen1-codegen-auth-adapter/src/auth_render_adapter.test.ts b/packages/amplify-gen1-codegen-auth-adapter/src/auth_render_adapter.test.ts new file mode 100644 index 00000000000..539d8fd89b2 --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/src/auth_render_adapter.test.ts @@ -0,0 +1,616 @@ +import assert from 'node:assert'; +import { IdentityProviderTypeType, LambdaConfigType, PasswordPolicyType, UserPoolMfaType } from '@aws-sdk/client-cognito-identity-provider'; +import { DEFAULT_PASSWORD_SETTINGS, getAuthDefinition } from './auth_render_adapter'; +import { Attribute, AuthTriggerEvents, StandardAttribute, StandardAttributes } from '@aws-amplify/amplify-gen2-codegen'; +/** + * @see https://github.com/aws-amplify/amplify-backend/blob/5d78622c7fd6fb050da11baff1295b9be0bd2eae/packages/auth-construct/src/construct.test.ts#L578 + * for examples of assertions in the cli codebase + */ + +void describe('auth codegen', () => { + void describe('identity providers', () => { + void it('contains google login if Google identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProviders: [{ ProviderType: IdentityProviderTypeType.Google, ProviderName: 'Google' }], + }); + assert(result.loginOptions?.googleLogin); + }); + void it('contains apple login if SignInWithApple identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProviders: [{ ProviderType: IdentityProviderTypeType.SignInWithApple, ProviderName: 'SignInWithApple' }], + }); + assert(result.loginOptions?.appleLogin); + }); + void it('contains amazon login if LoginWithAmazon identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProviders: [{ ProviderType: IdentityProviderTypeType.LoginWithAmazon, ProviderName: 'LoginWithAmazon' }], + }); + assert(result.loginOptions?.amazonLogin); + }); + void it('contains facebook login if Facebook identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProviders: [{ ProviderType: IdentityProviderTypeType.Facebook, ProviderName: 'Facebook' }], + }); + assert(result.loginOptions?.facebookLogin); + }); + void it('contains oidc login if OIDC identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { ProviderType: IdentityProviderTypeType.OIDC, ProviderName: 'OIDC_1', ProviderDetails: { oidc_issuer: 'https://example.com' } }, + { ProviderType: IdentityProviderTypeType.OIDC, ProviderName: 'OIDC_2', ProviderDetails: { oidc_issuer: 'https://example.com' } }, + ], + }); + assert(result.loginOptions?.oidcLogin); + }); + void it('contains SAML login if SAML identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataContent: 'https://example.com' }, + }, + ], + }); + assert(result.loginOptions?.samlLogin); + }); + }); + void describe('OIDC and SAML providers', () => { + void describe('OIDC', () => { + void it('contains name and issuerUrl if OIDC identityProviderDetails is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_1', + ProviderDetails: { + oidc_issuer: 'https://example.com', + authorize_url: 'https://example.com', + token_url: 'https://example.com', + attributes_url: 'https://example.com', + jwks_uri: 'https://example.com', + }, + }, + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_2', + ProviderDetails: { oidc_issuer: 'https://example.com' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.oidcLogin, [ + { + endpoints: { + authorization: 'https://example.com', + token: 'https://example.com', + userInfo: 'https://example.com', + jwksUri: 'https://example.com', + }, + issuerUrl: 'https://example.com', + name: 'OIDC_1', + }, + { issuerUrl: 'https://example.com', name: 'OIDC_2' }, + ]); + }); + void it('contains attributeMapping if AttributeMapping is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_1', + ProviderDetails: { oidc_issuer: 'https://example.com' }, + AttributeMapping: { name: 'name', phone_number: 'phone_number' }, + }, + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_2', + ProviderDetails: { oidc_issuer: 'https://example.com' }, + AttributeMapping: { name: 'name', phone_number: 'phone_number' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.oidcLogin, [ + { issuerUrl: 'https://example.com', name: 'OIDC_1', attributeMapping: { fullname: 'name', phoneNumber: 'phone_number' } }, + { issuerUrl: 'https://example.com', name: 'OIDC_2', attributeMapping: { fullname: 'name', phoneNumber: 'phone_number' } }, + ]); + }); + }); + void describe('SAML', () => { + void it('contains metadataType URL if SAML identityProviderDetails and metadataURL is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataURL: 'https://example.com' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.samlLogin, { + metadata: { metadataContent: 'https://example.com', metadataType: 'URL' }, + name: 'SAML_1', + }); + }); + void it('contains metadataType FILE if SAML identityProviderDetails and metadataContent is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataContent: 'https://example.com' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.samlLogin, { + metadata: { metadataContent: 'https://example.com', metadataType: 'FILE' }, + name: 'SAML_1', + }); + }); + void it('contains attributeMapping if AttributeMapping is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataContent: 'https://example.com' }, + AttributeMapping: { name: 'name', phone_number: 'phone_number' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.samlLogin, { + attributeMapping: { fullname: 'name', phoneNumber: 'phone_number' }, + metadata: { metadataContent: 'https://example.com', metadataType: 'FILE' }, + name: 'SAML_1', + }); + }); + }); + void it('contains oidc login if OIDC identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { ProviderType: IdentityProviderTypeType.OIDC, ProviderName: 'OIDC_1', ProviderDetails: { oidc_issuer: 'https://example.com' } }, + { ProviderType: IdentityProviderTypeType.OIDC, ProviderName: 'OIDC_2', ProviderDetails: { oidc_issuer: 'https://example.com' } }, + ], + }); + assert(result.loginOptions?.oidcLogin); + }); + void it('contains SAML login if SAML identityProvider type is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataContent: 'https://example.com' }, + }, + ], + }); + assert(result.loginOptions?.samlLogin); + }); + }); + void describe('OIDC and SAML providers', () => { + void describe('OIDC', () => { + void it('contains name and issuerUrl if OIDC identityProviderDetails is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_1', + ProviderDetails: { + oidc_issuer: 'https://example.com', + authorize_url: 'https://example.com', + token_url: 'https://example.com', + attributes_url: 'https://example.com', + jwks_uri: 'https://example.com', + }, + }, + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_2', + ProviderDetails: { oidc_issuer: 'https://example.com' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.oidcLogin, [ + { + endpoints: { + authorization: 'https://example.com', + token: 'https://example.com', + userInfo: 'https://example.com', + jwksUri: 'https://example.com', + }, + issuerUrl: 'https://example.com', + name: 'OIDC_1', + }, + { issuerUrl: 'https://example.com', name: 'OIDC_2' }, + ]); + }); + void it('contains attributeMapping if AttributeMapping is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_1', + ProviderDetails: { oidc_issuer: 'https://example.com' }, + AttributeMapping: { name: 'name', phone_number: 'phone_number' }, + }, + { + ProviderType: IdentityProviderTypeType.OIDC, + ProviderName: 'OIDC_2', + ProviderDetails: { oidc_issuer: 'https://example.com' }, + AttributeMapping: { name: 'name', phone_number: 'phone_number' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.oidcLogin, [ + { issuerUrl: 'https://example.com', name: 'OIDC_1', attributeMapping: { fullname: 'name', phoneNumber: 'phone_number' } }, + { issuerUrl: 'https://example.com', name: 'OIDC_2', attributeMapping: { fullname: 'name', phoneNumber: 'phone_number' } }, + ]); + }); + }); + void describe('SAML', () => { + void it('contains metadataType URL if SAML identityProviderDetails and metadataURL is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataURL: 'https://example.com' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.samlLogin, { + metadata: { metadataContent: 'https://example.com', metadataType: 'URL' }, + name: 'SAML_1', + }); + }); + void it('contains metadataType FILE if SAML identityProviderDetails and metadataContent is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataContent: 'https://example.com' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.samlLogin, { + metadata: { metadataContent: 'https://example.com', metadataType: 'FILE' }, + name: 'SAML_1', + }); + }); + void it('contains attributeMapping if AttributeMapping is passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityProvidersDetails: [ + { + ProviderType: IdentityProviderTypeType.SAML, + ProviderName: 'SAML_1', + ProviderDetails: { metadataContent: 'https://example.com' }, + AttributeMapping: { name: 'name', phone_number: 'phone_number' }, + }, + ], + }); + assert.deepEqual(result.loginOptions?.samlLogin, { + attributeMapping: { fullname: 'name', phoneNumber: 'phone_number' }, + metadata: { metadataContent: 'https://example.com', metadataType: 'FILE' }, + name: 'SAML_1', + }); + }); + }); + }); + void describe('callback URLs and logout URLs', () => { + void it('contains callback urls if callbackURLs array is passed', () => { + const result = getAuthDefinition({ userPool: {}, webClient: { CallbackURLs: ['https://example.com/callback'] } }); + assert.deepEqual(result.loginOptions?.callbackURLs, ['https://example.com/callback']); + }); + void it('contains logout urls if logoutURLs array is passed', () => { + const result = getAuthDefinition({ userPool: {}, webClient: { LogoutURLs: ['https://example.com/logout'] } }); + assert.deepEqual(result.loginOptions?.logoutURLs, ['https://example.com/logout']); + }); + }); + void describe('no login parameters are provided', () => { + void it('loginWith contains `email: true`', () => { + const result = getAuthDefinition({ userPool: {} }); + assert.deepEqual(result.loginOptions, { email: true }); + }); + }); + void describe('`Enable users to login with phone` is selected', () => { + // gen1 oddities: In order to match the gen1 template, + // we can't add phone to loginOptions as it changes the Schema & AutoVerifiedAttributes section. + // It just needs to be present as part of usernameAttributes + void it('loginWith does not contains `phone: true`', () => { + const result = getAuthDefinition({ userPool: { UsernameAttributes: ['phone_number'] } }); + expect(result.loginOptions).toBeDefined(); + expect(result.loginOptions?.phone).not.toBeDefined(); + assert.deepEqual(result.userPoolOverrides, { usernameAttributes: ['phone_number'] }); + }); + }); + void describe('Password policy', () => { + void describe('gen 2 defaults', () => { + const defaultPasswordPolicy: PasswordPolicyType = DEFAULT_PASSWORD_SETTINGS; + for (const key in defaultPasswordPolicy) { + const typedKey = key as keyof PasswordPolicyType; + const testValue = defaultPasswordPolicy[typedKey]; + void it(`does explicitly override the value for ${typedKey} when set to the default value of ${testValue}`, () => { + const result = getAuthDefinition({ + userPool: { + Policies: { + PasswordPolicy: { + [typedKey]: testValue, + }, + }, + }, + }); + assert(`Policies.PasswordPolicy.${typedKey}` in result.userPoolOverrides!); + }); + } + }); + void describe('overrides', () => { + type PasswordPolicyTestCase = { + [Property in keyof PasswordPolicyType]: Array; + }; + const passwordPolicyTestCases: PasswordPolicyTestCase = { + MinimumLength: [-Number.MAX_SAFE_INTEGER, 0, Number.MAX_SAFE_INTEGER], + RequireUppercase: [false], + RequireLowercase: [false], + RequireNumbers: [false], + RequireSymbols: [true, false], + TemporaryPasswordValidityDays: [-Number.MAX_SAFE_INTEGER, 0, Number.MAX_SAFE_INTEGER], + }; + + for (const key in passwordPolicyTestCases) { + const typedKey = key as keyof PasswordPolicyType; + for (const testValue of passwordPolicyTestCases[typedKey]!) { + void it(`sets the ${typedKey} to ${testValue}`, () => { + const result = getAuthDefinition({ + userPool: { + Policies: { + PasswordPolicy: { + [typedKey]: testValue, + }, + }, + }, + }); + assert.equal(result.userPoolOverrides?.[`Policies.PasswordPolicy.${typedKey}`], testValue); + }); + } + } + }); + }); + void describe('MFA settings', () => { + const modeMap: Record = { + ON: 'REQUIRED', + OFF: 'OFF', + OPTIONAL: 'OPTIONAL', + }; + + for (const mode of Object.keys(modeMap)) { + void describe(`when ${mode} is passed to mfa`, () => { + void it(`sets multifactor to ${mode}`, () => { + const result = getAuthDefinition({ + userPool: {}, + mfaConfig: mode as UserPoolMfaType, + totpConfig: { Enabled: true }, + }); + if (mode === 'OFF') { + assert.deepEqual(result.mfa, { + mode: modeMap[mode as UserPoolMfaType], + }); + } else { + assert.deepEqual(result.mfa, { + mode: modeMap[mode as UserPoolMfaType], + sms: true, + totp: true, + }); + } + }); + }); + } + void describe('no MFA setting parameter is provided', () => { + void it('sets mode to off', () => { + const result = getAuthDefinition({ userPool: {} }); + assert.deepEqual(result.mfa, { + mode: 'OFF', + }); + }); + }); + }); + void describe('Email verification settings', () => { + void it('it sets email verification with code message', () => { + const emailMessage = 'Your verification code is {####}'; + const result = getAuthDefinition({ + userPool: { + EmailVerificationMessage: emailMessage, + }, + }); + assert.equal(result.loginOptions?.emailOptions?.emailVerificationBody, emailMessage); + }); + void it('sets email verification with code subject', () => { + const emailSubject = 'Your verification code'; + const result = getAuthDefinition({ + userPool: { EmailVerificationSubject: emailSubject }, + }); + assert.equal(result.loginOptions?.emailOptions?.emailVerificationSubject, emailSubject); + }); + }); + void describe('Triggers', () => { + type triggerTestCase = [keyof LambdaConfigType, AuthTriggerEvents]; + const testCases: triggerTestCase[] = [ + ['PreSignUp', 'preSignUp'], + ['CustomMessage', 'customMessage'], + ['UserMigration', 'userMigration'], + ['PostConfirmation', 'postConfirmation'], + ['PreAuthentication', 'preAuthentication'], + ['PostAuthentication', 'postAuthentication'], + ['PreTokenGeneration', 'preTokenGeneration'], + ['DefineAuthChallenge', 'defineAuthChallenge'], + ['CreateAuthChallenge', 'createAuthChallenge'], + ['VerifyAuthChallengeResponse', 'verifyAuthChallengeResponse'], + ['PreTokenGenerationConfig', 'preTokenGeneration'], + ]; + for (const [lambdaConfigKey, authEventKey] of testCases) { + void it(`adapts user pool lambda config key ${lambdaConfigKey} to triggers ${authEventKey}`, () => { + const result = getAuthDefinition({ + userPool: { LambdaConfig: { [lambdaConfigKey]: {} } }, + }); + assert(result.lambdaTriggers); + if (lambdaConfigKey === 'PreTokenGenerationConfig') { + expect(result.lambdaTriggers[authEventKey]).toBeUndefined(); + } else { + assert.deepEqual(result.lambdaTriggers[authEventKey], { source: '' }); + } + }); + } + }); + + void describe('User attributes', () => { + void describe('Sign-up Standard User Attributes', () => { + const mappedUserAttributeName = { + address: 'address', + birthdate: 'birthdate', + email: 'email', + family_name: 'familyName', + gender: 'gender', + given_name: 'givenName', + locale: 'locale', + middle_name: 'middleName', + name: 'fullname', + nickname: 'nickname', + phone_number: 'phoneNumber', + picture: 'profilePicture', + preferred_username: 'preferredUsername', + profile: 'profilePage', + zoneinfo: 'timezone', + updated_at: 'lastUpdateTime', + website: 'website', + }; + for (const key in mappedUserAttributeName) { + const typedKey = key as keyof typeof mappedUserAttributeName; + const testValue = mappedUserAttributeName[typedKey]; + void it(`sets the attribute.Name ${typedKey} to ${testValue}`, () => { + const result = getAuthDefinition({ + userPool: { SchemaAttributes: [{ Name: typedKey, Required: true, Mutable: false }] }, + }); + assert.deepEqual(result.standardUserAttributes, { + [testValue as Attribute]: { required: true, mutable: false } as StandardAttribute, + } as StandardAttributes); + }); + } + void it('sets the standard attributes to empty object if no attributes are passed', () => { + const result = getAuthDefinition({ + userPool: {}, + }); + assert.deepEqual(result.standardUserAttributes, {}); + }); + }); + void describe('Custom User Attributes', () => { + void it('sets the custom attributes', () => { + const result = getAuthDefinition({ + userPool: { + SchemaAttributes: [ + { + Name: 'custom:Test1', + AttributeDataType: 'Number', + Mutable: true, + NumberAttributeConstraints: { MinValue: '10', MaxValue: '100' }, + }, + { + Name: 'custom:Test2', + AttributeDataType: 'String', + Mutable: true, + StringAttributeConstraints: { MinLength: '10', MaxLength: '100' }, + }, + ], + }, + }); + assert.deepEqual(result.customUserAttributes, { + 'custom:Test1': { dataType: 'Number', mutable: true, min: 10, max: 100 }, + 'custom:Test2': { dataType: 'String', mutable: true, minLen: 10, maxLen: 100 }, + }); + }); + void it('sets the custom attributes to empty object if no custom attributes are passed', () => { + const result = getAuthDefinition({ + userPool: {}, + }); + assert.deepEqual(result.customUserAttributes, {}); + }); + }); + }); + + void describe('User pool Groups', () => { + void it('sets the group names and sorts according to precedence', () => { + const result = getAuthDefinition({ + userPool: {}, + identityGroups: [ + { GroupName: 'group3', Precedence: 3 }, + { GroupName: 'group1', Precedence: 1 }, + { GroupName: 'group2', Precedence: 2 }, + ], + }); + assert.deepEqual(result.groups, ['group1', 'group2', 'group3']); + }); + void it('sets the group names to empty array if no groups are passed', () => { + const result = getAuthDefinition({ + userPool: {}, + identityGroups: [], + }); + assert.deepEqual(result.groups, []); + }); + }); + void describe('Oauth Scopes', () => { + void it('sets the oauth scopes', () => { + const result = getAuthDefinition({ + userPool: {}, + webClient: { AllowedOAuthScopes: ['email', 'openid', 'aws.cognito.signin.user.admin'] }, + }); + assert.deepEqual(result.loginOptions?.scopes, ['EMAIL', 'OPENID', 'COGNITO_ADMIN']); + }); + void it('Does not render anything if no oauth scopes are passed', () => { + const result = getAuthDefinition({ + userPool: {}, + webClient: {}, + }); + assert(result.loginOptions?.scopes === undefined); + }); + }); + void describe('Unauthenticated Login', () => { + void it('sets the guestLogin to true', () => { + const result = getAuthDefinition({ + userPool: {}, + guestLogin: true, + }); + assert(result.guestLogin); + }); + void it('sets the guestLogin to false', () => { + const result = getAuthDefinition({ + userPool: {}, + guestLogin: false, + }); + assert(!result.guestLogin); + }); + }); + void describe('UserPool Name', () => { + void it('sets the userPool name', () => { + const result = getAuthDefinition({ + userPool: { Name: 'test' }, + }); + assert.deepEqual(result.userPoolOverrides, { userPoolName: 'test', usernameAttributes: undefined }); + }); + }); +}); diff --git a/packages/amplify-gen1-codegen-auth-adapter/src/auth_render_adapter.ts b/packages/amplify-gen1-codegen-auth-adapter/src/auth_render_adapter.ts new file mode 100644 index 00000000000..e7018173007 --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/src/auth_render_adapter.ts @@ -0,0 +1,377 @@ +import { + Lambda, + AuthDefinition, + EmailOptions, + PasswordPolicyPath, + AuthTriggerEvents, + MultifactorOptions, + StandardAttributes, + StandardAttribute, + CustomAttribute, + CustomAttributes, + Attribute, + PolicyOverrides, + SamlOptions, + OidcOptions, + LoginOptions, + Scope, +} from '@aws-amplify/amplify-gen2-codegen'; +import { AttributeMappingRule, ReferenceAuth } from '@aws-amplify/amplify-gen2-codegen/src/auth/source_builder'; +import { + LambdaConfigType, + IdentityProviderTypeType, + PasswordPolicyType, + ProviderDescription, + UserPoolMfaType, + UserPoolType, + UserPoolClientType, + SchemaAttributeType, + GroupType, + IdentityProviderType, + SoftwareTokenMfaConfigType, +} from '@aws-sdk/client-cognito-identity-provider'; + +export interface AuthTriggerConnection { + triggerType: keyof LambdaConfigType; + lambdaFunctionName: string; +} + +export type AuthTriggerConnectionSourceMap = Partial>; + +export interface AuthSynthesizerOptions { + userPool: UserPoolType; + identityPoolName?: string; + identityProviders?: ProviderDescription[]; + identityProvidersDetails?: IdentityProviderType[]; + identityGroups?: GroupType[]; + webClient?: UserPoolClientType; + authTriggerConnections?: AuthTriggerConnectionSourceMap; + referenceAuth?: ReferenceAuth; + guestLogin?: boolean; + mfaConfig?: UserPoolMfaType; + totpConfig?: SoftwareTokenMfaConfigType; + userPoolClient?: UserPoolClientType; +} + +export const DEFAULT_PASSWORD_SETTINGS: PasswordPolicyType = { + MinimumLength: 8, + RequireLowercase: true, + RequireUppercase: true, + RequireNumbers: true, + TemporaryPasswordValidityDays: 3, +}; + +const COGNITO_TRIGGERS_TO_SKIP = ['PreTokenGenerationConfig']; + +const getPasswordPolicyOverrides = (passwordPolicy: Partial): Partial => { + const policyOverrides: Partial = {}; + const passwordOverridePath = (policyKey: keyof PasswordPolicyType): PasswordPolicyPath => `Policies.PasswordPolicy.${policyKey}`; + for (const key of Object.keys(passwordPolicy)) { + const typedKey: keyof PasswordPolicyType = key as keyof PasswordPolicyType; + if (passwordPolicy[typedKey] !== undefined) { + policyOverrides[passwordOverridePath(typedKey)] = passwordPolicy[typedKey]; + } + } + return policyOverrides; +}; + +const getUserPoolOverrides = (userPool: UserPoolType): Partial => { + const userPoolOverrides: Partial = {}; + Object.assign(userPoolOverrides, getPasswordPolicyOverrides(userPool.Policies?.PasswordPolicy ?? {})); + if (userPool.Name) { + const userNamePolicy: Partial = { + userPoolName: userPool.Name, + }; + Object.assign(userPoolOverrides, userNamePolicy); + } + if (userPool.UsernameAttributes === undefined || userPool.UsernameAttributes.length === 0) { + userPoolOverrides.usernameAttributes = undefined; + } else { + userPoolOverrides.usernameAttributes = userPool.UsernameAttributes; + } + return userPoolOverrides; +}; + +const getMfaConfiguration = (mfaConfig?: UserPoolMfaType, totpConfig?: SoftwareTokenMfaConfigType): MultifactorOptions => { + const multifactor: MultifactorOptions = { + mode: 'OFF', + }; + if (mfaConfig === 'ON') { + multifactor.mode = 'REQUIRED'; + multifactor.sms = true; + totpConfig?.Enabled ? (multifactor.totp = true) : (multifactor.totp = false); + } else if (mfaConfig === 'OPTIONAL') { + multifactor.mode = 'OPTIONAL'; + multifactor.sms = true; + totpConfig?.Enabled ? (multifactor.totp = true) : (multifactor.totp = false); + } + return multifactor; +}; + +const getEmailConfig = (userPool: UserPoolType): EmailOptions => { + return { + emailVerificationBody: userPool.EmailVerificationMessage ?? '', + emailVerificationSubject: userPool.EmailVerificationSubject ?? '', + }; +}; + +const getStandardUserAttributes = ( + signupAttributes: SchemaAttributeType[] | undefined, + mappedUserAttributeName: Record, +): StandardAttributes => { + return ( + signupAttributes?.reduce((standardAttributes: StandardAttributes, attribute: SchemaAttributeType) => { + const standardAttribute: StandardAttribute = { + required: attribute.Required, + mutable: attribute.Mutable, + }; + if (attribute.Name !== undefined && attribute.Name in mappedUserAttributeName && attribute.Required) { + return { + ...standardAttributes, + [mappedUserAttributeName[attribute.Name as keyof typeof mappedUserAttributeName] as Attribute]: standardAttribute, + }; + } + return standardAttributes; + }, {} as StandardAttributes) || {} + ); +}; + +const getCustomUserAttributes = (signupAttributes: SchemaAttributeType[] | undefined): CustomAttributes => { + return ( + signupAttributes?.reduce((customAttributes: CustomAttributes, attribute: SchemaAttributeType) => { + if (attribute.Name !== undefined && attribute.Name.startsWith('custom:')) { + const customAttribute: CustomAttribute = { + mutable: attribute.Mutable, + dataType: attribute.AttributeDataType, + }; + + if (attribute.NumberAttributeConstraints && Object.keys(attribute.NumberAttributeConstraints).length > 0) { + customAttribute.min = Number(attribute.NumberAttributeConstraints.MinValue); + customAttribute.max = Number(attribute.NumberAttributeConstraints.MaxValue); + } else if (attribute.StringAttributeConstraints && Object.keys(attribute.StringAttributeConstraints).length > 0) { + customAttribute.minLen = Number(attribute.StringAttributeConstraints.MinLength); + customAttribute.maxLen = Number(attribute.StringAttributeConstraints.MaxLength); + } + return { + ...customAttributes, + [attribute.Name]: customAttribute, + }; + } + return customAttributes; + }, {} as CustomAttributes) || {} + ); +}; + +const getGroups = (identityGroups?: GroupType[]): string[] => { + if (!identityGroups || identityGroups.length === 0) { + return []; + } + const groupsWithPrecedence = identityGroups.filter((group) => group.Precedence !== undefined); + + return groupsWithPrecedence + .sort((a, b) => (a.Precedence || 0) - (b.Precedence || 0)) + .map((group) => group.GroupName) + .filter((groupName): groupName is string => groupName !== undefined); +}; + +const getScopes = (scopes: string[]): Scope[] => { + const mappedScopes: Record = { + email: 'EMAIL', + openid: 'OPENID', + phone: 'PHONE', + profile: 'PROFILE', + 'aws.cognito.signin.user.admin': 'COGNITO_ADMIN', + }; + return scopes.map((scope) => mappedScopes[scope] as Scope); +}; + +/** + * [getAuthDefinition] describes gen 1 auth resources in terms that can be used to generate Gen 2 code. + */ +const mappedLambdaConfigKey = (key: keyof LambdaConfigType): AuthTriggerEvents => { + switch (key) { + case 'PreSignUp': + return 'preSignUp'; + case 'CustomMessage': + return 'customMessage'; + case 'UserMigration': + return 'userMigration'; + case 'PostConfirmation': + return 'postConfirmation'; + case 'PreAuthentication': + return 'preAuthentication'; + case 'PostAuthentication': + return 'postAuthentication'; + case 'PreTokenGeneration': + return 'preTokenGeneration'; + case 'DefineAuthChallenge': + return 'defineAuthChallenge'; + case 'CreateAuthChallenge': + return 'createAuthChallenge'; + case 'VerifyAuthChallengeResponse': + return 'verifyAuthChallengeResponse'; + default: + throw new Error(`Could not map the provided key: ${key}`); + } +}; + +const getAuthTriggers = ( + lambdaConfig: LambdaConfigType, + triggerSourceFiles: AuthTriggerConnectionSourceMap, +): Partial> => { + return ( + Object.keys(lambdaConfig) + // There is PreTokenGenerationConfig that is duplicated, but is of a different format. Cognito introduced this at a later stage. + // We look for PreTokenGeneration which is maintained for legacy reasons and is always populated. + .filter((triggerName) => !COGNITO_TRIGGERS_TO_SKIP.includes(triggerName)) + .reduce((prev, key) => { + const typedKey = key as keyof LambdaConfigType; + prev[mappedLambdaConfigKey(typedKey)] = { source: triggerSourceFiles[typedKey] ?? '' }; + return prev; + }, {} as Partial>) + ); +}; + +function filterAttributeMapping( + attributeMapping: Record, + mappedUserAttributeName: Record, +): Record { + return Object.fromEntries( + Object.entries(attributeMapping) + .filter(([key]) => Object.keys(mappedUserAttributeName).includes(key)) + .map(([key, value]) => [mappedUserAttributeName[key as keyof typeof mappedUserAttributeName], value]), + ); +} + +/** + * [getAuthDefinition] describes gen 1 auth resources in terms that can be used to generate Gen 2 code. + */ +export const getAuthDefinition = ({ + userPool, + identityPoolName, + identityProviders, + identityProvidersDetails, + identityGroups, + webClient, + authTriggerConnections, + guestLogin, + referenceAuth, + mfaConfig, + totpConfig, + userPoolClient, +}: AuthSynthesizerOptions): AuthDefinition => { + const mappedUserAttributeName = { + address: 'address', + birthdate: 'birthdate', + email: 'email', + family_name: 'familyName', + gender: 'gender', + given_name: 'givenName', + locale: 'locale', + middle_name: 'middleName', + name: 'fullname', + nickname: 'nickname', + phone_number: 'phoneNumber', + picture: 'profilePicture', + preferred_username: 'preferredUsername', + profile: 'profilePage', + zoneinfo: 'timezone', + updated_at: 'lastUpdateTime', + website: 'website', + }; + + const loginWith: LoginOptions = { email: true }; + const mapIdentityProvider = { + [IdentityProviderTypeType.Google]: ['googleLogin', 'googleAttributes'], + [IdentityProviderTypeType.SignInWithApple]: ['appleLogin', 'appleAttributes'], + [IdentityProviderTypeType.LoginWithAmazon]: ['amazonLogin', 'amazonAttributes'], + [IdentityProviderTypeType.Facebook]: ['facebookLogin', 'facebookAttributes'], + }; + + if (identityProviders !== undefined) { + identityProviders.forEach((provider) => { + const loginWithProperty = mapIdentityProvider[provider?.ProviderType as keyof typeof mapIdentityProvider]; + if (loginWithProperty !== undefined) { + const loginProperty = loginWithProperty[0]; + (loginWith[loginProperty as keyof LoginOptions] as boolean) = true; + } + }); + } + + if (identityProvidersDetails) { + const oidcOptions: OidcOptions[] = []; + let samlOptions: SamlOptions | undefined; + + for (const provider of identityProvidersDetails) { + const { ProviderType, ProviderName, ProviderDetails, AttributeMapping } = provider; + + if (ProviderType === IdentityProviderTypeType.OIDC && ProviderDetails) { + const { oidc_issuer, authorize_url, token_url, attributes_url, jwks_uri } = ProviderDetails; + const oidcOption: OidcOptions = { + issuerUrl: oidc_issuer, + }; + if (ProviderName) oidcOption.name = ProviderName; + if (authorize_url && token_url && attributes_url && jwks_uri) { + oidcOption.endpoints = { + authorization: authorize_url, + token: token_url, + userInfo: attributes_url, + jwksUri: jwks_uri, + }; + } + if (AttributeMapping) + oidcOption.attributeMapping = filterAttributeMapping(AttributeMapping, mappedUserAttributeName) as AttributeMappingRule; + oidcOptions.push(oidcOption); + } else if (ProviderType === IdentityProviderTypeType.SAML && ProviderDetails) { + const { metadataURL, metadataContent } = ProviderDetails; + samlOptions = { + metadata: { + metadataContent: metadataURL || metadataContent, + metadataType: metadataURL ? 'URL' : 'FILE', + }, + }; + if (ProviderName) samlOptions.name = ProviderName; + if (AttributeMapping) + samlOptions.attributeMapping = filterAttributeMapping(AttributeMapping, mappedUserAttributeName) as AttributeMappingRule; + } else { + if (AttributeMapping) { + const attributeOption = mapIdentityProvider[provider?.ProviderType as keyof typeof mapIdentityProvider][1]; + loginWith[attributeOption] = filterAttributeMapping(AttributeMapping, mappedUserAttributeName); + } + } + } + loginWith.oidcLogin = oidcOptions; + loginWith.samlLogin = samlOptions; + } + + if (userPool.EmailVerificationMessage || userPool.EmailVerificationSubject) { + loginWith.emailOptions = getEmailConfig(userPool); + } + if (webClient?.CallbackURLs) { + loginWith.callbackURLs = webClient?.CallbackURLs; + } + if (webClient?.LogoutURLs) { + loginWith.logoutURLs = webClient?.LogoutURLs; + } + if (webClient?.AllowedOAuthScopes) { + loginWith.scopes = getScopes(webClient?.AllowedOAuthScopes); + } + + const userPoolOverrides = getUserPoolOverrides(userPool); + return { + loginOptions: loginWith, + mfa: getMfaConfiguration(mfaConfig, totpConfig), + standardUserAttributes: getStandardUserAttributes(userPool.SchemaAttributes, mappedUserAttributeName), + customUserAttributes: getCustomUserAttributes(userPool.SchemaAttributes), + groups: getGroups(identityGroups), + userPoolOverrides, + lambdaTriggers: getAuthTriggers(userPool.LambdaConfig ?? {}, authTriggerConnections ?? {}), + guestLogin, + identityPoolName, + oAuthFlows: webClient?.AllowedOAuthFlows, + readAttributes: webClient?.ReadAttributes, + writeAttributes: webClient?.WriteAttributes, + referenceAuth, + userPoolClient, + }; +}; diff --git a/packages/amplify-gen1-codegen-auth-adapter/src/index.ts b/packages/amplify-gen1-codegen-auth-adapter/src/index.ts new file mode 100644 index 00000000000..1867bffe87a --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/src/index.ts @@ -0,0 +1 @@ +export { AuthSynthesizerOptions, getAuthDefinition, AuthTriggerConnection, AuthTriggerConnectionSourceMap } from './auth_render_adapter.js'; diff --git a/packages/amplify-gen1-codegen-auth-adapter/tsconfig.json b/packages/amplify-gen1-codegen-auth-adapter/tsconfig.json new file mode 100644 index 00000000000..5dea616bde6 --- /dev/null +++ b/packages/amplify-gen1-codegen-auth-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "extends": "../../tsconfig.base.json", + "references": [ + { + "path": "../amplify-gen2-codegen" + } + ] +} diff --git a/packages/amplify-gen1-codegen-data-adapter/.gitignore b/packages/amplify-gen1-codegen-data-adapter/.gitignore new file mode 100644 index 00000000000..c3af857904e --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/amplify-gen1-codegen-data-adapter/API.md b/packages/amplify-gen1-codegen-data-adapter/API.md new file mode 100644 index 00000000000..aac4d5aca9a --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/API.md @@ -0,0 +1,15 @@ +## API Report File for "@aws-amplify/amplify-gen1-codegen-data-adapter" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { DataTableMapping } from '@aws-amplify/amplify-gen2-codegen'; +import { Stack } from '@aws-sdk/client-cloudformation'; + +// @public (undocumented) +export const getDataDefinition: (dataStack: Stack) => DataTableMapping; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/amplify-gen1-codegen-data-adapter/CHANGELOG.md b/packages/amplify-gen1-codegen-data-adapter/CHANGELOG.md new file mode 100644 index 00000000000..77f549f4325 --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/CHANGELOG.md @@ -0,0 +1,107 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-9.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-7.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-9.0) (2025-04-22) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-6.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-7.0) (2025-04-19) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-5.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-6.0) (2025-03-21) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-4.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-5.0) (2025-03-19) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +# [0.1.0-next-4.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next-4.0) (2025-03-12) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-beta-latest.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-next.0) (2025-02-14) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-alpha.1...@aws-amplify/amplify-gen1-codegen-data-adapter@0.1.0-beta-latest.0) (2025-02-12) + + +### Bug Fixes + +* gen2 data table mapping, userpoolclient provider casing ([ab5a244](https://github.com/aws-amplify/amplify-cli/commit/ab5a244da56022a67fa275f10e3f4a2fe53a0a78)) + + +### Features + +* include all envs in gen 2 data codegen ([#14087](https://github.com/aws-amplify/amplify-cli/issues/14087)) ([6a437e3](https://github.com/aws-amplify/amplify-cli/commit/6a437e3345489ce22d78621de18acc46f969d883)) + + + + + +## [0.0.2-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-alpha.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-alpha.1) (2024-12-05) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +## [0.0.2-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-gen2-migrations-alpha.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-alpha.0) (2024-11-21) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +## [0.0.2-gen2-migrations-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-gen2-migration-test-alpha.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-gen2-migrations-alpha.0) (2024-10-10) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +## [0.0.2-gen2-migration-test-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-gen2-migrations-test.0...@aws-amplify/amplify-gen1-codegen-data-adapter@0.0.2-gen2-migration-test-alpha.0) (2024-09-26) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-data-adapter + + + + + +## 0.0.2-gen2-migrations-test.0 (2024-09-23) + + +### Bug Fixes + +* make gen2 migration packages public ([a7832cb](https://github.com/aws-amplify/amplify-cli/commit/a7832cb622cabf3eec3f770393477256117ea47d)) diff --git a/packages/amplify-gen1-codegen-data-adapter/package.json b/packages/amplify-gen1-codegen-data-adapter/package.json new file mode 100644 index 00000000000..89a3fe5eb0a --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/package.json @@ -0,0 +1,54 @@ +{ + "name": "@aws-amplify/amplify-gen1-codegen-data-adapter", + "version": "0.1.0-next-9.0", + "type": "commonjs", + "main": "lib/index.js", + "devDependencies": { + "jest": "^29.5.0", + "typescript": "^5.4.5" + }, + "dependencies": { + "@aws-amplify/amplify-gen2-codegen": "0.1.0-next-9.0", + "@aws-amplify/auth-construct": "^1.1.5", + "@aws-sdk/client-amplify": "^3.592.0", + "@aws-sdk/client-cloudformation": "^3.592.0", + "@aws-sdk/client-cognito-identity": "^3.592.0", + "@aws-sdk/client-cognito-identity-provider": "^3.592.0" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "pretest": "mkdir -p coverage", + "test": "jest --logHeapUsage", + "build": "tsc", + "watch": "tsc -w", + "extract-api": "ts-node ../../scripts/extract-api.ts" + }, + "author": "", + "license": "Apache-2.0", + "description": "" +} diff --git a/packages/amplify-gen1-codegen-data-adapter/src/get_data_definition.test.ts b/packages/amplify-gen1-codegen-data-adapter/src/get_data_definition.test.ts new file mode 100644 index 00000000000..919bb7fe352 --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/src/get_data_definition.test.ts @@ -0,0 +1,18 @@ +import assert from 'node:assert'; +import { Stack } from '@aws-sdk/client-cloudformation'; +import { getDataDefinition, tableMappingKey } from './get_data_definition'; + +describe('Data definition', () => { + it('parses the table mapping', () => { + const stack: Stack = { + Outputs: [ + { + OutputKey: tableMappingKey, + OutputValue: '{"hello":"world"}', + }, + ], + } as Stack; + const result = getDataDefinition(stack); + assert.equal(result.hello, 'world'); + }); +}); diff --git a/packages/amplify-gen1-codegen-data-adapter/src/get_data_definition.ts b/packages/amplify-gen1-codegen-data-adapter/src/get_data_definition.ts new file mode 100644 index 00000000000..18f5d2cddf1 --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/src/get_data_definition.ts @@ -0,0 +1,12 @@ +import assert from 'node:assert'; +import { DataTableMapping } from '@aws-amplify/amplify-gen2-codegen'; +import { Stack } from '@aws-sdk/client-cloudformation'; + +export const tableMappingKey = 'DataSourceMappingOutput'; + +export const getDataDefinition = (dataStack: Stack): DataTableMapping => { + const rawTableMapping = dataStack.Outputs?.find((o) => o.OutputKey === tableMappingKey)?.OutputValue; + assert(rawTableMapping); + const tableMapping = JSON.parse(rawTableMapping); + return tableMapping; +}; diff --git a/packages/amplify-gen1-codegen-data-adapter/src/index.ts b/packages/amplify-gen1-codegen-data-adapter/src/index.ts new file mode 100644 index 00000000000..ce7af8e3f75 --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/src/index.ts @@ -0,0 +1 @@ +export { getDataDefinition } from './get_data_definition'; diff --git a/packages/amplify-gen1-codegen-data-adapter/tsconfig.json b/packages/amplify-gen1-codegen-data-adapter/tsconfig.json new file mode 100644 index 00000000000..5dea616bde6 --- /dev/null +++ b/packages/amplify-gen1-codegen-data-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "extends": "../../tsconfig.base.json", + "references": [ + { + "path": "../amplify-gen2-codegen" + } + ] +} diff --git a/packages/amplify-gen1-codegen-function-adapter/.gitignore b/packages/amplify-gen1-codegen-function-adapter/.gitignore new file mode 100644 index 00000000000..c3af857904e --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/amplify-gen1-codegen-function-adapter/API.md b/packages/amplify-gen1-codegen-function-adapter/API.md new file mode 100644 index 00000000000..2f7ae334e74 --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/API.md @@ -0,0 +1,29 @@ +## API Report File for "@aws-amplify/amplify-gen1-codegen-function-adapter" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { FunctionConfiguration } from '@aws-sdk/client-lambda'; +import { FunctionDefinition } from '@aws-amplify/amplify-gen2-codegen'; + +// @public (undocumented) +export type AmplifyMetaFunction = { + service: string; + providerPlugin: 'awscloudformation'; + output: Record; +}; + +// @public (undocumented) +export type AmplifyMetaWithFunction = { + function: Record; +}; + +// Warning: (ae-forgotten-export) The symbol "FunctionSchedule" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export const getFunctionDefinition: (functionConfigurations: FunctionConfiguration[], functionSchedules: FunctionSchedule[], functionCategoryMap: Map, meta: AmplifyMetaWithFunction) => FunctionDefinition[]; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/amplify-gen1-codegen-function-adapter/CHANGELOG.md b/packages/amplify-gen1-codegen-function-adapter/CHANGELOG.md new file mode 100644 index 00000000000..a5930afad31 --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/CHANGELOG.md @@ -0,0 +1,119 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-9.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-7.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-9.0) (2025-04-22) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-6.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-7.0) (2025-04-19) + + +### Bug Fixes + +* codegen custom resources and added function scheduling ([1e25182](https://github.com/aws-amplify/amplify-cli/commit/1e251820905291bc8c6ca058ce5397b7ddec5e5b)) + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-5.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-6.0) (2025-03-21) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-4.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-5.0) (2025-03-19) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-next-4.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-4.0) (2025-03-12) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-beta-latest.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next.0) (2025-02-14) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-alpha.1...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-beta-latest.0) (2025-02-12) + + +### Bug Fixes + +* dedupe lock, update api md ([7520e27](https://github.com/aws-amplify/amplify-cli/commit/7520e2760cc2fa0934f3c095f37aedc01b689161)) +* function migration adapter category ([afb1136](https://github.com/aws-amplify/amplify-cli/commit/afb1136d5c1eb82e0aa7baf6c12784b06a72de17)) +* gen1 function adapter test ([1038d85](https://github.com/aws-amplify/amplify-cli/commit/1038d8553ad3c670261070460dd4e740cb392b6e)) +* lint errros and warnings in amplify-migration ([8464c01](https://github.com/aws-amplify/amplify-cli/commit/8464c019b70cadbb786b281b9f0b02ca057c402e)) + + + + + +# [0.1.0-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-alpha.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-alpha.1) (2024-12-05) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-gen2-migrations-alpha.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-alpha.0) (2024-11-21) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-gen2-migrations-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-gen2-migration-test-alpha.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-gen2-migrations-alpha.0) (2024-10-10) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# [0.1.0-gen2-migration-test-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-gen2-migrations-test.0...@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-gen2-migration-test-alpha.0) (2024-09-26) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-function-adapter + + + + + +# 0.1.0-gen2-migrations-test.0 (2024-09-23) + + +### Bug Fixes + +* make gen2 migration packages public ([a7832cb](https://github.com/aws-amplify/amplify-cli/commit/a7832cb622cabf3eec3f770393477256117ea47d)) + + +### Features + +* added functions codegen ([b88f2e2](https://github.com/aws-amplify/amplify-cli/commit/b88f2e2733940af6910af132b82af28912b26dcc)) +* added functions codegen ([5120ab4](https://github.com/aws-amplify/amplify-cli/commit/5120ab4d5bcb30793f9ab4b42aec5a40a1ba2974)) +* fixed lint and ran yarn extract-api ([b4f256c](https://github.com/aws-amplify/amplify-cli/commit/b4f256c3b433a38974f7a8612505d1c7c21befeb)) +* functions codegeb ([ba3babf](https://github.com/aws-amplify/amplify-cli/commit/ba3babfb1403e8f740e1cfbf795707cdd085612f)) +* updated API.md for amplify-gen1-codegen-function-adapter ([86c0e5e](https://github.com/aws-amplify/amplify-cli/commit/86c0e5efd6e61564089d546a86ed0b0fe7d653d1)) +* updated functions codegen ([bc1acfa](https://github.com/aws-amplify/amplify-cli/commit/bc1acfa9ee8d78e31c3dcb0ec25d0672b0dab1c4)) +* updated functions codegen ([4ac9324](https://github.com/aws-amplify/amplify-cli/commit/4ac932478633274e87524aea9eb9f48d3640d36c)) diff --git a/packages/amplify-gen1-codegen-function-adapter/package.json b/packages/amplify-gen1-codegen-function-adapter/package.json new file mode 100644 index 00000000000..0010c5657f6 --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/package.json @@ -0,0 +1,50 @@ +{ + "name": "@aws-amplify/amplify-gen1-codegen-function-adapter", + "version": "0.1.0-next-9.0", + "type": "commonjs", + "main": "lib/index.js", + "devDependencies": { + "jest": "^29.5.0", + "typescript": "^5.4.5" + }, + "dependencies": { + "@aws-amplify/amplify-gen2-codegen": "0.1.0-next-9.0", + "@aws-sdk/client-lambda": "^3.637.0" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "pretest": "mkdir -p coverage", + "test": "jest --logHeapUsage", + "build": "tsc", + "watch": "tsc -w", + "extract-api": "ts-node ../../scripts/extract-api.ts" + }, + "author": "", + "license": "Apache-2.0", + "description": "" +} diff --git a/packages/amplify-gen1-codegen-function-adapter/src/function_render_adapter.test.ts b/packages/amplify-gen1-codegen-function-adapter/src/function_render_adapter.test.ts new file mode 100644 index 00000000000..3c4a02c6048 --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/src/function_render_adapter.test.ts @@ -0,0 +1,49 @@ +import assert from 'node:assert'; +import { getFunctionDefinition } from './function_render_adapter'; +import { FunctionConfiguration } from '@aws-sdk/client-lambda'; + +void describe('function codegen', () => { + void describe('Function definition', () => { + void it('sets the correct function configuration', () => { + const configurations: FunctionConfiguration[] = []; + const functionConf1: FunctionConfiguration = {}; + functionConf1.FunctionName = 'function1'; + functionConf1.Runtime = 'nodejs18.x'; + functionConf1.Handler = 'index.handler'; + functionConf1.Timeout = 3; + functionConf1.MemorySize = 128; + functionConf1.Environment = { Variables: { ENV: 'dev', REGION: 'us-west-2' } }; + configurations.push(functionConf1); + + const functionSchedules = [{ functionName: 'function1', scheduleExpression: 'rate(5 minutes)' }]; + + const result = getFunctionDefinition(configurations, functionSchedules, new Map([['function1', 'function']]), { + function: { + function1: { + providerPlugin: 'awscloudformation', + service: 'Lambda', + output: { + Name: 'function1', + }, + }, + function2: { + providerPlugin: 'awscloudformation', + service: 'Lambda', + output: { + Name: 'function2', + }, + }, + }, + }); + + for (const func of result) { + assert.equal(func.runtime, 'nodejs18.x'); + assert.equal(func.timeoutSeconds, 3); + assert.equal(func.memoryMB, 128); + assert.deepEqual(func.environment, { Variables: { ENV: 'dev', REGION: 'us-west-2' } }); + assert.equal(func.entry, 'index.handler'); + assert.equal(func.schedule, 'rate(5 minutes)'); + } + }); + }); +}); diff --git a/packages/amplify-gen1-codegen-function-adapter/src/function_render_adapter.ts b/packages/amplify-gen1-codegen-function-adapter/src/function_render_adapter.ts new file mode 100644 index 00000000000..71681b036a2 --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/src/function_render_adapter.ts @@ -0,0 +1,48 @@ +import { FunctionDefinition } from '@aws-amplify/amplify-gen2-codegen'; +import { FunctionConfiguration } from '@aws-sdk/client-lambda'; +import assert from 'node:assert'; + +export type AmplifyMetaFunction = { + service: string; + providerPlugin: 'awscloudformation'; + output: Record; +}; + +type FunctionSchedule = { + functionName: string; + scheduleExpression: string | undefined; +}; + +export type AmplifyMetaWithFunction = { + function: Record; +}; + +export const getFunctionDefinition = ( + functionConfigurations: FunctionConfiguration[], + functionSchedules: FunctionSchedule[], + functionCategoryMap: Map, + meta: AmplifyMetaWithFunction, +): FunctionDefinition[] => { + const funcDefList: FunctionDefinition[] = []; + + for (const configuration of functionConfigurations) { + const funcDef: FunctionDefinition = {}; + funcDef.entry = configuration?.Handler; + funcDef.name = configuration?.FunctionName; + funcDef.timeoutSeconds = configuration?.Timeout; + funcDef.memoryMB = configuration?.MemorySize; + funcDef.environment = configuration?.Environment; + funcDef.runtime = configuration?.Runtime; + const functionName = configuration?.FunctionName; + assert(functionName); + const functionRecordInMeta = Object.entries(meta.function).find(([, value]) => value.output.Name === functionName); + assert(functionRecordInMeta); + funcDef.category = functionCategoryMap.get(functionRecordInMeta[0]) ?? 'function'; + funcDef.resourceName = functionRecordInMeta[0]; + funcDef.schedule = functionSchedules.find((schedule) => schedule.functionName === functionName)?.scheduleExpression; + + funcDefList.push(funcDef); + } + + return funcDefList; +}; diff --git a/packages/amplify-gen1-codegen-function-adapter/src/index.ts b/packages/amplify-gen1-codegen-function-adapter/src/index.ts new file mode 100644 index 00000000000..ece71e3e79b --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/src/index.ts @@ -0,0 +1 @@ +export { getFunctionDefinition, AmplifyMetaWithFunction, AmplifyMetaFunction } from './function_render_adapter.js'; diff --git a/packages/amplify-gen1-codegen-function-adapter/tsconfig.json b/packages/amplify-gen1-codegen-function-adapter/tsconfig.json new file mode 100644 index 00000000000..5dea616bde6 --- /dev/null +++ b/packages/amplify-gen1-codegen-function-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "extends": "../../tsconfig.base.json", + "references": [ + { + "path": "../amplify-gen2-codegen" + } + ] +} diff --git a/packages/amplify-gen1-codegen-storage-adapter/.gitignore b/packages/amplify-gen1-codegen-storage-adapter/.gitignore new file mode 100644 index 00000000000..c3af857904e --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/amplify-gen1-codegen-storage-adapter/API.md b/packages/amplify-gen1-codegen-storage-adapter/API.md new file mode 100644 index 00000000000..870021189c7 --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/API.md @@ -0,0 +1,38 @@ +## API Report File for "@aws-amplify/amplify-gen1-codegen-storage-adapter" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Lambda } from '@aws-amplify/amplify-gen2-codegen'; +import { StorageRenderParameters } from '@aws-amplify/amplify-gen2-codegen'; +import { StorageTriggerEvent } from '@aws-amplify/amplify-gen2-codegen'; + +// @public (undocumented) +export type CLIV1Permission = 'READ' | 'CREATE_AND_UPDATE' | 'DELETE'; + +// @public (undocumented) +export const getStorageDefinition: ({ bucketName, cliInputs, triggers }: StorageInputs) => StorageRenderParameters; + +// @public (undocumented) +export type StorageCLIInputsJSON = { + resourceName?: string; + policyUUID?: string; + bucketName?: string; + storageAccess?: string; + guestAccess: CLIV1Permission[]; + authAccess: CLIV1Permission[]; + triggerFunction?: string; + groupAccess?: Record; +}; + +// @public (undocumented) +export type StorageInputs = { + bucketName: string; + cliInputs: StorageCLIInputsJSON; + triggers?: Partial>; +}; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/amplify-gen1-codegen-storage-adapter/CHANGELOG.md b/packages/amplify-gen1-codegen-storage-adapter/CHANGELOG.md new file mode 100644 index 00000000000..48f2657edfc --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/CHANGELOG.md @@ -0,0 +1,109 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-9.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-7.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-9.0) (2025-04-22) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-6.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-7.0) (2025-04-19) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-5.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-6.0) (2025-03-21) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-4.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-5.0) (2025-03-19) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-next-4.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-4.0) (2025-03-12) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-beta-latest.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next.0) (2025-02-14) + + +### Bug Fixes + +* added dynamic reference to the env name in backend.ts and fixed a few bugs ([59e6a01](https://github.com/aws-amplify/amplify-cli/commit/59e6a014a6aadc17c170e57e6278242bed054697)) + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-alpha.1...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-beta-latest.0) (2025-02-12) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-alpha.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-alpha.1) (2024-12-05) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-gen2-migrations-alpha.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-alpha.0) (2024-11-21) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-gen2-migrations-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-gen2-migration-test-alpha.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-gen2-migrations-alpha.0) (2024-10-10) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# [0.1.0-gen2-migration-test-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-gen2-migrations-test.0...@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-gen2-migration-test-alpha.0) (2024-09-26) + +**Note:** Version bump only for package @aws-amplify/amplify-gen1-codegen-storage-adapter + + + + + +# 0.1.0-gen2-migrations-test.0 (2024-09-23) + + +### Bug Fixes + +* make gen2 migration packages public ([a7832cb](https://github.com/aws-amplify/amplify-cli/commit/a7832cb622cabf3eec3f770393477256117ea47d)) +* resolve workflow errors ([aad8b48](https://github.com/aws-amplify/amplify-cli/commit/aad8b486809a49b38c39570047418aa4c808bf70)) + + +### Features + +* **cli:** initial migration merge ([#13856](https://github.com/aws-amplify/amplify-cli/issues/13856)) ([ebe5cd0](https://github.com/aws-amplify/amplify-cli/commit/ebe5cd046cfb18c38ffdce17610ed3a133cc9d44)) +* storage codegen ([9e45af9](https://github.com/aws-amplify/amplify-cli/commit/9e45af9c881572ce67d5bad7e05e057609c80b00)) diff --git a/packages/amplify-gen1-codegen-storage-adapter/package.json b/packages/amplify-gen1-codegen-storage-adapter/package.json new file mode 100644 index 00000000000..a46f9fae270 --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/package.json @@ -0,0 +1,53 @@ +{ + "name": "@aws-amplify/amplify-gen1-codegen-storage-adapter", + "version": "0.1.0-next-9.0", + "type": "commonjs", + "main": "lib/index.js", + "dependencies": { + "@aws-amplify/amplify-gen2-codegen": "0.1.0-next-9.0", + "@aws-sdk/client-amplify": "^3.592.0", + "@aws-sdk/client-cognito-identity": "^3.592.0", + "@aws-sdk/client-cognito-identity-provider": "^3.592.0", + "@aws-sdk/client-s3": "^3.592.0" + }, + "devDependencies": { + "jest": "^29.5.0", + "typescript": "^5.4.5" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "pretest": "mkdir -p coverage", + "test": "jest --logHeapUsage", + "build": "tsc", + "watch": "tsc -w", + "extract-api": "ts-node ../../scripts/extract-api.ts" + }, + "author": "", + "license": "Apache-2.0", + "description": "" +} diff --git a/packages/amplify-gen1-codegen-storage-adapter/src/gen1_storage_codegen_adapter.test.ts b/packages/amplify-gen1-codegen-storage-adapter/src/gen1_storage_codegen_adapter.test.ts new file mode 100644 index 00000000000..7aa0814c36e --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/src/gen1_storage_codegen_adapter.test.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert'; +import { getStorageDefinition } from './gen1_storage_codegen_adapter'; +import { StorageCLIInputsJSON, getStorageAccess } from './storage_access'; + +void describe('getStorageDefinition', () => { + void it('returns the bucket name', () => { + const bucketName = 'my-cool-bucket'; + const definition = getStorageDefinition({ + bucketName, + cliInputs: { + guestAccess: [], + authAccess: [], + }, + }); + assert.equal(definition.storageIdentifier, bucketName); + }); + void it('returns gen 2 permissions', () => { + const gen1Input: StorageCLIInputsJSON = { + authAccess: ['READ', 'CREATE_AND_UPDATE', 'DELETE'], + guestAccess: ['READ', 'CREATE_AND_UPDATE', 'DELETE'], + groupAccess: { + deleters: ['DELETE'], + readers: ['READ'], + creators: ['CREATE_AND_UPDATE'], + }, + }; + const permissions = getStorageAccess(gen1Input); + const definition = getStorageDefinition({ + cliInputs: gen1Input, + bucketName: 'hello', + }); + assert.deepEqual(definition.accessPatterns, permissions); + }); +}); diff --git a/packages/amplify-gen1-codegen-storage-adapter/src/gen1_storage_codegen_adapter.ts b/packages/amplify-gen1-codegen-storage-adapter/src/gen1_storage_codegen_adapter.ts new file mode 100644 index 00000000000..0365f6c1d06 --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/src/gen1_storage_codegen_adapter.ts @@ -0,0 +1,15 @@ +import { StorageTriggerEvent, Lambda, StorageRenderParameters } from '@aws-amplify/amplify-gen2-codegen'; +import { StorageCLIInputsJSON, getStorageAccess } from './storage_access'; + +export type StorageInputs = { + bucketName: string; + cliInputs: StorageCLIInputsJSON; + triggers?: Partial>; +}; +export const getStorageDefinition = ({ bucketName, cliInputs, triggers }: StorageInputs): StorageRenderParameters => { + return { + accessPatterns: getStorageAccess(cliInputs), + storageIdentifier: bucketName, + triggers: triggers ?? {}, + }; +}; diff --git a/packages/amplify-gen1-codegen-storage-adapter/src/index.ts b/packages/amplify-gen1-codegen-storage-adapter/src/index.ts new file mode 100644 index 00000000000..d11096b1279 --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/src/index.ts @@ -0,0 +1,2 @@ +export { getStorageDefinition, StorageInputs } from './gen1_storage_codegen_adapter.js'; +export { StorageCLIInputsJSON, CLIV1Permission } from './storage_access.js'; diff --git a/packages/amplify-gen1-codegen-storage-adapter/src/storage_access.test.ts b/packages/amplify-gen1-codegen-storage-adapter/src/storage_access.test.ts new file mode 100644 index 00000000000..e7738554a41 --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/src/storage_access.test.ts @@ -0,0 +1,68 @@ +import assert from 'node:assert'; +import { Permission } from '@aws-amplify/amplify-gen2-codegen'; +import { StorageCLIInputsJSON, getStorageAccess } from './storage_access'; +import { CLIV1Permission } from './storage_access'; + +const getCLIInput = (): StorageCLIInputsJSON => ({ + resourceName: 'myappstorage', + policyUUID: 'my-policy-uuid', + bucketName: 'my-cool-bucket', + storageAccess: 'authAndGuest', + guestAccess: [], + authAccess: [], + triggerFunction: 'S3Triggerb5519e27', + groupAccess: {}, +}); + +void describe('getStorageAccess', () => { + void describe('group permissions', () => { + type StorageAccessTestCase = { + gen1: Record; + gen2: Record; + }; + const testCases: StorageAccessTestCase[] = [ + { + gen1: { + managers: ['CREATE_AND_UPDATE', 'READ', 'DELETE'], + employees: ['CREATE_AND_UPDATE', 'READ'], + viewers: ['READ'], + }, + gen2: { + managers: ['read', 'write', 'delete'], + employees: ['write', 'read'], + viewers: ['read'], + }, + }, + ]; + for (const { gen1, gen2 } of testCases) { + void it('returns group permissions', () => { + const input = getCLIInput(); + input.groupAccess = gen1; + const access = getStorageAccess(input); + for (const group in gen1) { + assert.deepEqual(access?.groups?.[group].sort(), gen2[group].sort()); + } + }); + + void it('returns empty group permissions', () => { + const input = getCLIInput(); + input.groupAccess = {}; + const access = getStorageAccess(input); + assert.notEqual(access, undefined); + assert.deepEqual(access?.groups, undefined); + }); + } + }); + void describe('auth and unauth', () => { + void it('correctly maps permissions', () => { + const input = getCLIInput(); + const unauthPermissions: CLIV1Permission[] = ['READ']; + const authPermissions: CLIV1Permission[] = ['CREATE_AND_UPDATE', 'DELETE', 'READ']; + input.authAccess = authPermissions; + input.guestAccess = unauthPermissions; + const access = getStorageAccess(input); + assert.deepEqual(access?.guest?.sort(), ['read'].sort()); + assert.deepEqual(access?.auth?.sort(), ['read', 'write', 'delete'].sort()); + }); + }); +}); diff --git a/packages/amplify-gen1-codegen-storage-adapter/src/storage_access.ts b/packages/amplify-gen1-codegen-storage-adapter/src/storage_access.ts new file mode 100644 index 00000000000..6b559394bf4 --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/src/storage_access.ts @@ -0,0 +1,36 @@ +import { Permission, AccessPatterns } from '@aws-amplify/amplify-gen2-codegen'; + +export type CLIV1Permission = 'READ' | 'CREATE_AND_UPDATE' | 'DELETE'; +export type StorageCLIInputsJSON = { + resourceName?: string; + policyUUID?: string; + bucketName?: string; + storageAccess?: string; + guestAccess: CLIV1Permission[]; + authAccess: CLIV1Permission[]; + triggerFunction?: string; + groupAccess?: Record; +}; + +const PERMISSION_MAP: Record = { + READ: ['read'], + DELETE: ['delete'], + CREATE_AND_UPDATE: ['write'], +}; +const getGen2Permissions = (permissions: CLIV1Permission[]): Permission[] => { + return permissions.flatMap((p) => PERMISSION_MAP[p]); +}; +export const getStorageAccess = (input: StorageCLIInputsJSON): AccessPatterns => { + let groups: AccessPatterns['groups'] | undefined; + if (input.groupAccess && Object.keys(input.groupAccess).length > 0) { + groups = Object.entries(input.groupAccess).reduce((acc, [key, value]) => { + acc[key] = getGen2Permissions(value); + return acc; + }, {} as NonNullable); + } + return { + guest: getGen2Permissions(input.guestAccess), + auth: getGen2Permissions(input.authAccess), + groups, + }; +}; diff --git a/packages/amplify-gen1-codegen-storage-adapter/tsconfig.json b/packages/amplify-gen1-codegen-storage-adapter/tsconfig.json new file mode 100644 index 00000000000..5dea616bde6 --- /dev/null +++ b/packages/amplify-gen1-codegen-storage-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "extends": "../../tsconfig.base.json", + "references": [ + { + "path": "../amplify-gen2-codegen" + } + ] +} diff --git a/packages/amplify-gen2-codegen/.gitignore b/packages/amplify-gen2-codegen/.gitignore new file mode 100644 index 00000000000..2e6b92a0c97 --- /dev/null +++ b/packages/amplify-gen2-codegen/.gitignore @@ -0,0 +1,2 @@ +lib/ +coverage/ diff --git a/packages/amplify-gen2-codegen/API.md b/packages/amplify-gen2-codegen/API.md new file mode 100644 index 00000000000..0f140f2881d --- /dev/null +++ b/packages/amplify-gen2-codegen/API.md @@ -0,0 +1,287 @@ +## API Report File for "@aws-amplify/amplify-gen2-codegen" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { BucketAccelerateStatus } from '@aws-sdk/client-s3'; +import { BucketVersioningStatus } from '@aws-sdk/client-s3'; +import { EnvironmentResponse } from '@aws-sdk/client-lambda'; +import { PasswordPolicyType } from '@aws-sdk/client-cognito-identity-provider'; +import { Runtime } from '@aws-sdk/client-lambda'; +import { ServerSideEncryptionByDefault } from '@aws-sdk/client-s3'; +import { UserPoolClientType } from '@aws-sdk/client-cognito-identity-provider'; + +// @public (undocumented) +export type AccessPatterns = { + auth?: Permission[]; + guest?: Permission[]; + groups?: Record; +}; + +// @public (undocumented) +export type Attribute = 'address' | 'birthdate' | 'email' | 'familyName' | 'gender' | 'givenName' | 'locale' | 'middleName' | 'fullname' | 'nickname' | 'phoneNumber' | 'profilePicture' | 'preferredUsername' | 'profilePage' | 'timezone' | 'lastUpdateTime' | 'website'; + +// @public (undocumented) +export type AttributeMappingRule = Record; + +// @public (undocumented) +export interface AuthDefinition { + // (undocumented) + customUserAttributes?: CustomAttributes; + // (undocumented) + groups?: Group[]; + // (undocumented) + guestLogin?: boolean; + // (undocumented) + identityPoolName?: string; + // (undocumented) + lambdaTriggers?: Partial; + // (undocumented) + loginOptions?: LoginOptions; + // (undocumented) + mfa?: MultifactorOptions; + // (undocumented) + oAuthFlows?: string[]; + // (undocumented) + readAttributes?: string[]; + // (undocumented) + referenceAuth?: ReferenceAuth; + // (undocumented) + standardUserAttributes?: StandardAttributes; + // (undocumented) + userPoolClient?: UserPoolClientType; + // (undocumented) + userPoolOverrides?: PolicyOverrides; + // (undocumented) + writeAttributes?: string[]; +} + +// @public (undocumented) +export type AuthLambdaTriggers = Record; + +// @public (undocumented) +export type AuthTriggerEvents = 'createAuthChallenge' | 'customMessage' | 'defineAuthChallenge' | 'postAuthentication' | 'postConfirmation' | 'preAuthentication' | 'preSignUp' | 'preTokenGeneration' | 'userMigration' | 'verifyAuthChallengeResponse'; + +// @public (undocumented) +export const createGen2Renderer: ({ outputDir, backendEnvironmentName, auth, storage, data, functions, customResources, unsupportedCategories, fileWriter, }: Readonly) => Renderer; + +// @public (undocumented) +export type CustomAttribute = { + readonly dataType: string | undefined; + readonly mutable?: boolean; + minLen?: number; + maxLen?: number; + min?: number; + max?: number; +}; + +// @public (undocumented) +export type CustomAttributes = Partial>; + +// @public (undocumented) +export type DataDefinition = { + tableMappings: Record; + schema: string; +}; + +// @public (undocumented) +export type DataTableMapping = Record; + +// @public (undocumented) +export type EmailOptions = { + emailVerificationBody: string; + emailVerificationSubject: string; +}; + +// @public (undocumented) +export interface FunctionDefinition { + // (undocumented) + category?: string; + // (undocumented) + entry?: string; + // (undocumented) + environment?: EnvironmentResponse; + // (undocumented) + memoryMB?: number; + // (undocumented) + name?: string; + // (undocumented) + resourceName?: string; + // (undocumented) + runtime?: Runtime | string; + // (undocumented) + schedule?: string; + // (undocumented) + timeoutSeconds?: number; +} + +// @public (undocumented) +export interface Gen2RenderingOptions { + // (undocumented) + appId?: string; + // (undocumented) + auth?: AuthDefinition; + // (undocumented) + backendEnvironmentName?: string | undefined; + // (undocumented) + customResources?: Map; + // (undocumented) + data?: DataDefinition; + // (undocumented) + fileWriter?: (content: string, path: string) => Promise; + // (undocumented) + functions?: FunctionDefinition[]; + // (undocumented) + outputDir: string; + // (undocumented) + storage?: StorageRenderParameters; + // (undocumented) + unsupportedCategories?: Map; +} + +// @public (undocumented) +export type Group = string; + +// @public (undocumented) +export type Lambda = { + source: string; +}; + +// @public (undocumented) +export type LoginOptions = { + email?: boolean; + phone?: boolean; + emailOptions?: Partial; + googleLogin?: boolean; + amazonLogin?: boolean; + appleLogin?: boolean; + facebookLogin?: boolean; + oidcLogin?: OidcOptions[]; + samlLogin?: SamlOptions; + googleAttributes?: AttributeMappingRule; + amazonAttributes?: AttributeMappingRule; + appleAttributes?: AttributeMappingRule; + facebookAttributes?: AttributeMappingRule; + callbackURLs?: string[]; + logoutURLs?: string[]; + scopes?: Scope[]; + [key: string]: boolean | Partial | string[] | Scope[] | OidcOptions[] | SamlOptions | AttributeMappingRule | undefined; +}; + +// @public (undocumented) +export type MetadataOptions = { + metadataContent: string; + metadataType: 'URL' | 'FILE'; +}; + +// @public (undocumented) +export type MultifactorOptions = { + mode: UserPoolMfaConfig; + totp?: boolean; + sms?: boolean; +}; + +// @public (undocumented) +export type OidcEndPoints = { + authorization?: string; + token?: string; + userInfo?: string; + jwksUri?: string; +}; + +// @public (undocumented) +export type OidcOptions = { + issuerUrl: string; + name?: string; + endpoints?: OidcEndPoints; + attributeMapping?: AttributeMappingRule; +}; + +// @public (undocumented) +export type PasswordPolicyPath = `Policies.PasswordPolicy.${keyof PasswordPolicyType}`; + +// @public (undocumented) +export type Permission = 'read' | 'write' | 'create' | 'delete'; + +// @public (undocumented) +export type PolicyOverrides = Partial>; + +// @public (undocumented) +export type ReferenceAuth = { + userPoolId?: string; + identityPoolId?: string; + authRoleArn?: string; + unauthRoleArn?: string; + userPoolClientId?: string; + groups?: Record; +}; + +// @public (undocumented) +export interface Renderer { + // (undocumented) + render(): Promise; +} + +// @public (undocumented) +export type S3TriggerDefinition = Record; + +// @public (undocumented) +export type SamlOptions = { + name?: string; + metadata: MetadataOptions; + attributeMapping?: AttributeMappingRule; +}; + +// @public (undocumented) +export type Scope = 'PHONE' | 'EMAIL' | 'OPENID' | 'PROFILE' | 'COGNITO_ADMIN'; + +// @public (undocumented) +export type SendingAccount = 'COGNITO_DEFAULT' | 'DEVELOPER'; + +// @public (undocumented) +export type ServerSideEncryptionConfiguration = { + serverSideEncryptionByDefault: ServerSideEncryptionByDefault; + bucketKeyEnabled: boolean; +}; + +// @public (undocumented) +export type StandardAttribute = { + readonly mutable?: boolean; + readonly required?: boolean; +}; + +// @public (undocumented) +export type StandardAttributes = Partial>; + +// @public (undocumented) +export interface StorageRenderParameters { + // (undocumented) + accelerateConfiguration?: BucketAccelerateStatus; + // (undocumented) + accessPatterns?: AccessPatterns; + // (undocumented) + bucketEncryptionAlgorithm?: ServerSideEncryptionConfiguration; + // (undocumented) + bucketName?: string; + // (undocumented) + dynamoDB?: string; + // (undocumented) + lambdas?: S3TriggerDefinition[]; + // (undocumented) + storageIdentifier?: string; + // (undocumented) + triggers?: Partial>; + // (undocumented) + versioningConfiguration?: BucketVersioningStatus; +} + +// @public (undocumented) +export type StorageTriggerEvent = 'onDelete' | 'onUpload'; + +// @public (undocumented) +export type UserPoolMfaConfig = 'OFF' | 'REQUIRED' | 'OPTIONAL'; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/amplify-gen2-codegen/CHANGELOG.md b/packages/amplify-gen2-codegen/CHANGELOG.md new file mode 100644 index 00000000000..dacf1bc9257 --- /dev/null +++ b/packages/amplify-gen2-codegen/CHANGELOG.md @@ -0,0 +1,250 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-9.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-next-7.0...@aws-amplify/amplify-gen2-codegen@0.1.0-next-9.0) (2025-04-22) + + +### Bug Fixes + +* **migrate:** codegen triggers created outside of standard triggers flow ([bc7010a](https://github.com/aws-amplify/amplify-cli/commit/bc7010ab5928681d2a208d6576296b80f09d6e78)) + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-next-6.0...@aws-amplify/amplify-gen2-codegen@0.1.0-next-7.0) (2025-04-19) + + +### Bug Fixes + +* add amplify data as a dependency to amplify-gen2-codegen ([3f0713b](https://github.com/aws-amplify/amplify-cli/commit/3f0713b9777e37a240e9c16c0840c533c880e753)) +* add domain removal statement ([341cdb1](https://github.com/aws-amplify/amplify-cli/commit/341cdb19260cac829317518731cbae61c7394723)) +* added error handling ([a4647df](https://github.com/aws-amplify/amplify-cli/commit/a4647df76b7e6c8a470690dd43320a96a0a45e97)) +* codegen custom resources and added function scheduling ([1e25182](https://github.com/aws-amplify/amplify-cli/commit/1e251820905291bc8c6ca058ce5397b7ddec5e5b)) +* fixed test ([b56171e](https://github.com/aws-amplify/amplify-cli/commit/b56171e4f354baf882d70ffa6997fe6a32914690)) +* lint errors, add type gauard for auth output ([4b74382](https://github.com/aws-amplify/amplify-cli/commit/4b7438270634b04cd73c302610e814790fb4328a)) +* remove extraneous provider setup code ([5b91485](https://github.com/aws-amplify/amplify-cli/commit/5b9148547026ead6a04628e8b56cb5eeccce97c6)) +* remove s3 deletion policy ([7c5abf3](https://github.com/aws-amplify/amplify-cli/commit/7c5abf310df3c9c9ae60fd367d49bf402d248a97)) + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-next-5.0...@aws-amplify/amplify-gen2-codegen@0.1.0-next-6.0) (2025-03-21) + + +### Bug Fixes + +* add idp codegen and fix a couple of minor bugs ([2b1d203](https://github.com/aws-amplify/amplify-cli/commit/2b1d203f2c065a701076cf4cdbd50b4ef51bc7ee)) +* **amplify-gen2-codegen:** remove extraneous newline ([aaf6dba](https://github.com/aws-amplify/amplify-cli/commit/aaf6dba696091933bc99583a2262f23dc96ea0ec)) +* migration qa feedback ([0f50d1c](https://github.com/aws-amplify/amplify-cli/commit/0f50d1c0ff2607c63cd3bcdd1589d38940a2b6d4)) + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-next-4.0...@aws-amplify/amplify-gen2-codegen@0.1.0-next-5.0) (2025-03-19) + + +### Bug Fixes + +* **amplify-gen2-codegen:** add node 20 and 22 version support for functions ([9fc8294](https://github.com/aws-amplify/amplify-cli/commit/9fc82944d962a56b0e0c9bde2ffb7b824f97bd15)) +* **amplify-gen2-codegen:** skip setting runtime for unsupported runtimes in function ([91486f1](https://github.com/aws-amplify/amplify-cli/commit/91486f1e4761db29ba2296bc8f6acba752636b55)) +* update data prop names ([#14139](https://github.com/aws-amplify/amplify-cli/issues/14139)) ([416d561](https://github.com/aws-amplify/amplify-cli/commit/416d5612f1b2e5faa2e736250cbf673476451aeb)) + + + + + +# [0.1.0-next-4.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-next.0...@aws-amplify/amplify-gen2-codegen@0.1.0-next-4.0) (2025-03-12) + +**Note:** Version bump only for package @aws-amplify/amplify-gen2-codegen + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-beta-latest.0...@aws-amplify/amplify-gen2-codegen@0.1.0-next.0) (2025-02-14) + + +### Bug Fixes + +* add tag to force deployment post refactor in gen2 codegen ([dc953af](https://github.com/aws-amplify/amplify-cli/commit/dc953afd376eb6d7f36729580c9b2cc0a1a09652)) +* add tags only for auth or storage categories ([203425d](https://github.com/aws-amplify/amplify-cli/commit/203425d4c61c71cd974efaa22d3d12fc1c060193)) +* added dynamic reference to the env name in backend.ts and fixed a few bugs ([59e6a01](https://github.com/aws-amplify/amplify-cli/commit/59e6a014a6aadc17c170e57e6278242bed054697)) +* conditional data codegen, phone attribute ([0d5c59f](https://github.com/aws-amplify/amplify-cli/commit/0d5c59fae2849277bced4e4f0c0529d916c0e165)) + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-alpha.1...@aws-amplify/amplify-gen2-codegen@0.1.0-beta-latest.0) (2025-02-12) + + +### Bug Fixes + +* add check for client secret key presence in map ([89b420b](https://github.com/aws-amplify/amplify-cli/commit/89b420b0194143af4326c2b193210a0f29c4c5a2)) +* add import for removal policy in gen2 codegen ([300d169](https://github.com/aws-amplify/amplify-cli/commit/300d1696968705d90788a2b6393884631f29873e)) +* add uncomment instructions in readme ([b1ca1b1](https://github.com/aws-amplify/amplify-cli/commit/b1ca1b1efe70425b97c9083f5ac47d71c32aaeb7)) +* dedupe lock, update api md ([7520e27](https://github.com/aws-amplify/amplify-cli/commit/7520e2760cc2fa0934f3c095f37aedc01b689161)) +* gen2 data table mapping, userpoolclient provider casing ([ab5a244](https://github.com/aws-amplify/amplify-cli/commit/ab5a244da56022a67fa275f10e3f4a2fe53a0a78)) +* lint errors in gen2 codegen ([566f887](https://github.com/aws-amplify/amplify-cli/commit/566f8878a314089aed9bf15ad9524cb620ded0d9)) +* orphaned functions, import auth ([26fd22b](https://github.com/aws-amplify/amplify-cli/commit/26fd22be0232ba11e37d165135c0912deeb0c520)) +* package json name and deps, always set generateSecrets prop ([731071c](https://github.com/aws-amplify/amplify-cli/commit/731071c8c12e64e33229b856d9d5decc680efc16)) + + +### Features + +* include all envs in gen 2 data codegen ([#14087](https://github.com/aws-amplify/amplify-cli/issues/14087)) ([6a437e3](https://github.com/aws-amplify/amplify-cli/commit/6a437e3345489ce22d78621de18acc46f969d883)) + + + + + +# [0.1.0-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-alpha.0...@aws-amplify/amplify-gen2-codegen@0.1.0-alpha.1) (2024-12-05) + + +### Bug Fixes + +* added user pool client codegen ([29a7b5e](https://github.com/aws-amplify/amplify-cli/commit/29a7b5eed227b1fa3e5df670cd527477fe5df321)) +* addressed comments ([9b4eaab](https://github.com/aws-amplify/amplify-cli/commit/9b4eaab12cc08e8e6c6bf7c45deb9961824243d4)) +* addressed comments ([458c5e3](https://github.com/aws-amplify/amplify-cli/commit/458c5e3ee0d53ad7faaa770894b385acfdf00c96)) +* fixed ESLint ([3d61929](https://github.com/aws-amplify/amplify-cli/commit/3d61929695d38c6642bcd9f6fb01677a7c86be4a)) +* lint ([dc5f7b0](https://github.com/aws-amplify/amplify-cli/commit/dc5f7b03f3a46403f7e3b1cf1673c8f6cadf0865)) + + + + + +# [0.1.0-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-gen2-migrations-alpha.0...@aws-amplify/amplify-gen2-codegen@0.1.0-alpha.0) (2024-11-21) + + +### Bug Fixes + +* api md export ([1f5d7ee](https://github.com/aws-amplify/amplify-cli/commit/1f5d7ee2c01bcd4dbf1741ead5bcc8c5089db717)) +* delete codegen e2e package ([71cc41b](https://github.com/aws-amplify/amplify-cli/commit/71cc41bbfe62cbede225b31b5fd6ad37ce986b58)) +* include only required userAttributes and generate identityPoolName in backend file ([76f1bf8](https://github.com/aws-amplify/amplify-cli/commit/76f1bf8bdbc9135bf0f9c983fd2f5448a169af42)) +* prefer early return instead of else block with nesting ([72d178b](https://github.com/aws-amplify/amplify-cli/commit/72d178bcdf10b660ff53f90ca9bb3c24dd460344)) +* ref auth - set group name prop as a string to accomodate hyphenated chars ([785ae3a](https://github.com/aws-amplify/amplify-cli/commit/785ae3aadf560c2b9adc4be7a465ecb42c5ab0ff)) +* remove duplicate code and .amplify dir ([822bc58](https://github.com/aws-amplify/amplify-cli/commit/822bc5844aa59f22068b4dcb6b09766a5de3ad52)) +* remove duplicate test ([3e445d5](https://github.com/aws-amplify/amplify-cli/commit/3e445d512ba1e299d319d13007d573c3e82a4a33)) +* remove unused vars ([fdeb8dd](https://github.com/aws-amplify/amplify-cli/commit/fdeb8dd8395ab9fbfdb3d1946cf9470e4ca21153)) +* resolve api extract errors ([1ee4481](https://github.com/aws-amplify/amplify-cli/commit/1ee4481b45ee1ce24b1f0c521459095888e0b59e)) +* update API.md file for gen1-gen2 codegen ([2531475](https://github.com/aws-amplify/amplify-cli/commit/2531475bb5b65ab3d2a9cdf63b97f81a0916069b)) +* updated storage codegen to include encryption and removal policy ([94299ce](https://github.com/aws-amplify/amplify-cli/commit/94299ced6bd550675ecd87d9087fbca190cce740)) + + +### Features + +* ref auth codegen ([d6b1f28](https://github.com/aws-amplify/amplify-cli/commit/d6b1f288299c03d8809ccb3bcf8b74129c850e56)) + + + + + +# [0.1.0-gen2-migrations-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-gen2-migration-test-alpha.0...@aws-amplify/amplify-gen2-codegen@0.1.0-gen2-migrations-alpha.0) (2024-10-10) + + +### Bug Fixes + +* add usage data metrics for codegen ([ffc8041](https://github.com/aws-amplify/amplify-cli/commit/ffc8041041c6d1b66589c537e93f05a7453e5bc9)) +* move importedModels key up to defineData ([#13943](https://github.com/aws-amplify/amplify-cli/issues/13943)) ([9bae7d4](https://github.com/aws-amplify/amplify-cli/commit/9bae7d460b70f3ab799d56531d2d3927a8a10f83)) + + + + + +# [0.1.0-gen2-migration-test-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-gen2-codegen@0.1.0-gen2-migrations-test.0...@aws-amplify/amplify-gen2-codegen@0.1.0-gen2-migration-test-alpha.0) (2024-09-26) + + +### Features + +* add error for unsupported categories ([a22772d](https://github.com/aws-amplify/amplify-cli/commit/a22772d54c65ff59dffd5721e17ec4501c16d759)) +* unsupported categories codegen ([1e8d175](https://github.com/aws-amplify/amplify-cli/commit/1e8d17585157a460ae8cf1f53546b270893e2b99)) + + + + + +# 0.1.0-gen2-migrations-test.0 (2024-09-23) + + +### Bug Fixes + +* add attribute mapping for external providers ([4f4d9fd](https://github.com/aws-amplify/amplify-cli/commit/4f4d9fd261eefbaca6bd3a563b03e59573869e91)) +* add relevant removed code due to incorrect merge ([fe1ab64](https://github.com/aws-amplify/amplify-cli/commit/fe1ab6430a668fb55e280552cb358ae97503d002)) +* add test cases for source builder and synthesizer ([c7bb106](https://github.com/aws-amplify/amplify-cli/commit/c7bb10681a1cbdd1e92eebcc81357399cf681362)) +* add test cases for source builder and synthesizer ([94e1a0e](https://github.com/aws-amplify/amplify-cli/commit/94e1a0e25ac33a42ebd960ae2bcaebea746bd4b7)) +* bugfixes for data codegen ([#13880](https://github.com/aws-amplify/amplify-cli/issues/13880)) ([263cd85](https://github.com/aws-amplify/amplify-cli/commit/263cd85da1acb689e647db42fe0bf176da036cb5)) +* correct package versions; remove unused import ([2855e28](https://github.com/aws-amplify/amplify-cli/commit/2855e28744bc0d319ff85d7a7a1a36d5fbdad253)) +* extract api ([6f4c58b](https://github.com/aws-amplify/amplify-cli/commit/6f4c58b947fa3be4c2c7c200484fa46b6823bb30)) +* fixed warnings in API.md ([49ed426](https://github.com/aws-amplify/amplify-cli/commit/49ed4269c77927dad85fa805174249ac6b1f2ac6)) +* lint spellcheck and unexpected any error ([5b85e96](https://github.com/aws-amplify/amplify-cli/commit/5b85e96ae87ab3278313010a8b0837b61cac37d7)) +* make gen2 migration packages public ([a7832cb](https://github.com/aws-amplify/amplify-cli/commit/a7832cb622cabf3eec3f770393477256117ea47d)) +* **migrate:** convert to gen2 app ([abeb9c9](https://github.com/aws-amplify/amplify-cli/commit/abeb9c9863c6aa78dde0f5b10228537f1038c9b1)) +* remove unnecessary log statement ([c3943b0](https://github.com/aws-amplify/amplify-cli/commit/c3943b0a8ad191af5a8b5f0c8928934641663cb9)) +* resolve extract-api warnings and add saml to dict ([60d2ac9](https://github.com/aws-amplify/amplify-cli/commit/60d2ac94878b76ac7627ea01c51058cbc42324ef)) +* resolve failing test error ([c28e4f9](https://github.com/aws-amplify/amplify-cli/commit/c28e4f9418d6f6b9139b5c0907c2b76f723d7311)) +* resolve incorrect mfaconifg option ([5f1dd79](https://github.com/aws-amplify/amplify-cli/commit/5f1dd79bbebab1616a5752524d2ecb0ec255fd1a)) +* resolve lint and extract-api errors ([e924e3f](https://github.com/aws-amplify/amplify-cli/commit/e924e3f871e1c58767c2088c0fa8b9dc1cbfb7ec)) +* resolve test errors ([6e72ab4](https://github.com/aws-amplify/amplify-cli/commit/6e72ab4b3db6cfb52dc72fbea2651874402c81ba)) +* resolve test errors ([a555585](https://github.com/aws-amplify/amplify-cli/commit/a555585455623fbc8fbd19cfb54eb47b14fa56ef)) +* resolve workflow errors ([b2e96ea](https://github.com/aws-amplify/amplify-cli/commit/b2e96ea522810edcd4acc69a0b1fe2dc203edba7)) +* resolve workflow errors ([aad8b48](https://github.com/aws-amplify/amplify-cli/commit/aad8b486809a49b38c39570047418aa4c808bf70)) +* resolve workflow errors ([1d5be0a](https://github.com/aws-amplify/amplify-cli/commit/1d5be0a175f1053a6302dd2c1c7032fa75356f83)) +* yarn extract-api changes ([e0a33e3](https://github.com/aws-amplify/amplify-cli/commit/e0a33e3f3db6f7d8426b481a081807e6c17391d7)) + + +### Features + +* add comments to gen1 triggers ([#13866](https://github.com/aws-amplify/amplify-cli/issues/13866)) ([2ec9470](https://github.com/aws-amplify/amplify-cli/commit/2ec947084a89bb000f2b34cc2662121e8cf04fb6)) +* added custom attributes codegen ([0b44538](https://github.com/aws-amplify/amplify-cli/commit/0b445387e45faaa851df93d76cdcdddb6b55f8fe)) +* added custom attributes codegen ([2be715c](https://github.com/aws-amplify/amplify-cli/commit/2be715c9acca312c760e4fd70b519fea14256ea9)) +* added functions auth ([50dc7a2](https://github.com/aws-amplify/amplify-cli/commit/50dc7a20e43898b964df824a0a91d1d3b182a461)) +* added functions auth ([46d8524](https://github.com/aws-amplify/amplify-cli/commit/46d8524f78d04de802e770276021ec0b2b25a73d)) +* added functions auth ([263bc8a](https://github.com/aws-amplify/amplify-cli/commit/263bc8a46666fa845b2bee28d71f07d95f937002)) +* added functions codegen ([80580ce](https://github.com/aws-amplify/amplify-cli/commit/80580ce9560273af0983b65c5a8134cadfc5a869)) +* added functions codegen ([8b679a6](https://github.com/aws-amplify/amplify-cli/commit/8b679a64f20d30f7399302c17599538589381a4d)) +* added functions codegen ([b9080ec](https://github.com/aws-amplify/amplify-cli/commit/b9080ecafae25390b05aaf37326fa38cb8640c6b)) +* bucket versioning override codegen ([c14156d](https://github.com/aws-amplify/amplify-cli/commit/c14156d4fed0514b0bf7ed6f885bac0419f3dcb2)) +* **cli:** initial migration merge ([f803827](https://github.com/aws-amplify/amplify-cli/commit/f8038278b95d321aef4ff75b1bd5a604815fc821)) +* **cli:** initial migration merge ([#13856](https://github.com/aws-amplify/amplify-cli/issues/13856)) ([ebe5cd0](https://github.com/aws-amplify/amplify-cli/commit/ebe5cd046cfb18c38ffdce17610ed3a133cc9d44)) +* configure username codegen ([f032b76](https://github.com/aws-amplify/amplify-cli/commit/f032b762c870b8d50729ab044eeae87be880606e)) +* configure username codegen ([b06eb18](https://github.com/aws-amplify/amplify-cli/commit/b06eb1848ffe52d963448ae43a7c8d286edf4953)) +* fixed failing test ([61dbbac](https://github.com/aws-amplify/amplify-cli/commit/61dbbac8ad39d7d288881a438a6881bf6cdf0e87)) +* fixed lint and ran yarn extract-api ([b4f256c](https://github.com/aws-amplify/amplify-cli/commit/b4f256c3b433a38974f7a8612505d1c7c21befeb)) +* friendly userpool name codegen ([b03e5b0](https://github.com/aws-amplify/amplify-cli/commit/b03e5b03ab7fc0a70ff3981b1232c61edc0fc3a3)) +* friendly userpool name codegen ([3057f69](https://github.com/aws-amplify/amplify-cli/commit/3057f696f3aa000073c2a64a1e83e1ac985256c3)) +* functions codegeb ([ba3babf](https://github.com/aws-amplify/amplify-cli/commit/ba3babfb1403e8f740e1cfbf795707cdd085612f)) +* oauth flows codegen ([8858ef9](https://github.com/aws-amplify/amplify-cli/commit/8858ef92d2f005d6ebe5363e8bb8696a9a72e8ed)) +* oauth flows codegen ([7e0d535](https://github.com/aws-amplify/amplify-cli/commit/7e0d53591d8acb78a05e23ffcb75545d8f08a84f)) +* oauth scopes codegen ([a0edbc1](https://github.com/aws-amplify/amplify-cli/commit/a0edbc1af025ed6058ed9098da240a05f68384f2)) +* oauth scopes codegen ([6ad8080](https://github.com/aws-amplify/amplify-cli/commit/6ad808008f74941644500bd71bcbefeebaf9afd9)) +* oidc/saml external providers codegen ([f248955](https://github.com/aws-amplify/amplify-cli/commit/f2489550925e2f90a53a7d0f833d53571a546ae1)) +* oidc/saml external providers codegen ([66df938](https://github.com/aws-amplify/amplify-cli/commit/66df938e01827a5c3ca96be9be9bd6fe42841b02)) +* read/write permissions for attributes codegen ([36021a3](https://github.com/aws-amplify/amplify-cli/commit/36021a35ec554682c4aca0b32d5a82d85c04f749)) +* read/write permissions for attributes codegen ([7a84af5](https://github.com/aws-amplify/amplify-cli/commit/7a84af5639af1a21dd9d90176d4dde5eb526bb9a)) +* signup user attributes/groups auth codegen ([bacb17b](https://github.com/aws-amplify/amplify-cli/commit/bacb17b29f3bd55ac9d28b55903d4091a5786b15)) +* signup user attributes/groups auth codegen ([772b3e6](https://github.com/aws-amplify/amplify-cli/commit/772b3e66cd4e1413daf33e3477feadce7f1a2da5)) +* social auth codegen ([96cc8d5](https://github.com/aws-amplify/amplify-cli/commit/96cc8d580b39ba80745fd235bd00f2b724962adc)) +* storage codegen ([da810f0](https://github.com/aws-amplify/amplify-cli/commit/da810f0168db87be03aab4ba409947c0214f2d42)) +* storage codegen ([83da5fe](https://github.com/aws-amplify/amplify-cli/commit/83da5fea6a06d3c49678799c579ebb5103eb4cca)) +* storage codegen ([a61100e](https://github.com/aws-amplify/amplify-cli/commit/a61100edf2357d18aec8a462a18e0448d659fdb8)) +* storage codegen ([dade8f2](https://github.com/aws-amplify/amplify-cli/commit/dade8f2f9284a7f4f6dae949f1311cf31f100400)) +* storage codegen ([9e45af9](https://github.com/aws-amplify/amplify-cli/commit/9e45af9c881572ce67d5bad7e05e057609c80b00)) +* storage triggers ([#13869](https://github.com/aws-amplify/amplify-cli/issues/13869)) ([3847399](https://github.com/aws-amplify/amplify-cli/commit/38473994e563cd90452ecc50639ea056bb8dd039)) +* unauthenticated logins codegen ([2d0b700](https://github.com/aws-amplify/amplify-cli/commit/2d0b700f099ceb36b70ab0745a562bcdd5f5ce4b)) +* unauthenticated logins codegen ([6f83374](https://github.com/aws-amplify/amplify-cli/commit/6f8337453da7d9889784836452629a5f35d92e0e)) +* update functions codegen ([dc027e9](https://github.com/aws-amplify/amplify-cli/commit/dc027e9030dfd9085451748bf8d9bde76753da44)) +* update functions codegen ([411511d](https://github.com/aws-amplify/amplify-cli/commit/411511d463ba1cccabcf179319eddff06f535c51)) +* update functions codegen ([bfd4be7](https://github.com/aws-amplify/amplify-cli/commit/bfd4be7787e465e02645d60d1caa403fbfa31961)) +* update functions codegen ([47358bd](https://github.com/aws-amplify/amplify-cli/commit/47358bdaa35e807cde5487f236bd54ac992ad96d)) +* update functions codegen ([1ef8938](https://github.com/aws-amplify/amplify-cli/commit/1ef89380028856e39cfcb2b55e8fd1bd7f6e41ed)) +* updated functions codegen ([1e82262](https://github.com/aws-amplify/amplify-cli/commit/1e822625a7058a8e1f251ccab9f96e8661c2d838)) +* updated functions codegen ([bc1acfa](https://github.com/aws-amplify/amplify-cli/commit/bc1acfa9ee8d78e31c3dcb0ec25d0672b0dab1c4)) +* updated functions codegen ([c2c5969](https://github.com/aws-amplify/amplify-cli/commit/c2c5969f083abc4d3701c03403b9873e0fe4e717)) +* updated functions codegen ([5a8819b](https://github.com/aws-amplify/amplify-cli/commit/5a8819bbb014dd482cac1af30d685d92c7fa5fea)) +* updated functions codegen ([4ac9324](https://github.com/aws-amplify/amplify-cli/commit/4ac932478633274e87524aea9eb9f48d3640d36c)) +* updated secret code ([f54457b](https://github.com/aws-amplify/amplify-cli/commit/f54457b8280e4736ea84786f5879206d7eeed571)) diff --git a/packages/amplify-gen2-codegen/package.json b/packages/amplify-gen2-codegen/package.json new file mode 100644 index 00000000000..6b41c9029dc --- /dev/null +++ b/packages/amplify-gen2-codegen/package.json @@ -0,0 +1,54 @@ +{ + "name": "@aws-amplify/amplify-gen2-codegen", + "version": "0.1.0-next-9.0", + "main": "lib/index.js", + "type": "commonjs", + "scripts": { + "extract-api": "ts-node ../../scripts/extract-api.ts", + "build": "tsc", + "watch": "tsc -w", + "pretest": "mkdir -p coverage", + "test": "jest --logHeapUsage" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + }, + "publishConfig": { + "access": "public" + }, + "author": "", + "license": "Apache-2.0", + "description": "", + "dependencies": { + "@aws-amplify/auth-construct": "^1.1.5", + "@aws-sdk/client-cognito-identity-provider": "^3.592.0", + "@aws-sdk/client-lambda": "^3.651.1", + "@aws-sdk/client-s3": "^3.651.1", + "aws-cdk-lib": "~2.187.0", + "typescript": "^5.4.5" + }, + "devDependencies": { + "@types/node": "^20.14.2", + "jest": "^29.5.0" + } +} diff --git a/packages/amplify-gen2-codegen/src/auth/source_builder.test.ts b/packages/amplify-gen2-codegen/src/auth/source_builder.test.ts new file mode 100644 index 00000000000..76ddbb2dfb9 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/auth/source_builder.test.ts @@ -0,0 +1,456 @@ +import { StandardAttributes } from 'aws-cdk-lib/aws-cognito'; +import assert from 'node:assert'; +import { + Attribute, + AttributeMappingRule, + AuthDefinition, + AuthTriggerEvents, + EmailOptions, + ReferenceAuth, + renderAuthNode, + UserPoolMfaConfig, +} from './source_builder'; +import { printNodeArray } from '../test_utils/ts_node_printer'; + +describe('render auth node', () => { + describe('external providers', () => { + describe('Google', () => { + it('renders the google provider', () => { + const rendered = renderAuthNode({ + loginOptions: { googleLogin: true, callbackURLs: ['https://example.com/callback'], logoutURLs: ['https://example.com/logout'] }, + }); + const source = printNodeArray(rendered); + assert.match(source, /google:/); + assert.match(source, /clientId: secret\("GOOGLE_CLIENT_ID"\)/); + assert.match(source, /clientSecret: secret\("GOOGLE_CLIENT_SECRET"\)/); + assert.match(source, /callbackUrls: \[\"https:\/\/example\.com\/callback\"\]/); + assert.match(source, /logoutUrls: \[\"https:\/\/example\.com\/logout\"\]/); + }); + }); + describe('Facebook', () => { + it('renders the facebook provider', () => { + const rendered = renderAuthNode({ + loginOptions: { facebookLogin: true, callbackURLs: ['https://example.com/callback'], logoutURLs: ['https://example.com/logout'] }, + }); + const source = printNodeArray(rendered); + assert.match(source, /facebook:/); + assert.match(source, /clientId: secret\("FACEBOOK_CLIENT_ID"\)/); + assert.match(source, /clientSecret: secret\("FACEBOOK_CLIENT_SECRET"\)/); + assert.match(source, /callbackUrls: \[\"https:\/\/example\.com\/callback\"\]/); + assert.match(source, /logoutUrls: \[\"https:\/\/example\.com\/logout\"\]/); + }); + }); + describe('Apple', () => { + it('renders the apple provider', () => { + const rendered = renderAuthNode({ + loginOptions: { appleLogin: true, callbackURLs: ['https://example.com/callback'], logoutURLs: ['https://example.com/logout'] }, + }); + const source = printNodeArray(rendered); + assert.match(source, /signInWithApple:/); + assert.match(source, /clientId: secret\("SIWA_CLIENT_ID"\)/); + assert.match(source, /keyId: secret\("SIWA_KEY_ID"\)/); + assert.match(source, /privateKey: secret\("SIWA_PRIVATE_KEY"\)/); + assert.match(source, /teamId: secret\("SIWA_TEAM_ID"\)/); + assert.match(source, /callbackUrls: \[\"https:\/\/example\.com\/callback\"\]/); + assert.match(source, /logoutUrls: \[\"https:\/\/example\.com\/logout\"\]/); + }); + }); + describe('Amazon', () => { + it('renders the amazon provider', () => { + const rendered = renderAuthNode({ + loginOptions: { amazonLogin: true, callbackURLs: ['https://example.com/callback'], logoutURLs: ['https://example.com/logout'] }, + }); + const source = printNodeArray(rendered); + assert.match(source, /loginWithAmazon:/); + assert.match(source, /clientId: secret\("LOGINWITHAMAZON_CLIENT_ID"\)/); + assert.match(source, /clientSecret: secret\("LOGINWITHAMAZON_CLIENT_SECRET"\)/); + assert.match(source, /callbackUrls: \[\"https:\/\/example\.com\/callback\"\]/); + assert.match(source, /logoutUrls: \[\"https:\/\/example\.com\/logout\"\]/); + }); + }); + describe('OIDC', () => { + it('renders the oidc provider', () => { + const rendered = renderAuthNode({ + loginOptions: { + oidcLogin: [{ issuerUrl: 'https://e' }, { name: 'Sanay', issuerUrl: 'hey' }], + callbackURLs: ['https://example.com/callback'], + logoutURLs: ['https://example.com/logout'], + }, + }); + const source = printNodeArray(rendered); + assert.match(source, /oidc:/); + assert.match(source, /clientId: secret\("OIDC_CLIENT_ID_1"\)/); + assert.match(source, /clientSecret: secret\("OIDC_CLIENT_SECRET_1"\)/); + assert.match(source, /issuerUrl: \"https:\/\/e\"/); + assert.match(source, /issuerUrl: \"hey\"/); + assert.match(source, /name: "Sanay"/); + }); + it('does not render OIDC if not passed', () => { + const rendered = renderAuthNode({ + loginOptions: { + oidcLogin: [], + }, + }); + const source = printNodeArray(rendered); + assert(!source.includes('oidc:')); + }); + }); + describe('SAML', () => { + it('renders the saml provider', () => { + const rendered = renderAuthNode({ + loginOptions: { + samlLogin: { name: 'Sanay', metadata: { metadataContent: 'content', metadataType: 'URL' } }, + callbackURLs: ['https://example.com/callback'], + logoutURLs: ['https://example.com/logout'], + }, + }); + const source = printNodeArray(rendered); + assert.match(source, /saml:/); + assert.match(source, /metadataContent: \"content\"/); + assert.match(source, /metadataType: \"URL\"/); + assert.match(source, /name: "Sanay"/); + }); + it('does not render SAML if not passed', () => { + const rendered = renderAuthNode({ + loginOptions: {}, + }); + const source = printNodeArray(rendered); + assert(!source.includes('saml:')); + }); + }); + }); + describe('lambda', () => { + it('adds a triggers object when a lambda trigger is defined', () => { + const rendered = renderAuthNode({ lambdaTriggers: { preSignUp: { source: 'amplify/backend/function/testfunction/handler.ts' } } }); + const source = printNodeArray(rendered); + assert.match(source, /triggers: \{/); + }); + const testCases: Record = { + createAuthChallenge: true, + customMessage: true, + defineAuthChallenge: true, + postAuthentication: true, + postConfirmation: true, + preAuthentication: true, + preSignUp: true, + preTokenGeneration: true, + userMigration: true, + verifyAuthChallengeResponse: true, + }; + for (const testCase of Object.keys(testCases)) { + const rendered = renderAuthNode({ lambdaTriggers: { [testCase]: { source: `amplify/backend/function/${testCase}/handler.ts` } } }); + const source = printNodeArray(rendered); + assert.match(source, new RegExp(`triggers:\\s*{\\s*${testCase}:\\s*${testCase}\\s*}`)); + } + }); + describe('mfa', () => { + it('does not render the multifactor property if no multifactor options are specified', () => { + const rendered = renderAuthNode({}); + const source = printNodeArray(rendered); + assert.doesNotMatch(source, new RegExp(`multifactor:`)); + }); + describe('totp', () => { + it('does not render totp if totp is not specified', () => { + const rendered = renderAuthNode({ mfa: { mode: 'OPTIONAL' } }); + const source = printNodeArray(rendered); + assert.doesNotMatch(source, new RegExp(`multifactor:\\s+\\{[\\s\\S]*totp:\\strue`)); + }); + const totpStates: boolean[] = [true, false]; + for (const state of totpStates) { + it(`correctly renders totp state of ${state}`, async () => { + const rendered = renderAuthNode({ mfa: { mode: 'OPTIONAL', totp: state } }); + const source = printNodeArray(rendered); + assert.match(source, new RegExp(`multifactor:\\s+\\{[\\s\\S]*totp:\\s${state}`)); + }); + } + }); + describe('sms', () => { + it('does not render sms if sms is not specified', () => { + const rendered = renderAuthNode({ mfa: { mode: 'OPTIONAL' } }); + const source = printNodeArray(rendered); + assert.doesNotMatch(source, new RegExp(`multifactor:\\s+\\{[\\s\\S]*sms:\\strue`)); + }); + const smsStates: boolean[] = [true, false]; + for (const state of smsStates) { + it(`correctly renders sms state of ${state}`, async () => { + const rendered = renderAuthNode({ mfa: { mode: 'OPTIONAL', sms: state } }); + const source = printNodeArray(rendered); + assert.match(source, new RegExp(`multifactor:\\s+\\{[\\s\\S]*sms:\\s${state}`)); + }); + } + }); + const modes: UserPoolMfaConfig[] = ['REQUIRED', 'OFF', 'OPTIONAL']; + for (const mode of modes) { + it(`correctly renders mfa state of ${mode}`, async () => { + const rendered = renderAuthNode({ mfa: { mode } }); + const source = printNodeArray(rendered); + assert.match(source, new RegExp(`multifactor:\\s+\\{\\s+mode:\\s"${mode}"`)); + }); + } + }); + describe('imports', () => { + it('imports @aws-amplify/backend', async () => { + const rendered = renderAuthNode({ + loginOptions: { email: true }, + }); + const source = printNodeArray(rendered); + assert.match(source, /import\s?\{\s?defineAuth\s?\}\s?from\s?"\@aws-amplify\/backend"/); + }); + }); + describe('username attributes', () => { + describe('Standard Attributes', () => { + const attributes: Array = [ + 'email', + 'gender', + 'locale', + 'address', + 'website', + 'fullname', + 'nickname', + 'timezone', + 'birthdate', + 'givenName', + 'familyName', + 'middleName', + 'phoneNumber', + 'profilePage', + 'profilePicture', + 'lastUpdateTime', + 'preferredUsername', + ]; + for (const attribute of attributes) { + for (const truthiness of [true, false]) { + it(`renders ${attribute}: ${truthiness} individually`, () => { + const authDefinition: AuthDefinition = { + loginOptions: { + email: true, + }, + standardUserAttributes: { + [attribute as Attribute]: { + mutable: truthiness, + required: truthiness, + }, + }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert(source.includes(attribute)); + assert(source.includes(`mutable: ${truthiness}`)); + assert(source.includes(`required: ${truthiness}`)); + }); + } + } + }); + describe('Custom Attributes', () => { + it('renders custom attributes', () => { + const authDefinition: AuthDefinition = { + loginOptions: { + email: true, + }, + customUserAttributes: { 'custom:Test1': { dataType: 'Number', mutable: true, min: 10, max: 100 } }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert(source.includes('custom:Test1')); + assert(source.includes('dataType: "Number"')); + }); + it('does not render anything if CustomAttribute is undefined', () => { + const authDefinition: AuthDefinition = { + loginOptions: { + email: true, + }, + customUserAttributes: { 'custom:isAllowed': undefined }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert(!source.includes('custom:isAllowed')); + }); + }); + }); + describe('groups', () => { + it('renders groups', () => { + const authDefinition: AuthDefinition = { + loginOptions: {}, + groups: ['manager'], + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /defineAuth\(\{[\s\S]*groups:\s\["manager"\]/); + }); + }); + describe('loginWith', () => { + describe('email', () => { + type TestCase = { + optionProperty: T; + gen2DefinitionProperty: string; + value: EmailOptions[T]; + searchPattern: string; + }; + + const emailPropertyTestCases: TestCase[] = [ + { + optionProperty: 'emailVerificationSubject', + value: 'My Verification Subject', + gen2DefinitionProperty: 'verificationEmailSubject', + searchPattern: '"My Verification Subject"', + }, + { + optionProperty: 'emailVerificationBody', + gen2DefinitionProperty: 'verificationEmailBody', + value: 'My Verification Body', + searchPattern: '\\(\\) => "My Verification Body"', + }, + ]; + for (const { optionProperty: property, value, searchPattern, gen2DefinitionProperty } of emailPropertyTestCases) { + it(`renders email login parameter ${property}`, () => { + const emailOptions: Partial = { + [property as keyof EmailOptions]: value, + }; + const authDefinition: AuthDefinition = { + loginOptions: { + emailOptions, + }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match( + source, + new RegExp( + `defineAuth\\(\\{\\s+loginWith:\\s+\\{\\s+email:\\s+\\{\\s+${gen2DefinitionProperty}: ${searchPattern}\\s+\\}\\s+\\}\\s+\\}\\)`, + ), + ); + }); + } + it('renders `email: true`', () => { + const authDefinition: AuthDefinition = { + loginOptions: { + email: true, + }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /defineAuth\(\{\s+loginWith:\s+\{\s+email:\s?true\s+\}\s+\}\)/); + }); + }); + describe('phone', () => { + it('renders `phone: true`', () => { + const authDefinition: AuthDefinition = { + loginOptions: { + phone: true, + }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /defineAuth\(\{\s+loginWith:\s+\{\s+phone:\s?true\s+\}\s+\}\)/); + }); + }); + describe('OAuth scopes', () => { + it('renders oauth scopes', () => { + const authDefinition: AuthDefinition = { + loginOptions: { + googleLogin: true, + scopes: ['EMAIL', 'OPENID'], + }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /defineAuth\(\{[\s\S]*scopes:\s\["EMAIL",\s"OPENID"\]/); + }); + it('renders no oauth scopes if not passed', () => { + const authDefinition: AuthDefinition = { + loginOptions: {}, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.doesNotMatch(source, /scopes:/); + }); + }); + it('renders attributeMapping if passed along with Google login', () => { + const authDefinition: AuthDefinition = { + loginOptions: { + googleLogin: true, + googleAttributes: { fullname: 'name' } as AttributeMappingRule, + }, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /defineAuth\(\{[\s\S]*attributeMapping:\s\{[\s\S]*fullname:\s"name"/); + }); + }); + describe('reference auth', () => { + it(`renders successfully for imported userpool`, () => { + const referenceAuthProps: ReferenceAuth = { + userPoolId: 'userPoolId', + userPoolClientId: 'userPoolClientId', + groups: { + Admin: 'AdminRoleARN', + ReadOnly: 'ReadOnlyRoleARN', + }, + }; + const authDefinition: AuthDefinition = { + referenceAuth: referenceAuthProps, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /referenceAuth/); + assert.match(source, /userPoolId: "userPoolId"/); + assert.match(source, /userPoolClientId: "userPoolClientId"/); + assert.match(source, /groups:/); + assert.match(source, /"Admin": "AdminRoleARN"/); + assert.match(source, /"ReadOnly": "ReadOnlyRoleARN"/); + assert.doesNotMatch(source, /identityPoolId: "identityPoolId"/); + assert.doesNotMatch(source, /authRoleArn: "authRoleArn"/); + assert.doesNotMatch(source, /unauthRoleArn: "unauthRoleArn"/); + }); + + it(`renders successfully for imported identity pool`, () => { + const referenceAuthProps: ReferenceAuth = { + identityPoolId: 'identityPoolId', + authRoleArn: 'authRoleArn', + unauthRoleArn: 'unauthRoleArn', + }; + const authDefinition: AuthDefinition = { + referenceAuth: referenceAuthProps, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /referenceAuth/); + assert.match(source, /identityPoolId: "identityPoolId"/); + assert.match(source, /authRoleArn: "authRoleArn"/); + assert.match(source, /unauthRoleArn: "unauthRoleArn"/); + assert.doesNotMatch(source, /userPoolId: "userPoolId"/); + assert.doesNotMatch(source, /userPoolClientId: "userPoolClientId"/); + assert.doesNotMatch(source, /groups:/); + assert.doesNotMatch(source, /"Admin": "AdminRoleARN"/); + assert.doesNotMatch(source, /"ReadOnly": "ReadOnlyRoleARN"/); + }); + + it(`renders successfully for imported userpool and identity pool`, () => { + const referenceAuthProps: ReferenceAuth = { + userPoolId: 'userPoolId', + userPoolClientId: 'userPoolClientId', + identityPoolId: 'identityPoolId', + authRoleArn: 'authRoleArn', + unauthRoleArn: 'unauthRoleArn', + groups: { + Admin: 'AdminRoleARN', + 'Read-Only': 'ReadOnlyRoleARN', + }, + }; + const authDefinition: AuthDefinition = { + referenceAuth: referenceAuthProps, + }; + const node = renderAuthNode(authDefinition); + const source = printNodeArray(node); + assert.match(source, /referenceAuth/); + assert.match(source, /userPoolId: "userPoolId"/); + assert.match(source, /userPoolClientId: "userPoolClientId"/); + assert.match(source, /identityPoolId: "identityPoolId"/); + assert.match(source, /authRoleArn: "authRoleArn"/); + assert.match(source, /unauthRoleArn: "unauthRoleArn"/); + assert.match(source, /groups:/); + assert.match(source, /"Admin": "AdminRoleARN"/); + assert.match(source, /"Read-Only": "ReadOnlyRoleARN"/); + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/auth/source_builder.ts b/packages/amplify-gen2-codegen/src/auth/source_builder.ts new file mode 100644 index 00000000000..d177e1adc1c --- /dev/null +++ b/packages/amplify-gen2-codegen/src/auth/source_builder.ts @@ -0,0 +1,585 @@ +import ts, { PropertyAssignment } from 'typescript'; +import assert from 'node:assert'; +import { PasswordPolicyType, UserPoolClientType } from '@aws-sdk/client-cognito-identity-provider'; +import { renderResourceTsFile } from '../resource/resource'; +import { createTriggersProperty, Lambda } from '../function/lambda'; + +export type Scope = 'PHONE' | 'EMAIL' | 'OPENID' | 'PROFILE' | 'COGNITO_ADMIN'; + +export type StandardAttribute = { + readonly mutable?: boolean; + readonly required?: boolean; +}; + +export type CustomAttribute = { + readonly dataType: string | undefined; + readonly mutable?: boolean; + + // StringAttributeConstraints + minLen?: number; + maxLen?: number; + + // NumberAttributeConstraints + min?: number; + max?: number; +}; + +export type Attribute = + | 'address' + | 'birthdate' + | 'email' + | 'familyName' + | 'gender' + | 'givenName' + | 'locale' + | 'middleName' + | 'fullname' + | 'nickname' + | 'phoneNumber' + | 'profilePicture' + | 'preferredUsername' + | 'profilePage' + | 'timezone' + | 'lastUpdateTime' + | 'website'; + +export type AttributeMappingRule = Record; + +export type SendingAccount = 'COGNITO_DEFAULT' | 'DEVELOPER'; + +export type UserPoolMfaConfig = 'OFF' | 'REQUIRED' | 'OPTIONAL'; + +export type PasswordPolicyPath = `Policies.PasswordPolicy.${keyof PasswordPolicyType}`; + +export type PolicyOverrides = Partial>; + +export type EmailOptions = { + emailVerificationBody: string; + emailVerificationSubject: string; +}; + +export type StandardAttributes = Partial>; +export type CustomAttributes = Partial>; + +export type Group = string; + +export type MetadataOptions = { + metadataContent: string; + metadataType: 'URL' | 'FILE'; +}; + +export type SamlOptions = { + name?: string; + metadata: MetadataOptions; + attributeMapping?: AttributeMappingRule; +}; + +export type OidcEndPoints = { + authorization?: string; + token?: string; + userInfo?: string; + jwksUri?: string; +}; + +export type OidcOptions = { + issuerUrl: string; + name?: string; + endpoints?: OidcEndPoints; + attributeMapping?: AttributeMappingRule; +}; + +export type LoginOptions = { + email?: boolean; + phone?: boolean; + emailOptions?: Partial; + googleLogin?: boolean; + amazonLogin?: boolean; + appleLogin?: boolean; + facebookLogin?: boolean; + oidcLogin?: OidcOptions[]; + samlLogin?: SamlOptions; + googleAttributes?: AttributeMappingRule; + amazonAttributes?: AttributeMappingRule; + appleAttributes?: AttributeMappingRule; + facebookAttributes?: AttributeMappingRule; + callbackURLs?: string[]; + logoutURLs?: string[]; + scopes?: Scope[]; + [key: string]: boolean | Partial | string[] | Scope[] | OidcOptions[] | SamlOptions | AttributeMappingRule | undefined; +}; + +export type MultifactorOptions = { + mode: UserPoolMfaConfig; + totp?: boolean; + sms?: boolean; +}; + +export type AuthLambdaTriggers = Record; + +export type AuthTriggerEvents = + | 'createAuthChallenge' + | 'customMessage' + | 'defineAuthChallenge' + | 'postAuthentication' + | 'postConfirmation' + | 'preAuthentication' + | 'preSignUp' + | 'preTokenGeneration' + | 'userMigration' + | 'verifyAuthChallengeResponse'; + +export type ReferenceAuth = { + userPoolId?: string; + identityPoolId?: string; + authRoleArn?: string; + unauthRoleArn?: string; + userPoolClientId?: string; + groups?: Record; +}; + +export interface AuthDefinition { + loginOptions?: LoginOptions; + groups?: Group[]; + mfa?: MultifactorOptions; + standardUserAttributes?: StandardAttributes; + customUserAttributes?: CustomAttributes; + userPoolOverrides?: PolicyOverrides; + lambdaTriggers?: Partial; + guestLogin?: boolean; + identityPoolName?: string; + oAuthFlows?: string[]; + readAttributes?: string[]; + writeAttributes?: string[]; + referenceAuth?: ReferenceAuth; + userPoolClient?: UserPoolClientType; +} + +const factory = ts.factory; + +const secretIdentifier = factory.createIdentifier('secret'); +const googleClientID = 'GOOGLE_CLIENT_ID'; +const googleClientSecret = 'GOOGLE_CLIENT_SECRET'; + +const amazonClientID = 'LOGINWITHAMAZON_CLIENT_ID'; +const amazonClientSecret = 'LOGINWITHAMAZON_CLIENT_SECRET'; + +const facebookClientID = 'FACEBOOK_CLIENT_ID'; +const facebookClientSecret = 'FACEBOOK_CLIENT_SECRET'; + +const appleClientID = 'SIWA_CLIENT_ID'; +const appleKeyId = 'SIWA_KEY_ID'; +const applePrivateKey = 'SIWA_PRIVATE_KEY'; +const appleTeamID = 'SIWA_TEAM_ID'; + +const oidcClientID = 'OIDC_CLIENT_ID'; +const oidcClientSecret = 'OIDC_CLIENT_SECRET'; + +function createProviderConfig(config: Record, attributeMapping: AttributeMappingRule | undefined) { + const properties: ts.ObjectLiteralElementLike[] = []; + + Object.entries(config).map(([key, value]) => + properties.push( + factory.createPropertyAssignment( + factory.createIdentifier(key), + factory.createCallExpression(secretIdentifier, undefined, [factory.createStringLiteral(value)]), + ), + ), + ); + + if (attributeMapping) { + const mappingProperties: ts.ObjectLiteralElementLike[] = []; + + Object.entries(attributeMapping).map(([key, value]) => + mappingProperties.push(factory.createPropertyAssignment(factory.createIdentifier(key), factory.createStringLiteral(value))), + ); + + properties.push( + factory.createPropertyAssignment( + factory.createIdentifier('attributeMapping'), + factory.createObjectLiteralExpression(mappingProperties, true), + ), + ); + } + + return properties; +} + +function createProviderPropertyAssignment( + name: string, + config: Record, + attributeMapping: AttributeMappingRule | undefined, +) { + return factory.createPropertyAssignment( + factory.createIdentifier(name), + factory.createObjectLiteralExpression(createProviderConfig(config, attributeMapping), true), + ); +} + +function createOidcSamlPropertyAssignments( + config: Record, +): PropertyAssignment[] { + return Object.entries(config).flatMap(([key, value]) => { + if (typeof value === 'string') { + return [factory.createPropertyAssignment(factory.createIdentifier(key), factory.createStringLiteral(value))]; + } else if (typeof value === 'object' && value !== null) { + return [ + factory.createPropertyAssignment( + factory.createIdentifier(key), + factory.createObjectLiteralExpression(createOidcSamlPropertyAssignments(value), true), + ), + ]; + } + return []; + }); +} + +function createSecretErrorStatements(secretVariables: string[]): ts.Node[] { + return secretVariables.map((secret) => + factory.createCallExpression(factory.createIdentifier('throw new Error'), undefined, [ + factory.createStringLiteral(`Secrets need to be reset, use \`npx ampx sandbox secret set ${secret}\` to set the value`), + ]), + ); +} + +function createExternalProvidersPropertyAssignment( + loginOptions: LoginOptions, + callbackUrls?: string[], + logoutUrls?: string[], + secretErrors?: ts.Node[], +) { + const providerAssignments: PropertyAssignment[] = []; + + if (loginOptions.googleLogin) { + providerAssignments.push( + createProviderPropertyAssignment( + 'google', + { + clientId: googleClientID, + clientSecret: googleClientSecret, + }, + loginOptions.googleAttributes, + ), + ); + secretErrors?.push(...createSecretErrorStatements([googleClientID, googleClientSecret])); + } + + if (loginOptions.appleLogin) { + providerAssignments.push( + createProviderPropertyAssignment( + 'signInWithApple', + { + clientId: appleClientID, + keyId: appleKeyId, + privateKey: applePrivateKey, + teamId: appleTeamID, + }, + loginOptions.appleAttributes, + ), + ); + secretErrors?.push(...createSecretErrorStatements([appleClientID, appleKeyId, applePrivateKey, appleTeamID])); + } + + if (loginOptions.amazonLogin) { + providerAssignments.push( + createProviderPropertyAssignment( + 'loginWithAmazon', + { + clientId: amazonClientID, + clientSecret: amazonClientSecret, + }, + loginOptions.amazonAttributes, + ), + ); + secretErrors?.push(...createSecretErrorStatements([amazonClientID, amazonClientSecret])); + } + + if (loginOptions.facebookLogin) { + providerAssignments.push( + createProviderPropertyAssignment( + 'facebook', + { + clientId: facebookClientID, + clientSecret: facebookClientSecret, + }, + loginOptions.facebookAttributes, + ), + ); + secretErrors?.push(...createSecretErrorStatements([facebookClientID, facebookClientSecret])); + } + + if (loginOptions.samlLogin) { + providerAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier('saml'), + factory.createObjectLiteralExpression(createOidcSamlPropertyAssignments(loginOptions.samlLogin), true), + ), + ); + } + + if (loginOptions.oidcLogin && loginOptions.oidcLogin.length > 0) { + providerAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier('oidc'), + factory.createArrayLiteralExpression( + loginOptions.oidcLogin.map((oidc, index) => + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('clientId'), + factory.createCallExpression(secretIdentifier, undefined, [factory.createStringLiteral(`${oidcClientID}_${index + 1}`)]), + ), + factory.createPropertyAssignment( + factory.createIdentifier('clientSecret'), + factory.createCallExpression(secretIdentifier, undefined, [ + factory.createStringLiteral(`${oidcClientSecret}_${index + 1}`), + ]), + ), + ...createOidcSamlPropertyAssignments(oidc), + ], + true, + ), + ), + true, + ), + ), + ); + secretErrors?.push(...createSecretErrorStatements([oidcClientID, oidcClientSecret])); + } + + if (loginOptions.scopes) { + providerAssignments.push( + factory.createPropertyAssignment( + factory.createIdentifier('scopes'), + factory.createArrayLiteralExpression(loginOptions.scopes.map((scope) => factory.createStringLiteral(scope))), + ), + ); + } + + const properties = [ + ...providerAssignments, + factory.createPropertyAssignment( + factory.createIdentifier('callbackUrls'), + factory.createArrayLiteralExpression(callbackUrls?.map((url) => factory.createStringLiteral(url))), + ), + factory.createPropertyAssignment( + factory.createIdentifier('logoutUrls'), + factory.createArrayLiteralExpression(logoutUrls?.map((url) => factory.createStringLiteral(url))), + ), + ]; + + return factory.createObjectLiteralExpression(properties, true); +} + +function createLogInWithPropertyAssignment(logInDefinition: LoginOptions = {}, secretErrors: ts.Node[]) { + const logInWith = factory.createIdentifier('loginWith'); + const assignments: ts.ObjectLiteralElementLike[] = []; + if (logInDefinition.email === true) { + assignments.push(factory.createPropertyAssignment(factory.createIdentifier('email'), factory.createTrue())); + } else if (typeof logInDefinition.emailOptions === 'object') { + const emailDefinitionAssignments: ts.ObjectLiteralElementLike[] = []; + + if (logInDefinition.emailOptions?.emailVerificationSubject) { + emailDefinitionAssignments.push( + factory.createPropertyAssignment( + 'verificationEmailSubject', + factory.createStringLiteral(logInDefinition.emailOptions.emailVerificationSubject), + ), + ); + } + if (logInDefinition.emailOptions?.emailVerificationBody) { + emailDefinitionAssignments.push( + factory.createPropertyAssignment( + 'verificationEmailBody', + factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + undefined, + factory.createStringLiteral(logInDefinition.emailOptions.emailVerificationBody), + ), + ), + ); + } + const emailDefinitionObject = factory.createObjectLiteralExpression(emailDefinitionAssignments, true); + assignments.push(factory.createPropertyAssignment(factory.createIdentifier('email'), emailDefinitionObject)); + } + if (logInDefinition.phone === true) { + assignments.push(factory.createPropertyAssignment(factory.createIdentifier('phone'), factory.createTrue())); + } + if ( + logInDefinition.amazonLogin || + logInDefinition.googleLogin || + logInDefinition.facebookLogin || + logInDefinition.appleLogin || + (logInDefinition.oidcLogin && logInDefinition.oidcLogin.length > 0) || + logInDefinition.samlLogin + ) { + assignments.push( + factory.createPropertyAssignment( + factory.createIdentifier('externalProviders'), + createExternalProvidersPropertyAssignment(logInDefinition, logInDefinition.callbackURLs, logInDefinition.logoutURLs, secretErrors), + ), + ); + } + return factory.createPropertyAssignment(logInWith, factory.createObjectLiteralExpression(assignments, true)); +} + +const createStandardAttributeDefinition = (attribute: StandardAttribute | CustomAttribute) => { + const properties: ts.PropertyAssignment[] = []; + + for (const key of Object.keys(attribute)) { + const value = attribute[key as keyof (StandardAttribute | CustomAttribute)]; + + if (typeof value === 'boolean') { + properties.push( + factory.createPropertyAssignment(factory.createIdentifier(key), value ? factory.createTrue() : factory.createFalse()), + ); + } else if (typeof value === 'string') { + properties.push(factory.createPropertyAssignment(factory.createIdentifier(key), factory.createStringLiteral(value))); + } else if (typeof value === 'number') { + properties.push(factory.createPropertyAssignment(factory.createIdentifier(key), factory.createNumericLiteral(value))); + } + } + + return factory.createObjectLiteralExpression(properties, true); +}; + +const createUserAttributeAssignments = ( + standardAttributes: StandardAttributes | undefined, + customAttributes: CustomAttributes | undefined, +) => { + const userAttributeIdentifier = factory.createIdentifier('userAttributes'); + const userAttributeProperties = []; + if (standardAttributes !== undefined) { + const standardAttributeProperties = Object.entries(standardAttributes).map(([key, value]) => { + return factory.createPropertyAssignment(factory.createIdentifier(key), createStandardAttributeDefinition(value)); + }); + userAttributeProperties.push(...standardAttributeProperties); + } + if (customAttributes !== undefined) { + const customAttributeProperties = Object.entries(customAttributes) + .map(([key, value]) => { + if (value !== undefined) { + return factory.createPropertyAssignment(factory.createStringLiteral(key), createStandardAttributeDefinition(value)); + } + return undefined; + }) + .filter((property): property is ts.PropertyAssignment => property !== undefined); + userAttributeProperties.push(...customAttributeProperties); + } + return factory.createPropertyAssignment(userAttributeIdentifier, factory.createObjectLiteralExpression(userAttributeProperties, true)); +}; + +export function renderAuthNode(definition: AuthDefinition): ts.NodeArray { + const namedImports: { [importedPackageName: string]: Set } = { '@aws-amplify/backend': new Set() }; + const refAuth = definition.referenceAuth; + if (refAuth) { + const referenceAuthProperties: Array = []; + namedImports['@aws-amplify/backend'].add('referenceAuth'); + for (const [key, value] of Object.entries(refAuth)) { + if (value) { + referenceAuthProperties.push( + factory.createPropertyAssignment( + factory.createIdentifier(key), + typeof value === 'object' + ? factory.createObjectLiteralExpression( + Object.entries(value).map(([_key, _value]) => + factory.createPropertyAssignment(factory.createStringLiteral(_key), factory.createStringLiteral(_value)), + ), + true, + ) + : factory.createStringLiteral(value), + ), + ); + } + } + return renderResourceTsFile({ + exportedVariableName: factory.createIdentifier('auth'), + functionCallParameter: factory.createObjectLiteralExpression(referenceAuthProperties, true), + additionalImportedBackendIdentifiers: namedImports, + backendFunctionConstruct: 'referenceAuth', + }); + } + + namedImports['@aws-amplify/backend'].add('defineAuth'); + const defineAuthProperties: Array = []; + const secretErrors: ts.Node[] = []; + + const logInWithPropertyAssignment = createLogInWithPropertyAssignment(definition.loginOptions, secretErrors); + defineAuthProperties.push(logInWithPropertyAssignment); + + if (definition.customUserAttributes || definition.standardUserAttributes) { + defineAuthProperties.push(createUserAttributeAssignments(definition.standardUserAttributes, definition.customUserAttributes)); + } + + if (definition.groups?.length) { + defineAuthProperties.push( + factory.createPropertyAssignment( + factory.createIdentifier('groups'), + factory.createArrayLiteralExpression(definition.groups.map((g) => factory.createStringLiteral(g))), + ), + ); + } + const hasFunctions = definition.lambdaTriggers && Object.keys(definition.lambdaTriggers).length > 0; + const { loginOptions } = definition; + if ( + loginOptions?.appleLogin || + loginOptions?.amazonLogin || + loginOptions?.googleLogin || + loginOptions?.facebookLogin || + (loginOptions?.oidcLogin && loginOptions.oidcLogin.length > 0) || + loginOptions?.samlLogin + ) { + namedImports['@aws-amplify/backend'].add('secret'); + } + if (hasFunctions) { + assert(definition.lambdaTriggers); + defineAuthProperties.push(createTriggersProperty(definition.lambdaTriggers)); + for (const value of Object.values(definition.lambdaTriggers)) { + const functionName = value.source.split('/')[3]; + if (!namedImports[`./${functionName}/resource`]) { + namedImports[`./${functionName}/resource`] = new Set(); + } + namedImports[`./${functionName}/resource`].add(functionName); + } + } + if (definition.mfa) { + const multifactorProperties = [ + factory.createPropertyAssignment(factory.createIdentifier('mode'), factory.createStringLiteral(definition.mfa.mode)), + ]; + + if (definition.mfa.totp !== undefined) { + multifactorProperties.push( + factory.createPropertyAssignment( + factory.createIdentifier('totp'), + definition.mfa.totp ? factory.createTrue() : factory.createFalse(), + ), + ); + } + + if (definition.mfa.sms !== undefined) { + multifactorProperties.push( + factory.createPropertyAssignment( + factory.createIdentifier('sms'), + definition.mfa.sms ? factory.createTrue() : factory.createFalse(), + ), + ); + } + + defineAuthProperties.push( + factory.createPropertyAssignment( + factory.createIdentifier('multifactor'), + factory.createObjectLiteralExpression(multifactorProperties, true), + ), + ); + } + + return renderResourceTsFile({ + exportedVariableName: factory.createIdentifier('auth'), + functionCallParameter: factory.createObjectLiteralExpression(defineAuthProperties, true), + additionalImportedBackendIdentifiers: namedImports, + backendFunctionConstruct: 'defineAuth', + postImportStatements: secretErrors, + }); +} diff --git a/packages/amplify-gen2-codegen/src/backend/synthesizer.test.ts b/packages/amplify-gen2-codegen/src/backend/synthesizer.test.ts new file mode 100644 index 00000000000..589bdcb9af6 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/backend/synthesizer.test.ts @@ -0,0 +1,554 @@ +import assert from 'node:assert'; +import { BackendSynthesizer } from './synthesizer'; +import { printNodeArray } from '../test_utils/ts_node_printer'; +import { getImportRegex } from '../test_utils/import_regex'; +import { PolicyOverrides } from '../auth/source_builder'; + +describe('BackendRenderer', () => { + describe('overrides', () => { + describe('user pool', () => { + describe('no overrides present', () => { + it('does not render cfnUserPool accessor', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + }, + }); + const output = printNodeArray(rendered); + assert(!output.includes('cfnUserPool')); + }); + }); + const testCases: PolicyOverrides = { + 'Policies.PasswordPolicy.MinimumLength': 32, + 'Policies.PasswordPolicy.RequireNumbers': true, + 'Policies.PasswordPolicy.RequireSymbols': false, + 'Policies.PasswordPolicy.RequireLowercase': true, + 'Policies.PasswordPolicy.RequireUppercase': false, + 'Policies.PasswordPolicy.TemporaryPasswordValidityDays': 10, + userPoolName: 'Test_Name-dev', + userNameAttributes: undefined, + }; + const mappedPolicyType: Record = { + MinimumLength: 'minimumLength', + RequireUppercase: 'requireUppercase', + RequireLowercase: 'requireLowercase', + RequireNumbers: 'requireNumbers', + RequireSymbols: 'requireSymbols', + PasswordHistorySize: 'passwordHistorySize', + TemporaryPasswordValidityDays: 'temporaryPasswordValidityDays', + }; + for (const [key, value] of Object.entries(testCases)) { + it(`renders override for ${key}`, () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + userPoolOverrides: { + [key]: value, + }, + }, + }); + const output = printNodeArray(rendered); + if (key.includes('userPoolName')) { + assert(value); + assert(typeof value === 'string'); + assert(output.includes('cfnUserPool.userPoolName = `Test_Name-${AMPLIFY_GEN_1_ENV_NAME}`')); + } else if (key.includes('PasswordPolicy')) { + const policyKey = key.split('.')[2]; + if (value !== undefined && policyKey in mappedPolicyType) { + if (typeof value === 'string') assert(output.includes(`cfnUserPool.policies = {passwordPolicy:{${policyKey}:"${value}"}}`)); + } else if (typeof value === 'number') { + assert(output.includes(`cfnUserPool.policies = {passwordPolicy:{${policyKey}:${value}}}`)); + } else if (typeof value === 'boolean') { + assert(output.includes(`cfnUserPool.policies = {passwordPolicy:{${policyKey}:${value}}}`)); + } + } else if (value === undefined) { + assert(output.includes(`cfnUserPool.${key} = ${value}`)); + } else { + assert(output.includes(`cfnUserPool.${key} = "${value}"`)); + } + }); + } + it('renders multiple overrides', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + userPoolOverrides: testCases, + }, + }); + const output = printNodeArray(rendered); + for (const [key, value] of Object.entries(testCases)) { + if (key.includes('userPoolName')) { + assert(value); + assert(typeof value === 'string'); + assert(output.includes('cfnUserPool.userPoolName = `Test_Name-${AMPLIFY_GEN_1_ENV_NAME}`')); + } else if (key.includes('PasswordPolicy')) { + const policyKey = key.split('.')[2]; + if (value !== undefined && policyKey in mappedPolicyType) { + if (typeof value === 'string') assert(output.includes(`cfnUserPool.policies = {passwordPolicy:{${policyKey}:"${value}"}}`)); + } else if (typeof value === 'number') { + assert(output.includes(`cfnUserPool.policies = {passwordPolicy:{${policyKey}:${value}}}`)); + } else if (typeof value === 'boolean') { + assert(output.includes(`cfnUserPool.policies = {passwordPolicy:{${policyKey}:${value}}}`)); + } + } else if (value) { + assert(output.includes(`cfnUserPool.${key} = "${value}"`)); + } + } + }); + }); + }); + describe('guestLogin', () => { + it('Renders cfnIdentityPool accessor if guestLogin is false', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + guestLogin: false, + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('cfnIdentityPool')); + }); + it('Does not render cfnIdentityPool accessor if guestLogin is true', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + guestLogin: true, + }, + }); + const output = printNodeArray(rendered); + assert(!output.includes('cfnIdentityPool')); + }); + }); + describe('Identity Pool Name', () => { + it('Renders cfnIdentityPool accessor if identityPoolName is defined', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + identityPoolName: 'Test_Name_dev', + guestLogin: true, + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('cfnIdentityPool.identityPoolName = `Test_Name_${AMPLIFY_GEN_1_ENV_NAME}`')); + }); + it('Does not render cfnIdentityPool accessor if identityPoolName is undefined and guestLogin is true', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + guestLogin: true, + }, + }); + const output = printNodeArray(rendered); + assert(!output.includes('cfnIdentityPool')); + }); + }); + describe('OAuth Flows', () => { + it('Renders cfnUserPoolClient accessor if oAuthFlows is defined', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + oAuthFlows: ['code'], + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('cfnUserPoolClient')); + }); + it('Does not render cfnUserPoolClient accessor if oAuthFlows is undefined', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + }, + }); + const output = printNodeArray(rendered); + assert(!output.includes('cfnUserPoolClient')); + }); + }); + describe('errors for unsupported categories', () => { + it('Renders error statement if unsupported categories are present', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + unsupportedCategories: new Map([ + ['rest api', 'https://docs.amplify.aws/react/build-a-backend/add-aws-services/rest-api/'], + ['geo', 'https://docs.amplify.aws/react/build-a-backend/add-aws-services/geo/'], + ['predictions', 'https://docs.amplify.aws/react/build-a-backend/add-aws-services/predictions/'], + ]), + }); + const output = printNodeArray(rendered); + assert( + output.includes( + 'throw new Error("Category rest api is unsupported, please follow https://docs.amplify.aws/react/build-a-backend/add-aws-services/rest-api/")', + ), + ); + assert( + output.includes( + 'throw new Error("Category geo is unsupported, please follow https://docs.amplify.aws/react/build-a-backend/add-aws-services/geo/")', + ), + ); + assert( + output.includes( + 'throw new Error("Category predictions is unsupported, please follow https://docs.amplify.aws/react/build-a-backend/add-aws-services/predictions/")', + ), + ); + }); + }); + describe('imports', () => { + for (const resource of ['storage', 'data', 'auth']) { + describe(resource, () => { + const importFrom = './my-test/path'; + const importRegex = new RegExp(`import \\{ ${resource} \\} from "${importFrom}"`); + it(`does not import ${resource} if no ${resource} key is passed`, () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({}); + const source = printNodeArray(rendered); + assert.doesNotMatch(source, importRegex); + }); + it(`imports ${resource}`, () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ [resource]: { importFrom, hasS3Bucket: 'bucket_name', bucketName: 'testBucket' } }); + const source = printNodeArray(rendered); + assert.match(source, importRegex); + }); + }); + } + }); + describe('defineBackend invocation', () => { + describe('storage', () => { + it('does not define storage property if it is undefined', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({}); + const output = printNodeArray(rendered); + assert(!output.includes('storage: storage')); + }); + it('adds property assignment when defined', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + storage: { + importFrom: 'my-storage', + hasS3Bucket: 'bucket_name', + bucketName: 'testBucket', + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('storage')); + }); + }); + describe('auth', () => { + it('does not define auth property if it is undefined', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({}); + const output = printNodeArray(rendered); + assert(!output.includes('storage')); + }); + it('adds property assignment when defined', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: 'my-auth', + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('auth')); + }); + }); + }); + describe('imports', () => { + describe('defineBackend', () => { + it('imports defineBackend from "@aws-amplify/backend"', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({}); + const output = printNodeArray(rendered); + const regex = getImportRegex('defineBackend', '@aws-amplify/backend'); + assert.match(output, regex); + }); + }); + describe('storage', () => { + it('imports storage from the specified import path', () => { + const storageImportLocation = 'storage/resource.ts'; + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + storage: { + importFrom: storageImportLocation, + hasS3Bucket: 'bucket_name', + bucketName: 'testBucket', + }, + }); + const output = printNodeArray(rendered); + const regex = getImportRegex('storage', storageImportLocation); + assert.match(output, regex); + }); + }); + describe('auth', () => { + it('imports auth from the specified import path', () => { + const authImportLocation = 'auth/resource.ts'; + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { importFrom: authImportLocation }, + }); + const output = printNodeArray(rendered); + const regex = getImportRegex('auth', authImportLocation); + assert.match(output, regex); + }); + }); + describe('data', () => { + it('imports data from the specified import path', () => { + const dataImportLocation = 'data/resource.ts'; + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + data: { importFrom: dataImportLocation }, + }); + const output = printNodeArray(rendered); + const regex = getImportRegex('data', dataImportLocation); + assert.match(output, regex); + expect(output).not.toContain('// Tags.of(backend.stack).add("gen1-migrated-app", "true")'); + }); + }); + }); + describe('renders storage overrides', () => { + it('renders s3 bucket name', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + storage: { + importFrom: 'my-storage', + bucketName: 'testBucket', + hasS3Bucket: 'bucket-name', + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('bucketName')); + }); + it('renders s3 bucket encryption algorithm', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + storage: { + importFrom: 'my-storage', + bucketName: 'testBucket', + hasS3Bucket: 'bucket-name', + bucketEncryptionAlgorithm: { + serverSideEncryptionByDefault: { + SSEAlgorithm: 'AES256', + KMSMasterKeyID: 'key-id', + }, + bucketKeyEnabled: true, + }, + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('bucketEncryption')); + assert(output.includes('sseAlgorithm')); + }); + }); + describe('UserPoolClient Configuration using render()', () => { + it('renders complete user pool client configuration', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + userPoolClient: { + UserPoolId: 'us-west-2_aaaaaaaaa', + ClientName: 'MyApp', + ClientId: '38fjsnc484p94kpqsnet7mpld0', + ClientSecret: 'CLIENT_SECRET', + RefreshTokenValidity: 30, + AccessTokenValidity: 79, + ReadAttributes: [ + 'address', + 'birthdate', + 'custom:CustomAttr1', + 'custom:CustomAttr2', + 'email', + 'email_verified', + 'family_name', + 'gender', + 'given_name', + 'locale', + 'middle_name', + 'name', + 'nickname', + 'phone_number', + 'phone_number_verified', + 'picture', + 'preferred_username', + 'profile', + 'updated_at', + 'website', + 'zoneinfo', + ], + WriteAttributes: [ + 'address', + 'birthdate', + 'custom:CustomAttr1', + 'custom:CustomAttr2', + 'email', + 'family_name', + 'gender', + 'given_name', + 'locale', + 'middle_name', + 'name', + 'nickname', + 'phone_number', + 'picture', + 'preferred_username', + 'profile', + 'updated_at', + 'website', + 'zoneinfo', + ], + ExplicitAuthFlows: ['ADMIN_NO_SRP_AUTH', 'USER_PASSWORD_AUTH'], + AllowedOAuthFlowsUserPoolClient: true, + AllowedOAuthFlows: ['code', 'implicit'], + AllowedOAuthScopes: [ + 'phone', + 'email', + 'openid', + 'profile', + 'aws.cognito.signin.user.admin', + 'custom:CustomScope1', + 'custom:CustomScope2', + ], + CallbackURLs: ['XXXXXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXX'], + LogoutURLs: ['XXXXXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXXXXXXXXX'], + DefaultRedirectURI: 'XXXXXXXXXXXXXXXXXX', + SupportedIdentityProviders: ['COGNITO', 'Facebook', 'LoginWithAmazon', 'SignInWithApple'], + AuthSessionValidity: 3, + EnableTokenRevocation: true, + EnablePropagateAdditionalUserContextData: true, + TokenValidityUnits: { + RefreshToken: 'hours', + AccessToken: 'minutes', + IdToken: 'minutes', + }, + }, + }, + }); + + const output = printNodeArray(rendered); + + // Basic configuration + expect(output).toContain('NativeAppClient'); + expect(output).toContain('userPoolClientName: "MyApp"'); + + // Token validity settings + expect(output).toContain('refreshTokenValidity: Duration.hours(30),'); + expect(output).toContain('accessTokenValidity: Duration.minutes(79)'); + + // Attributes + expect(output).toContain('readAttributes: new ClientAttributes().withStandardAttributes'); + expect(output).toContain('writeAttributes: new ClientAttributes().withStandardAttributes'); + expect(output).toContain('custom:CustomAttr1'); + + // OAuth configuration + expect(output).toContain('flows: { authorizationCodeGrant: true, implicitCodeGrant: true, clientCredentials: false }'); + + // OAuth scopes + expect(output).toContain('OAuthScope.PHONE'); + expect(output).toContain('OAuthScope.EMAIL'); + expect(output).toContain('OAuthScope.OPENID'); + + // URLs + expect(output).toContain('callbackUrls'); + expect(output).toContain('logoutUrls'); + expect(output).toContain('defaultRedirectUri'); + + // Auth flows + expect(output).toContain('authFlows: { adminUserPassword: false, custom: false, userPassword: false, userSrp: false }'); + + // Additional settings + expect(output).toContain(`supportedIdentityProviders`); + expect(output).toContain(`UserPoolClientIdentityProvider.COGNITO`); + expect(output).toContain(`UserPoolClientIdentityProvider.FACEBOOK`); + expect(output).toContain(`UserPoolClientIdentityProvider.AMAZON`); + expect(output).toContain(`UserPoolClientIdentityProvider.APPLE`); + expect(output).toContain('authSessionValidity: Duration.minutes(3)'); + expect(output).toContain('enableTokenRevocation: true'); + expect(output).toContain('enablePropagateAdditionalUserContextData: true'); + expect(output).toContain('generateSecret: true'); + + expect(output).toContain( + 'const providerSetupResult = (backend.auth.stack.node.children.find(child => child.node.id === "amplifyAuth") as any).providerSetupResult;', + ); + expect(output).toContain('Object.keys(providerSetupResult).forEach(provider => {'); + expect(output).toContain('userPoolClient.node.addDependency(providerSetupPropertyValue)'); + expect(output).toContain('// backend.auth.resources.userPool.node.tryRemoveChild("UserPoolDomain");'); + }); + it('renders user pool client configuration with default value for generateSecrets', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + userPoolClient: { + UserPoolId: 'us-west-2_aaaaaaaaa', + ClientName: 'MyApp', + ClientId: '38fjsnc484p94kpqsnet7mpld0', + }, + }, + }); + + const output = printNodeArray(rendered); + + // Basic configuration + expect(output).toContain('NativeAppClient'); + expect(output).toContain('userPoolClientName: "MyApp"'); + + // Additional settings + expect(output).toContain(`generateSecret: false\n});`); + }); + }); + describe('environment variables', () => { + it('renders dynamic environment variables', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + auth: { + importFrom: './auth/resource.ts', + }, + }); + const output = printNodeArray(rendered); + assert(output.includes('process.env.AMPLIFY_GEN_1_ENV_NAME')); + assert(output.includes('ci.isCI && !AMPLIFY_GEN_1_ENV_NAME')); + assert(output.includes('!ci.isCI && !AMPLIFY_GEN_1_ENV_NAME')); + assert(output.includes('throw new Error("AMPLIFY_GEN_1_ENV_NAME is required in CI environment")')); + assert(output.includes('AMPLIFY_GEN_1_ENV_NAME = "sandbox"')); + }); + }); + describe('Custom resources are rendered()', () => { + it('renders custom resources', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({ + customResources: new Map([ + ['resource1', 'resource1Value'], + ['resource2', 'resource2Value'], + ]), + }); + + const output = printNodeArray(rendered); + + const normalizedOutput = output.replace(/\s+/g, ' ').trim(); + + expect(normalizedOutput).toContain( + `new resource1(backend.stack, "resource1", undefined, { category: "custom", resourceName: "resource1" });`, + ); + expect(normalizedOutput).toContain( + `new resource2(backend.stack, "resource2", undefined, { category: "custom", resourceName: "resource2" });`, + ); + }); + + it('does not render custom resources when none are provided', () => { + const renderer = new BackendSynthesizer(); + const rendered = renderer.render({}); + + const output = printNodeArray(rendered); + + expect(output).not.toContain('new resource1'); + expect(output).not.toContain('category: "custom"'); + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/backend/synthesizer.ts b/packages/amplify-gen2-codegen/src/backend/synthesizer.ts new file mode 100644 index 00000000000..1d21f4b9de3 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/backend/synthesizer.ts @@ -0,0 +1,1069 @@ +import ts, { + CallExpression, + Expression, + ExpressionStatement, + Identifier, + ImportDeclaration, + Node, + NodeArray, + VariableDeclaration, + VariableStatement, +} from 'typescript'; +import { PolicyOverrides, ReferenceAuth } from '../auth/source_builder.js'; +import { BucketAccelerateStatus, BucketVersioningStatus } from '@aws-sdk/client-s3'; +import { AccessPatterns, ServerSideEncryptionConfiguration } from '../storage/source_builder.js'; +import { ExplicitAuthFlowsType, OAuthFlowType, UserPoolClientType } from '@aws-sdk/client-cognito-identity-provider'; +import assert from 'assert'; +import { newLineIdentifier } from '../ts_factory_utils'; + +const factory = ts.factory; +export interface BackendRenderParameters { + data?: { + importFrom: string; + }; + auth?: { + importFrom: string; + userPoolOverrides?: PolicyOverrides; + guestLogin?: boolean; + identityPoolName?: string; + oAuthFlows?: string[]; + readAttributes?: string[]; + writeAttributes?: string[]; + referenceAuth?: ReferenceAuth; + userPoolClient?: UserPoolClientType; + }; + storage?: { + importFrom: string; + dynamoDB?: string; + accelerateConfiguration?: BucketAccelerateStatus; + versionConfiguration?: BucketVersioningStatus; + hasS3Bucket?: string | AccessPatterns | undefined; + bucketEncryptionAlgorithm?: ServerSideEncryptionConfiguration; + bucketName?: string; + }; + + function?: { + importFrom: string; + functionNamesAndCategories: Map; + }; + customResources?: Map; + unsupportedCategories?: Map; +} + +const amplifyGen1EnvName = 'AMPLIFY_GEN_1_ENV_NAME'; + +export class BackendSynthesizer { + private importDurationFlag = false; + private oAuthFlag = false; + private readWriteAttributeFlag = false; + private supportedIdentityProviderFlag = false; + + private createPropertyAccessExpression(objectIdentifier: Identifier, propertyPath: string): Expression { + const parts = propertyPath.split('.'); + let expression: Expression = objectIdentifier; + for (let i = 0; i < parts.length; i++) { + expression = factory.createPropertyAccessExpression(expression, factory.createIdentifier(parts[i])); + } + return expression; + } + + private createVariableDeclaration(identifierName: string, propertyPath: string): VariableDeclaration { + const identifier = factory.createIdentifier(identifierName); + const propertyAccessExpression = this.createPropertyAccessExpression(factory.createIdentifier('backend'), propertyPath); + return factory.createVariableDeclaration(identifier, undefined, undefined, propertyAccessExpression); + } + + private createVariableStatement(variableDeclaration: VariableDeclaration): VariableStatement { + return factory.createVariableStatement([], factory.createVariableDeclarationList([variableDeclaration], ts.NodeFlags.Const)); + } + + private createImportStatement(identifiers: Identifier[], backendPackageName: string): ImportDeclaration { + return factory.createImportDeclaration( + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports(identifiers.map((identifier) => factory.createImportSpecifier(false, undefined, identifier))), + ), + factory.createStringLiteral(backendPackageName), + ); + } + + private defineBackendCall(backendFunctionIdentifier: Identifier, properties: ts.ObjectLiteralElementLike[]): CallExpression { + const backendFunctionArgs = factory.createObjectLiteralExpression(properties, true); + return factory.createCallExpression(backendFunctionIdentifier, undefined, [backendFunctionArgs]); + } + + private setPropertyValue( + objectIdentifier: Identifier, + propertyPath: string, + value: number | string | boolean | string[] | object | undefined, + ): ExpressionStatement { + const propertyAccessExpression = this.createPropertyAccessExpression(objectIdentifier, propertyPath); + const overrideValue = this.getOverrideValue(value); + + return factory.createExpressionStatement(factory.createAssignment(propertyAccessExpression, overrideValue)); + } + + private getOverrideValue(value: number | string | boolean | string[] | object | undefined): Expression { + if (typeof value === 'number') { + return factory.createNumericLiteral(value); + } else if (typeof value === 'string') { + return factory.createStringLiteral(value); + } else if (Array.isArray(value) && value.every((item) => typeof item === 'string')) { + return factory.createArrayLiteralExpression(value.map((item) => factory.createStringLiteral(item))); + } else if (typeof value === 'boolean') { + return value ? factory.createTrue() : factory.createFalse(); + } else if (typeof value === 'object' && value !== null) { + const properties: ts.PropertyAssignment[] = []; + for (const [key, val] of Object.entries(value)) { + const property = factory.createPropertyAssignment(factory.createIdentifier(key), this.getOverrideValue(val)); + properties.push(property); + } + return factory.createObjectLiteralExpression(properties, true); + } else if (value === undefined) { + return factory.createIdentifier('undefined'); + } + throw new TypeError(`Unrecognized type: ${typeof value}`); + } + + private createBooleanPropertyAssignment(identifier: string, condition: boolean) { + return factory.createPropertyAssignment(factory.createIdentifier(identifier), condition ? factory.createTrue() : factory.createFalse()); + } + + private createListPropertyAssignment(identifier: string, listAttribute: string[]) { + return factory.createPropertyAssignment( + factory.createIdentifier(identifier), + factory.createArrayLiteralExpression(listAttribute.map((attribute) => factory.createStringLiteral(attribute))), + ); + } + + private createEnumListPropertyAssignment(identifier: string, enumIdentifier: string, listAttribute: string[]) { + return factory.createPropertyAssignment( + factory.createIdentifier(identifier), + factory.createArrayLiteralExpression( + listAttribute.map((attribute) => + factory.createPropertyAccessExpression(factory.createIdentifier(enumIdentifier), factory.createIdentifier(attribute)), + ), + true, + ), + ); + } + + private createNumericPropertyAssignment(identifier: string, numericLiteral: number) { + return factory.createPropertyAssignment(factory.createIdentifier(identifier), factory.createNumericLiteral(numericLiteral)); + } + + private createDurationPropertyAssignment(identifier: string, numericLiteral: number, durationUnit: string) { + const duration = factory.createCallExpression(factory.createIdentifier(`Duration.${durationUnit}`), undefined, [ + factory.createNumericLiteral(numericLiteral), + ]); + return factory.createPropertyAssignment(factory.createIdentifier(identifier), duration); + } + + private createStringPropertyAssignment(identifier: string, stringLiteral: string) { + return factory.createPropertyAssignment(factory.createIdentifier(identifier), factory.createStringLiteral(stringLiteral)); + } + + private createUserPoolClientAssignment(userPoolClient: UserPoolClientType, imports: ts.ImportDeclaration[]) { + const userPoolClientAttributesMap = new Map(); + userPoolClientAttributesMap.set('ClientName', 'userPoolClientName'); + userPoolClientAttributesMap.set('ClientSecret', 'generateSecret'); + userPoolClientAttributesMap.set('ReadAttributes', 'readAttributes'); + userPoolClientAttributesMap.set('WriteAttributes', 'writeAttributes'); + userPoolClientAttributesMap.set('RefreshTokenValidity', 'refreshTokenValidity'); + userPoolClientAttributesMap.set('AccessTokenValidity', 'accessTokenValidity'); + userPoolClientAttributesMap.set('IdTokenValidity', 'idTokenValidity'); + userPoolClientAttributesMap.set('RefreshToken', 'refreshToken'); + userPoolClientAttributesMap.set('AccessToken', 'accessToken'); + userPoolClientAttributesMap.set('IdToken', 'idToken'); + userPoolClientAttributesMap.set('AllowedOAuthScopes', 'scopes'); + userPoolClientAttributesMap.set('CallbackURLs', 'callbackUrls'); + userPoolClientAttributesMap.set('LogoutURLs', 'logoutUrls'); + userPoolClientAttributesMap.set('DefaultRedirectURI', 'defaultRedirectUri'); + userPoolClientAttributesMap.set('AllowedOAuthFlowsUserPoolClient', 'disableOAuth'); + userPoolClientAttributesMap.set('EnableTokenRevocation', 'enableTokenRevocation'); + userPoolClientAttributesMap.set('EnablePropagateAdditionalUserContextData', 'enablePropagateAdditionalUserContextData'); + userPoolClientAttributesMap.set('SupportedIdentityProviders', 'supportedIdentityProviders'); + userPoolClientAttributesMap.set('AuthSessionValidity', 'authSessionValidity'); + userPoolClientAttributesMap.set('ExplicitAuthFlows', 'authFlows'); + userPoolClientAttributesMap.set('AllowedOAuthFlows', 'flows'); + + const userPoolClientDeclaration = factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('userPoolClient'), + undefined, + undefined, + factory.createCallExpression( + factory.createPropertyAccessExpression(factory.createIdentifier('userPool'), factory.createIdentifier('addClient')), + undefined, + [ + factory.createStringLiteral('NativeAppClient'), + this.createNestedObjectExpression(userPoolClient, userPoolClientAttributesMap), + ], + ), + ), + ], + ts.NodeFlags.Const, + ), + ); + + if (this.importDurationFlag) { + imports.push(this.createImportStatement([factory.createIdentifier('Duration')], 'aws-cdk-lib')); + } + + if (this.readWriteAttributeFlag || this.oAuthFlag || this.supportedIdentityProviderFlag) { + const identifiers = [ + ...(this.readWriteAttributeFlag ? [factory.createIdentifier('ClientAttributes')] : []), + ...(this.oAuthFlag ? [factory.createIdentifier('OAuthScope')] : []), + ...(this.supportedIdentityProviderFlag ? [factory.createIdentifier('UserPoolClientIdentityProvider')] : []), + ]; + + if (identifiers.length > 0) { + imports.push(this.createImportStatement(identifiers, 'aws-cdk-lib/aws-cognito')); + } + } + + return userPoolClientDeclaration; + } + + private createPropertyAccessChain(identifiers: string[]): ts.Expression { + return identifiers + .slice(1) + .reduce( + (acc, curr) => factory.createPropertyAccessExpression(acc, factory.createIdentifier(curr)), + factory.createIdentifier(identifiers[0]), + ); + } + + private getProviderSetupDeclaration(): ts.VariableStatement { + const providerSetupResult = 'providerSetupResult'; + return factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier(providerSetupResult), + undefined, + undefined, + factory.createPropertyAccessExpression( + factory.createParenthesizedExpression( + factory.createAsExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + this.createPropertyAccessChain(['backend', 'auth', 'stack', 'node', 'children']), + factory.createIdentifier('find'), + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('child'))], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createBinaryExpression( + this.createPropertyAccessChain(['child', 'node', 'id']), + factory.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken), + factory.createStringLiteral('amplifyAuth'), + ), + ), + ], + ), + factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + ), + ), + factory.createIdentifier(providerSetupResult), + ), + ), + ], + ts.NodeFlags.Const, + ), + ); + } + + private getProviderSetupForeachStatement(): ExpressionStatement { + const providerSetupResult = 'providerSetupResult'; + return factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression(factory.createIdentifier('Object'), factory.createIdentifier('keys')), + undefined, + [factory.createIdentifier(providerSetupResult)], + ), + factory.createIdentifier('forEach'), + ), + undefined, + [ + factory.createArrowFunction( + undefined, + undefined, + [factory.createParameterDeclaration(undefined, undefined, factory.createIdentifier('provider'))], + undefined, + factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + factory.createBlock( + [ + // const providerSetupPropertyValue = providerSetupResult[provider] + factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('providerSetupPropertyValue'), + undefined, + undefined, + factory.createElementAccessExpression( + factory.createIdentifier(providerSetupResult), + factory.createIdentifier('provider'), + ), + ), + ], + ts.NodeFlags.Const, + ), + ), + // if condition + factory.createIfStatement( + factory.createLogicalAnd( + factory.createPropertyAccessExpression( + factory.createIdentifier('providerSetupPropertyValue'), + factory.createIdentifier('node'), + ), + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createCallExpression( + factory.createPropertyAccessExpression( + this.createPropertyAccessChain(['providerSetupPropertyValue', 'node', 'id']), + factory.createIdentifier('toLowerCase'), + ), + undefined, + [], + ), + factory.createIdentifier('endsWith'), + ), + undefined, + [factory.createStringLiteral('idp')], + ), + ), + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createCallExpression( + this.createPropertyAccessChain(['userPoolClient', 'node', 'addDependency']), + undefined, + [factory.createIdentifier('providerSetupPropertyValue')], + ), + ), + ], + true, + ), + ), + ], + true, + ), + ), + ], + ), + ); + } + + private createProviderSetupCode(): ts.Statement[] { + // Create const providerSetupResult = (backend.auth.stack.node.children.find(child => child.node.id === "amplifyAuth") as any).providerSetupResult; + const providerSetupDeclaration = this.getProviderSetupDeclaration(); + + // Create Object.keys(providerSetupResult).forEach(...) + const forEachStatement = this.getProviderSetupForeachStatement(); + + return [providerSetupDeclaration, forEachStatement]; + } + + private createNestedObjectExpression(object: any, gen2PropertyMap: Map): ts.ObjectLiteralExpression { + const objectLiterals = []; + const clientSecretKey = 'ClientSecret'; + + for (const [key, value] of Object.entries(object)) { + const mappedProperty = gen2PropertyMap.get(key); + if (mappedProperty) { + if (typeof value == 'boolean') { + if (key === 'AllowedOAuthFlowsUserPoolClient') { + // CDK equivalent is disableOAuth which is opposite of this prop + objectLiterals.push(this.createBooleanPropertyAssignment(mappedProperty, !value)); + } else { + objectLiterals.push(this.createBooleanPropertyAssignment(mappedProperty, value)); + } + } else if (typeof value == 'string') { + if (!this.oAuthFlag && key == 'DefaultRedirectURI') { + this.oAuthFlag = true; + objectLiterals.push(this.createOAuthObjectExpression(object, gen2PropertyMap)); + } else if (key === clientSecretKey) { + objectLiterals.push(this.createBooleanPropertyAssignment(mappedProperty, true)); + } else if (key != 'DefaultRedirectURI') { + objectLiterals.push(this.createStringPropertyAssignment(mappedProperty, value)); + } + } else if (typeof value == 'number') { + if (['IdTokenValidity', 'RefreshTokenValidity', 'AccessTokenValidity', 'AuthSessionValidity'].includes(key)) { + // convert it to Duration + this.importDurationFlag = true; + if (key == 'IdTokenValidity') { + let durationUnit = 'hours'; + if (object['TokenValidityUnits'] && object['TokenValidityUnits'].IdToken) { + durationUnit = object['TokenValidityUnits'].IdToken; + } + objectLiterals.push(this.createDurationPropertyAssignment(mappedProperty, value, durationUnit)); + } else if (key == 'RefreshTokenValidity') { + let durationUnit = 'days'; + if (object['TokenValidityUnits'] && object['TokenValidityUnits'].RefreshToken) { + durationUnit = object['TokenValidityUnits'].RefreshToken; + } + objectLiterals.push(this.createDurationPropertyAssignment(mappedProperty, value, durationUnit)); + } else if (key == 'AccessTokenValidity') { + let durationUnit = 'hours'; + if (object['TokenValidityUnits'] && object['TokenValidityUnits'].AccessToken) { + durationUnit = object['TokenValidityUnits'].AccessToken; + } + objectLiterals.push(this.createDurationPropertyAssignment(mappedProperty, value, durationUnit)); + } else if (key == 'AuthSessionValidity') { + objectLiterals.push(this.createDurationPropertyAssignment(mappedProperty, value, 'minutes')); + } + } else { + objectLiterals.push(this.createNumericPropertyAssignment(mappedProperty, value)); + } + } else if (Array.isArray(value) && gen2PropertyMap.has(key)) { + if (key == 'ReadAttributes' || key == 'WriteAttributes') { + objectLiterals.push(this.createReadWriteAttributes(mappedProperty, value)); + } else if (key == 'SupportedIdentityProviders') { + this.supportedIdentityProviderFlag = true; + // Providers are upper case in CDK + objectLiterals.push( + this.createEnumListPropertyAssignment( + mappedProperty, + 'UserPoolClientIdentityProvider', + value.map((provider) => { + if (provider.toUpperCase() == 'LOGINWITHAMAZON') { + return 'AMAZON'; + } else if (provider.toUpperCase() === 'SIGNINWITHAPPLE') { + return 'APPLE'; + } + return provider.toUpperCase(); + }), + ), + ); + } else if (!this.oAuthFlag && key == 'AllowedOAuthFlows') { + this.oAuthFlag = true; + objectLiterals.push(this.createOAuthObjectExpression(object, gen2PropertyMap)); + } else if (key == 'ExplicitAuthFlows') { + objectLiterals.push( + factory.createPropertyAssignment(factory.createIdentifier(mappedProperty), this.createAuthFlowsObjectExpression(value)), + ); + } else if (!this.oAuthFlag && key == 'AllowedOAuthScopes') { + this.oAuthFlag = true; + objectLiterals.push(this.createOAuthObjectExpression(object, gen2PropertyMap)); + } else { + if (!this.oAuthFlag && (key == 'CallbackURLs' || key == 'LogoutURLs')) { + this.oAuthFlag = true; + objectLiterals.push(this.createOAuthObjectExpression(object, gen2PropertyMap)); + } else if (key != 'CallbackURLs' && key != 'LogoutURLs' && key != 'AllowedOAuthScopes') { + objectLiterals.push(this.createListPropertyAssignment(mappedProperty, value)); + } + } + } else if (typeof value == 'object' && value !== null) { + objectLiterals.push( + factory.createPropertyAssignment(factory.createIdentifier(key), this.createNestedObjectExpression(value, gen2PropertyMap)), + ); + } + } + } + // We need to set generateSecret to false explicitly when not defined. + // If it's set as undefined and current value in CFN template is false (moved from gen1 after refactor), CFN thinks the property has changed + // and requests for creation of a new resource (user pool client) instead of an update. + if (object[clientSecretKey] === undefined && gen2PropertyMap.has(clientSecretKey)) { + const mappedClientSecretKey = gen2PropertyMap.get(clientSecretKey); + assert(mappedClientSecretKey); + objectLiterals.push(this.createBooleanPropertyAssignment(mappedClientSecretKey, false)); + } + return factory.createObjectLiteralExpression(objectLiterals, true); + } + + private createReadWriteAttributes(identifier: string, attributes: string[]) { + const standardAttrMap = new Map(); + standardAttrMap.set('address', 'address'); + standardAttrMap.set('birthdate', 'birthdate'); + standardAttrMap.set('email', 'email'); + standardAttrMap.set('family_name', 'familyName'); + standardAttrMap.set('gender', 'gender'); + standardAttrMap.set('given_name', 'givenName'); + standardAttrMap.set('locale', 'locale'); + standardAttrMap.set('middle_name', 'middleName'); + standardAttrMap.set('name', 'fullname'); + standardAttrMap.set('nickname', 'nickname'); + standardAttrMap.set('phone_number', 'phoneNumber'); + standardAttrMap.set('picture', 'profilePicture'); + standardAttrMap.set('preferred_username', 'preferredUsername'); + standardAttrMap.set('profile', 'profilePage'); + standardAttrMap.set('updated_at', 'lastUpdateTime'); + standardAttrMap.set('website', 'website'); + standardAttrMap.set('zoneinfo', 'timezone'); + standardAttrMap.set('email_verified', 'emailVerified'); + standardAttrMap.set('phone_number_verified', 'phoneNumberVerified'); + + this.readWriteAttributeFlag = true; + const standardAttributes = attributes.filter((attribute) => !attribute.startsWith('custom:')); + const standardAttributesLiterals: ts.PropertyAssignment[] = []; + standardAttributes.forEach((attribute) => { + if (standardAttrMap.has(attribute)) { + const mappedAttribute = standardAttrMap.get(attribute); + if (mappedAttribute) { + standardAttributesLiterals.push( + factory.createPropertyAssignment(factory.createIdentifier(mappedAttribute), factory.createTrue()), + ); + } + } + }); + + let clientAttributes = factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('new ClientAttributes()'), + factory.createIdentifier('withStandardAttributes'), + ), + undefined, + [factory.createObjectLiteralExpression(standardAttributesLiterals, true)], + ); + + const customAttributes = attributes.filter((attribute) => attribute.startsWith('custom:')); + + if (customAttributes) { + clientAttributes = factory.createCallExpression( + factory.createPropertyAccessExpression(clientAttributes, factory.createIdentifier('withCustomAttributes')), + undefined, + customAttributes.map((attr) => factory.createStringLiteral(attr)), + ); + } + + return factory.createPropertyAssignment(factory.createIdentifier(identifier), clientAttributes); + } + + private mapOAuthScopes(scopes: string[]) { + const scopeMap = new Map(); + scopeMap.set('phone', 'PHONE'); + scopeMap.set('email', 'EMAIL'); + scopeMap.set('openid', 'OPENID'); + scopeMap.set('profile', 'PROFILE'); + + const scopesList: string[] = []; + scopes.forEach((scope) => { + if (scopeMap.has(scope)) { + const scopeValue = scopeMap.get(scope); + if (scopeValue) { + scopesList.push(scopeValue); + } + } + }); + return scopesList; + } + + private createOAuthObjectExpression(object: Record, map: Map) { + const oAuthLiterals = []; + + for (const [key, value] of Object.entries(object)) { + if (key == 'AllowedOAuthFlows') { + oAuthLiterals.push( + factory.createPropertyAssignment(factory.createIdentifier('flows'), this.createOAuthFlowsObjectExpression(value)), + ); + } else if (key == 'AllowedOAuthScopes') { + oAuthLiterals.push(this.createEnumListPropertyAssignment('scopes', 'OAuthScope', this.mapOAuthScopes(value))); + } else if (key == 'CallbackURLs' || key == 'LogoutURLs') { + const urlValue = map.get(key); + if (urlValue) { + oAuthLiterals.push(this.createListPropertyAssignment(urlValue, value)); + } + } else if (key == 'DefaultRedirectURI') { + const redirectUriValue = map.get(key); + if (redirectUriValue) { + oAuthLiterals.push(this.createStringPropertyAssignment(redirectUriValue, value)); + } + } + } + return factory.createPropertyAssignment(factory.createIdentifier('oAuth'), factory.createObjectLiteralExpression(oAuthLiterals, true)); + } + + private createOAuthFlowsObjectExpression(value: string[]) { + return factory.createObjectLiteralExpression([ + this.createBooleanPropertyAssignment('authorizationCodeGrant', value.includes(OAuthFlowType.code)), + this.createBooleanPropertyAssignment('implicitCodeGrant', value.includes(OAuthFlowType.implicit)), + this.createBooleanPropertyAssignment('clientCredentials', value.includes(OAuthFlowType.client_credentials)), + ]); + } + + private createAuthFlowsObjectExpression(value: string[]) { + return factory.createObjectLiteralExpression([ + this.createBooleanPropertyAssignment('adminUserPassword', value.includes(ExplicitAuthFlowsType.ALLOW_ADMIN_USER_PASSWORD_AUTH)), + this.createBooleanPropertyAssignment('custom', value.includes(ExplicitAuthFlowsType.ALLOW_CUSTOM_AUTH)), + this.createBooleanPropertyAssignment('userPassword', value.includes(ExplicitAuthFlowsType.ALLOW_USER_PASSWORD_AUTH)), + this.createBooleanPropertyAssignment('userSrp', value.includes(ExplicitAuthFlowsType.ALLOW_USER_SRP_AUTH)), + ]); + } + + // id1.id2 = `templateHead-${templateSpan}templateTail`; + private createTemplateLiteralExpression(id1: string, id2: string, templateHead: string, templateSpan: string, templateTail: string) { + return factory.createExpressionStatement( + factory.createAssignment( + factory.createPropertyAccessExpression(factory.createIdentifier(id1), factory.createIdentifier(id2)), + factory.createTemplateExpression(factory.createTemplateHead(templateHead), [ + factory.createTemplateSpan(factory.createIdentifier(templateSpan), factory.createTemplateTail(templateTail)), + ]), + ), + ); + } + + private createAmplifyEnvNameLogic() { + // Create: let AMPLIFY_GEN_1_ENV_NAME = process.env.AMPLIFY_GEN_1_ENV_NAME; + const variableDeclaration = factory.createVariableStatement( + undefined, + factory.createVariableDeclarationList( + [ + factory.createVariableDeclaration( + factory.createIdentifier('AMPLIFY_GEN_1_ENV_NAME'), + undefined, + undefined, + factory.createPropertyAccessExpression( + factory.createPropertyAccessExpression(factory.createIdentifier('process'), factory.createIdentifier('env')), + factory.createIdentifier('AMPLIFY_GEN_1_ENV_NAME'), + ), + ), + ], + ts.NodeFlags.Let, + ), + ); + + // Create: if (ci.isCI && !AMPLIFY_GEN_1_ENV_NAME) { ... } else if (!ci.isCI) { ... } + const ifStatement = factory.createIfStatement( + // Condition: ci.isCI && !AMPLIFY_GEN_1_ENV_NAME + factory.createLogicalAnd( + factory.createPropertyAccessExpression(factory.createIdentifier('ci'), factory.createIdentifier('isCI')), + factory.createLogicalNot(factory.createIdentifier('AMPLIFY_GEN_1_ENV_NAME')), + ), + // Then block: throw new Error('...') + factory.createBlock( + [ + factory.createThrowStatement( + factory.createNewExpression(factory.createIdentifier('Error'), undefined, [ + factory.createStringLiteral('AMPLIFY_GEN_1_ENV_NAME is required in CI environment'), + ]), + ), + ], + true, + ), + // Else block: if (!ci.isCI && !AMPLIFY_GEN_1_ENV_NAME) { ... } + factory.createIfStatement( + factory.createLogicalAnd( + factory.createLogicalNot( + factory.createPropertyAccessExpression(factory.createIdentifier('ci'), factory.createIdentifier('isCI')), + ), + factory.createLogicalNot(factory.createIdentifier('AMPLIFY_GEN_1_ENV_NAME')), + ), + // Then block: AMPLIFY_GEN_1_ENV_NAME = 'sandbox'; + factory.createBlock( + [ + factory.createExpressionStatement( + factory.createBinaryExpression( + factory.createIdentifier('AMPLIFY_GEN_1_ENV_NAME'), + factory.createToken(ts.SyntaxKind.EqualsToken), + factory.createStringLiteral('sandbox'), + ), + ), + ], + true, + ), + ), + ); + + return [variableDeclaration, ifStatement]; + } + + render(renderArgs: BackendRenderParameters): NodeArray { + const authFunctionIdentifier = factory.createIdentifier('auth'); + const storageFunctionIdentifier = factory.createIdentifier('storage'); + const dataFunctionIdentifier = factory.createIdentifier('data'); + const backendFunctionIdentifier = factory.createIdentifier('defineBackend'); + + const imports = []; + const errors = []; + const defineBackendProperties = []; + const nodes = []; + + const mappedPolicyType: Record = { + MinimumLength: 'minimumLength', + RequireUppercase: 'requireUppercase', + RequireLowercase: 'requireLowercase', + RequireNumbers: 'requireNumbers', + RequireSymbols: 'requireSymbols', + PasswordHistorySize: 'passwordHistorySize', + TemporaryPasswordValidityDays: 'temporaryPasswordValidityDays', + }; + + if (renderArgs.auth || renderArgs.storage?.hasS3Bucket || renderArgs.customResources) { + imports.push( + this.createImportStatement([factory.createIdentifier('RemovalPolicy'), factory.createIdentifier('Tags')], 'aws-cdk-lib'), + ); + } + + if (renderArgs.auth) { + imports.push(this.createImportStatement([authFunctionIdentifier], renderArgs.auth.importFrom)); + const auth = factory.createShorthandPropertyAssignment(authFunctionIdentifier); + defineBackendProperties.push(auth); + } + + if (renderArgs.data) { + imports.push(this.createImportStatement([dataFunctionIdentifier], renderArgs.data.importFrom)); + const data = factory.createShorthandPropertyAssignment(dataFunctionIdentifier); + defineBackendProperties.push(data); + } + + if (renderArgs.storage?.hasS3Bucket) { + imports.push(this.createImportStatement([storageFunctionIdentifier], renderArgs.storage.importFrom)); + const storage = factory.createShorthandPropertyAssignment(storageFunctionIdentifier); + defineBackendProperties.push(storage); + } + + if (renderArgs.function) { + const functionNameCategories = renderArgs.function.functionNamesAndCategories; + for (const [functionName, category] of functionNameCategories) { + const functionProperty = factory.createShorthandPropertyAssignment(factory.createIdentifier(functionName)); + defineBackendProperties.push(functionProperty); + imports.push(this.createImportStatement([factory.createIdentifier(functionName)], `./${category}/${functionName}/resource`)); + } + } + + if (renderArgs.storage?.dynamoDB) { + nodes.push( + factory.createThrowStatement( + factory.createNewExpression(factory.createIdentifier('Error'), undefined, [ + factory.createStringLiteral( + `DynamoDB table \`${renderArgs.storage.dynamoDB}\` is referenced in your Gen 1 backend and will need to be manually migrated to reference with CDK.`, + ), + ]), + ), + ); + } + + imports.push(this.createImportStatement([backendFunctionIdentifier], '@aws-amplify/backend')); + + if (renderArgs.unsupportedCategories) { + const categories = renderArgs.unsupportedCategories; + + for (const [key, value] of categories) { + errors.push( + factory.createCallExpression(factory.createIdentifier('throw new Error'), undefined, [ + factory.createStringLiteral(`Category ${key} is unsupported, please follow ${value}`), + ]), + ); + } + } + + if (renderArgs.customResources) { + for (const [resourceName, className] of renderArgs.customResources) { + const importStatement = factory.createImportDeclaration( + undefined, + factory.createImportClause( + false, + undefined, + factory.createNamedImports([ + factory.createImportSpecifier(false, factory.createIdentifier(`${className}`), factory.createIdentifier(`${resourceName}`)), + ]), + ), + factory.createStringLiteral(`./custom/${resourceName}/cdk-stack`), + undefined, + ); + + imports.push(importStatement); + + const customResourceExpression = factory.createNewExpression(factory.createIdentifier(`${resourceName}`), undefined, [ + factory.createPropertyAccessExpression(factory.createIdentifier('backend'), factory.createIdentifier('stack')), + factory.createStringLiteral(`${resourceName}`), + factory.createIdentifier('undefined'), + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment(factory.createIdentifier('category'), factory.createStringLiteral('custom')), + factory.createPropertyAssignment(factory.createIdentifier('resourceName'), factory.createStringLiteral(`${resourceName}`)), + ], + true, + ), + ]); + + nodes.push(factory.createExpressionStatement(customResourceExpression)); + } + } + + const ciInfoImportStatement = factory.createImportDeclaration( + undefined, + factory.createImportClause(false, factory.createIdentifier('ci'), undefined), + factory.createStringLiteral('ci-info'), + ); + + imports.push(ciInfoImportStatement); + const envNameStatements = this.createAmplifyEnvNameLogic(); + errors.push(...envNameStatements); + + const callBackendFn = this.defineBackendCall(backendFunctionIdentifier, defineBackendProperties); + const backendVariable = factory.createVariableDeclaration('backend', undefined, undefined, callBackendFn); + const backendStatement = factory.createVariableStatement( + [], + factory.createVariableDeclarationList([backendVariable], ts.NodeFlags.Const), + ); + + if (renderArgs.auth?.userPoolOverrides && !renderArgs?.auth?.referenceAuth) { + const cfnUserPoolVariableStatement = this.createVariableStatement( + this.createVariableDeclaration('cfnUserPool', 'auth.resources.cfnResources.cfnUserPool'), + ); + nodes.push(cfnUserPoolVariableStatement); + const policies: { passwordPolicy: Record } = { + passwordPolicy: {}, + }; + for (const [overridePath, value] of Object.entries(renderArgs.auth.userPoolOverrides)) { + if (overridePath.includes('userPoolName')) { + assert(value); + assert(typeof value === 'string'); + const splitUserPoolName = value.split('-'); + const userPoolWithoutBackendEnvName = splitUserPoolName.slice(0, -1).join('-'); + + const userPoolAssignment = this.createTemplateLiteralExpression( + 'cfnUserPool', + 'userPoolName', + `${userPoolWithoutBackendEnvName}-`, + amplifyGen1EnvName, + '', + ); + + nodes.push(userPoolAssignment); + } else if (overridePath.includes('PasswordPolicy')) { + const policyKey = overridePath.split('.')[2]; + if (value !== undefined && policyKey in mappedPolicyType) { + policies.passwordPolicy[mappedPolicyType[policyKey] as string] = value; + } + } else { + nodes.push(this.setPropertyValue(factory.createIdentifier('cfnUserPool'), overridePath, value)); + } + } + nodes.push( + this.setPropertyValue( + factory.createIdentifier('cfnUserPool'), + 'policies', + policies as number | string | boolean | string[] | object, + ), + ); + } + + if (renderArgs.auth?.guestLogin === false || (renderArgs.auth?.identityPoolName && !renderArgs?.auth?.referenceAuth)) { + const cfnIdentityPoolVariableStatement = this.createVariableStatement( + this.createVariableDeclaration('cfnIdentityPool', 'auth.resources.cfnResources.cfnIdentityPool'), + ); + nodes.push(cfnIdentityPoolVariableStatement); + if (renderArgs.auth?.identityPoolName) { + const splitIdentityPoolName = renderArgs.auth.identityPoolName.split('_'); + const identityPoolWithoutBackendEnvName = splitIdentityPoolName.slice(0, -1).join('_'); + + const identityPoolAssignment = this.createTemplateLiteralExpression( + 'cfnIdentityPool', + 'identityPoolName', + `${identityPoolWithoutBackendEnvName}_`, + amplifyGen1EnvName, + '', + ); + + nodes.push(identityPoolAssignment); + } + if (renderArgs.auth?.guestLogin === false) { + nodes.push(this.setPropertyValue(factory.createIdentifier('cfnIdentityPool'), 'allowUnauthenticatedIdentities', false)); + } + } + + if ( + (renderArgs.auth?.oAuthFlows || renderArgs.auth?.readAttributes || renderArgs.auth?.writeAttributes) && + !renderArgs?.auth?.referenceAuth + ) { + const cfnUserPoolClientVariableStatement = this.createVariableStatement( + this.createVariableDeclaration('cfnUserPoolClient', 'auth.resources.cfnResources.cfnUserPoolClient'), + ); + nodes.push(cfnUserPoolClientVariableStatement); + if (renderArgs.auth?.oAuthFlows) { + nodes.push( + this.setPropertyValue( + factory.createIdentifier('cfnUserPoolClient'), + 'allowedOAuthFlows', + renderArgs.auth?.oAuthFlows as number | string | boolean | string[], + ), + ); + } + + if (renderArgs.auth?.readAttributes) { + nodes.push( + this.setPropertyValue( + factory.createIdentifier('cfnUserPoolClient'), + 'readAttributes', + renderArgs.auth?.readAttributes as number | string | boolean | string[], + ), + ); + } + } + + if (renderArgs.auth?.writeAttributes && !renderArgs?.auth?.referenceAuth) { + nodes.push( + this.setPropertyValue( + factory.createIdentifier('cfnUserPoolClient'), + 'writeAttributes', + renderArgs.auth?.writeAttributes as string[], + ), + ); + } + + // Since Gen2 only supports 1 user pool client by default, we need to add CDK overrides for the additional user pool client from Gen1 + if (renderArgs.auth?.userPoolClient) { + const userPoolVariableStatement = this.createVariableStatement(this.createVariableDeclaration('userPool', 'auth.resources.userPool')); + nodes.push(userPoolVariableStatement); + nodes.push(this.createUserPoolClientAssignment(renderArgs.auth?.userPoolClient, imports)); + } + + if (renderArgs.storage && renderArgs.storage.hasS3Bucket) { + assert(renderArgs.storage.bucketName); + const cfnStorageVariableStatement = this.createVariableStatement( + this.createVariableDeclaration('s3Bucket', 'storage.resources.cfnResources.cfnBucket'), + ); + nodes.push(cfnStorageVariableStatement); + + const splitBucketName = renderArgs.storage.bucketName.split('-'); + const bucketNameWithoutBackendEnvName = splitBucketName.slice(0, -1).join('-'); + + const bucketNameAssignment = this.createTemplateLiteralExpression( + '// s3Bucket', + 'bucketName', + `${bucketNameWithoutBackendEnvName}-`, + amplifyGen1EnvName, + '', + ); + nodes.push(bucketNameAssignment); + } + + if ( + renderArgs.storage?.accelerateConfiguration || + renderArgs.storage?.versionConfiguration || + renderArgs.storage?.bucketEncryptionAlgorithm + ) { + if (renderArgs.storage?.accelerateConfiguration) { + const accelerateConfigAssignment = factory.createExpressionStatement( + factory.createAssignment( + factory.createPropertyAccessExpression( + factory.createIdentifier('s3Bucket'), + factory.createIdentifier('accelerateConfiguration'), + ), + factory.createObjectLiteralExpression( + [this.createStringPropertyAssignment('accelerationStatus', renderArgs.storage.accelerateConfiguration)], + false, + ), + ), + ); + nodes.push(accelerateConfigAssignment); + } + + if (renderArgs.storage?.versionConfiguration) { + const versionConfigAssignment = factory.createExpressionStatement( + factory.createAssignment( + factory.createPropertyAccessExpression( + factory.createIdentifier('s3Bucket'), + factory.createIdentifier('versioningConfiguration'), + ), + factory.createObjectLiteralExpression( + [this.createStringPropertyAssignment('status', renderArgs.storage.versionConfiguration)], + false, + ), + ), + ); + nodes.push(versionConfigAssignment); + } + + if (renderArgs.storage?.bucketEncryptionAlgorithm) { + const serverSideEncryptionByDefaultMap = new Map(); + serverSideEncryptionByDefaultMap.set('SSEAlgorithm', 'sseAlgorithm'); + serverSideEncryptionByDefaultMap.set('KMSMasterKeyID', 'kmsMasterKeyId'); + serverSideEncryptionByDefaultMap.set('bucketKeyEnabled', 'bucketKeyEnabled'); + serverSideEncryptionByDefaultMap.set('serverSideEncryptionByDefault', 'serverSideEncryptionByDefault'); + + const bucketEncryptionAssignment = factory.createExpressionStatement( + factory.createAssignment( + factory.createPropertyAccessExpression(factory.createIdentifier('s3Bucket'), factory.createIdentifier('bucketEncryption')), + factory.createObjectLiteralExpression( + [ + factory.createPropertyAssignment( + factory.createIdentifier('serverSideEncryptionConfiguration'), + factory.createArrayLiteralExpression( + [this.createNestedObjectExpression(renderArgs.storage.bucketEncryptionAlgorithm, serverSideEncryptionByDefaultMap)], + true, + ), + ), + ], + true, + ), + ), + ); + nodes.push(bucketEncryptionAssignment); + } + + imports.push( + factory.createImportDeclaration( + undefined, + factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier('s3'))), + factory.createStringLiteral('aws-cdk-lib/aws-s3'), + ), + ); + } + + if ( + renderArgs.auth?.userPoolClient && + renderArgs.auth.userPoolClient.SupportedIdentityProviders && + renderArgs.auth.userPoolClient.SupportedIdentityProviders.length > 0 + ) { + const idpStatements = this.createProviderSetupCode(); + nodes.push(...idpStatements); + + // Gen1 doesn't manage UserPoolDomains in CFN while Gen2 creates a default one for oauth apps. + // This causes an invalid domain request error when updating Gen2 post stack refactor. + // We are adding a commented line to remove the domain from Gen2 CDK. This will be + // uncommented by users post refactor (instructions will be in README.md). + // backend.auth.resources.userPool.node.tryRemoveChild('UserPoolDomain'); + const userPoolDomainRemovalStatementCommented = factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createPropertyAccessExpression( + factory.createIdentifier('// backend.auth.resources.userPool'), + factory.createIdentifier('node'), + ), + factory.createIdentifier('tryRemoveChild'), + ), + undefined, + [factory.createStringLiteral('UserPoolDomain')], + ), + ); + nodes.push(userPoolDomainRemovalStatementCommented); + } + + // Add a tag commented out to force a deployment post refactor + // Tags.of(backend.stack).add('gen1-migrated-app', 'true') + if (renderArgs.auth || renderArgs.storage?.hasS3Bucket || renderArgs.customResources) { + const tagAssignment = factory.createExpressionStatement( + factory.createCallExpression( + factory.createPropertyAccessExpression( + factory.createCallExpression(factory.createIdentifier('// Tags.of'), undefined, [factory.createIdentifier('backend.stack')]), + factory.createIdentifier('add'), + ), + undefined, + [factory.createStringLiteral('gen1-migrated-app'), factory.createStringLiteral('true')], + ), + ); + nodes.push(tagAssignment); + } + + return factory.createNodeArray([...imports, newLineIdentifier, ...errors, newLineIdentifier, backendStatement, ...nodes], true); + } +} diff --git a/packages/amplify-gen2-codegen/src/data/source_builder.test.ts b/packages/amplify-gen2-codegen/src/data/source_builder.test.ts new file mode 100644 index 00000000000..e6bb13d0ea5 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/data/source_builder.test.ts @@ -0,0 +1,48 @@ +import assert from 'node:assert'; +import { printNodeArray } from '../test_utils/ts_node_printer'; +import { generateDataSource } from './source_builder'; +describe('Data Category code generation', () => { + it('generates the correct import', () => { + const source = printNodeArray(generateDataSource()); + assert.match(source, /import\s?\{\s?defineData\s?\}\s?from\s?"\@aws-amplify\/backend"/); + }); + describe('import map', () => { + it('is rendered', () => { + const tableMappings = { dev: { Todo: 'my-todo-mapping' } }; + const source = printNodeArray(generateDataSource({ tableMappings, schema: 'schema' })); + assert.match( + source, + /migratedAmplifyGen1DynamoDbTableMappings: \[\{\n\s+\/\/ Replace the environment name \(dev\) with the corresponding branch name. Use ['"]sandbox['"] for your sandbox environment.\n\s+branchName: ['"]dev['"],\n\s+modelNameToTableNameMapping: { Todo: ['"]my-todo-mapping['"] }\n\s+}]/, + ); + }); + it('includes multiple table mappings', () => { + const tableMappings = { + dev: { Todo: 'my-todo-mapping' }, + prod: { Todo: 'my-todo-mapping-prod' }, + }; + const source = printNodeArray(generateDataSource({ tableMappings, schema: 'schema' })); + assert.match( + source, + /migratedAmplifyGen1DynamoDbTableMappings: \[\{\n\s+\/\/ Replace the environment name \(dev\) with the corresponding branch name. Use ['"]sandbox['"] for your sandbox environment.\n\s+branchName: ['"]dev['"],\n\s+modelNameToTableNameMapping: { Todo: ['"]my-todo-mapping['"] }\n\s+}, {\n\s+\/\/ Replace the environment name \(prod\) with the corresponding branch name. Use ['"]sandbox['"] for your sandbox environment.\n\s+branchName: ['"]prod['"],\n\s+modelNameToTableNameMapping: { Todo: ['"]my-todo-mapping-prod['"] }\n\s+}]/, + ); + }); + it('includes a comment for missing table mappings', () => { + const tableMappings = { + dev: undefined, + }; + const source = printNodeArray(generateDataSource({ tableMappings, schema: 'schema' })); + assert.match( + source, + /\/\*\*\n\s+\* Unable to find the table mapping for this environment.\n\s+\* This could be due the enableGen2Migration feature flag not being set to true for this environment.\n\s+\* Please enable the feature flag and push the backend resources.\n\s+\* If you are not planning to migrate this environment, you can remove this key.\n\s+\*\/\n\s+modelNameToTableNameMapping: {}/, + ); + }); + it('has each each key in defineData', () => { + const tableMappings = { dev: { Todo: 'my-todo-mapping' } }; + const source = printNodeArray(generateDataSource({ tableMappings, schema: 'schema' })); + assert.match( + source, + /const schema \= \`schema\`\;\n\nexport const data \= defineData\({\n\s+migratedAmplifyGen1DynamoDbTableMappings: \[\{\n\s+\/\/ Replace the environment name \(dev\) with the corresponding branch name. Use ['"]sandbox['"] for your sandbox environment.\n\s+branchName: ['"]dev['"],\n\s+modelNameToTableNameMapping: { Todo: ['"]my-todo-mapping['"] }\n\s+}],\n\s+schema\n}\)/, + ); + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/data/source_builder.ts b/packages/amplify-gen2-codegen/src/data/source_builder.ts new file mode 100644 index 00000000000..a4958ae3fa4 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/data/source_builder.ts @@ -0,0 +1,86 @@ +import ts, { ObjectLiteralElementLike, ObjectLiteralExpression } from 'typescript'; +import { renderResourceTsFile } from '../resource/resource'; +const factory = ts.factory; + +export type DataTableMapping = Record; +export type DataDefinition = { + tableMappings: Record; + schema: string; +}; + +const migratedAmplifyGen1DynamoDbTableMappingsKeyName = 'migratedAmplifyGen1DynamoDbTableMappings'; + +export const generateDataSource = (dataDefinition?: DataDefinition): ts.NodeArray => { + const dataRenderProperties: ObjectLiteralElementLike[] = []; + const namedImports: Record> = { '@aws-amplify/backend': new Set() }; + namedImports['@aws-amplify/backend'].add('defineData'); + + const schemaStatements: ts.Node[] = []; + + if (dataDefinition && dataDefinition.schema) { + const schemaVariableDeclaration = factory.createVariableDeclaration( + 'schema', + undefined, + undefined, + factory.createNoSubstitutionTemplateLiteral(dataDefinition.schema), + ); + const schemaStatementAssignment = factory.createVariableStatement( + [], + factory.createVariableDeclarationList([schemaVariableDeclaration], ts.NodeFlags.Const), + ); + schemaStatements.push(schemaStatementAssignment); + } + + if (dataDefinition?.tableMappings) { + const tableMappingEnvironments: ObjectLiteralExpression[] = []; + for (const [environmentName, tableMapping] of Object.entries(dataDefinition.tableMappings)) { + const tableMappingProperties: ObjectLiteralElementLike[] = []; + if (tableMapping) { + for (const [tableName, tableId] of Object.entries(tableMapping)) { + tableMappingProperties.push( + factory.createPropertyAssignment(factory.createIdentifier(tableName), factory.createStringLiteral(tableId)), + ); + } + } + + const branchNameExpression = ts.addSyntheticLeadingComment( + factory.createPropertyAssignment('branchName', factory.createStringLiteral(environmentName)), + ts.SyntaxKind.SingleLineCommentTrivia, + ` Replace the environment name (${environmentName}) with the corresponding branch name. Use "sandbox" for your sandbox environment.`, + true, + ); + let tableMappingExpression = factory.createPropertyAssignment( + 'modelNameToTableNameMapping', + factory.createObjectLiteralExpression(tableMappingProperties), + ); + if (tableMappingProperties.length === 0) { + tableMappingExpression = ts.addSyntheticLeadingComment( + tableMappingExpression, + ts.SyntaxKind.MultiLineCommentTrivia, + '*\n' + + '* Unable to find the table mapping for this environment.\n' + + '* This could be due the enableGen2Migration feature flag not being set to true for this environment.\n' + + '* Please enable the feature flag and push the backend resources.\n' + + '* If you are not planning to migrate this environment, you can remove this key.\n', + true, + ); + } + const tableMappingForEnvironment = factory.createObjectLiteralExpression([branchNameExpression, tableMappingExpression], true); + tableMappingEnvironments.push(tableMappingForEnvironment); + } + dataRenderProperties.push( + factory.createPropertyAssignment( + migratedAmplifyGen1DynamoDbTableMappingsKeyName, + factory.createArrayLiteralExpression(tableMappingEnvironments), + ), + ); + } + dataRenderProperties.push(factory.createShorthandPropertyAssignment(factory.createIdentifier('schema'))); + return renderResourceTsFile({ + exportedVariableName: factory.createIdentifier('data'), + functionCallParameter: factory.createObjectLiteralExpression(dataRenderProperties, true), + backendFunctionConstruct: 'defineData', + postImportStatements: schemaStatements, + additionalImportedBackendIdentifiers: namedImports, + }); +}; diff --git a/packages/amplify-gen2-codegen/src/function/lambda.ts b/packages/amplify-gen2-codegen/src/function/lambda.ts new file mode 100644 index 00000000000..4b29418a951 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/function/lambda.ts @@ -0,0 +1,19 @@ +import ts from 'typescript'; +export type Lambda = { + source: string; +}; + +const factory = ts.factory; + +export const createTriggersProperty = (triggers: Record) => { + return factory.createPropertyAssignment( + factory.createIdentifier('triggers'), + factory.createObjectLiteralExpression( + Object.entries(triggers).map(([key, value]) => { + const functionName = value.source.split('/')[3]; + return factory.createPropertyAssignment(factory.createIdentifier(key), factory.createIdentifier(functionName)); + }), + true, + ), + ); +}; diff --git a/packages/amplify-gen2-codegen/src/function/source_builder.test.ts b/packages/amplify-gen2-codegen/src/function/source_builder.test.ts new file mode 100644 index 00000000000..c85789588b2 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/function/source_builder.test.ts @@ -0,0 +1,97 @@ +import assert from 'node:assert'; +import { FunctionDefinition, renderFunctions } from './source_builder'; +import { printNodeArray } from '../test_utils/ts_node_printer'; +import { Runtime } from '@aws-sdk/client-lambda'; + +describe('render function', () => { + describe('import', () => { + it('imports defineFunction renderFunction is defined', () => { + const definition: FunctionDefinition = {}; + definition.name = 'function1'; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + + assert.match(source, /import\s?\{\s?defineFunction\s?\}\s?from\s?"\@aws-amplify\/backend"/); + }); + }); + describe('does not render', () => { + it('does not render the properties if its empty', () => { + const rendered = renderFunctions({}); + const source = printNodeArray(rendered); + assert.doesNotMatch(source, new RegExp(`entry:`)); + assert.match(source, /throw new Error/); + }); + }); + describe('render properties', () => { + it('does render entry property', () => { + const definition: FunctionDefinition = {}; + definition.entry = 'index.handler'; + definition.name = 'sayHello'; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + assert.match(source, /entry: /); + }); + it('does render name property', () => { + const definition: FunctionDefinition = {}; + definition.name = 'function1'; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + assert.match(source, /name: /); + }); + test.each([[Runtime.nodejs16x], [Runtime.nodejs18x], [Runtime.nodejs20x], [Runtime.nodejs22x]])( + 'does render runtime property for %s nodejs.', + (nodejsRuntime: Runtime) => { + const definition: FunctionDefinition = {}; + definition.runtime = nodejsRuntime; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + const expectedRuntime = nodejsRuntime.split('nodejs')[1].split('.')[0]; + assert(expectedRuntime); + assert.match(source, new RegExp(`runtime: ${expectedRuntime}`)); + }, + ); + + it('throws error for unsupported nodejs runtime', () => { + const definition: FunctionDefinition = {}; + definition.runtime = Runtime.nodejs14x; + + assert.throws(() => renderFunctions(definition), /Unsupported nodejs runtime/); + }); + it('does not render runtime property for unsupported runtimes', () => { + const definition: FunctionDefinition = {}; + definition.runtime = Runtime.dotnet8; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + assert.doesNotMatch(source, /runtime: /); + }); + it('does render timeoutSeconds property', () => { + const definition: FunctionDefinition = {}; + definition.timeoutSeconds = 3; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + assert.match(source, /timeoutSeconds: /); + }); + it('does render memoryMB property', () => { + const definition: FunctionDefinition = {}; + definition.memoryMB = 128; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + assert.match(source, /memoryMB: /); + }); + it('does render environment property', () => { + const definition: FunctionDefinition = {}; + definition.environment = { Variables: { ENV: 'dev', REGION: 'us-west-2' } }; + + const rendered = renderFunctions(definition); + const source = printNodeArray(rendered); + assert.match(source, /environment: /); + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/function/source_builder.ts b/packages/amplify-gen2-codegen/src/function/source_builder.ts new file mode 100644 index 00000000000..49d0fbeb934 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/function/source_builder.ts @@ -0,0 +1,191 @@ +import ts, { ObjectLiteralElementLike, VariableDeclaration, VariableStatement } from 'typescript'; +import { EnvironmentResponse, Runtime } from '@aws-sdk/client-lambda'; +import { renderResourceTsFile } from '../resource/resource'; +import assert from 'node:assert'; + +export interface FunctionDefinition { + category?: string; + entry?: string; + name?: string; + timeoutSeconds?: number; + memoryMB?: number; + environment?: EnvironmentResponse; + runtime?: Runtime | string; + resourceName?: string; + schedule?: string; +} + +const factory = ts.factory; + +const amplifyGen1EnvName = 'AMPLIFY_GEN_1_ENV_NAME'; + +const createParameter = ( + name: string, + value: ts.LiteralExpression | ts.ObjectLiteralExpression | ts.TemplateExpression, +): ts.PropertyAssignment => factory.createPropertyAssignment(factory.createIdentifier(name), value); + +const createVariableStatement = (variableDeclaration: VariableDeclaration): VariableStatement => { + return factory.createVariableStatement([], factory.createVariableDeclarationList([variableDeclaration], ts.NodeFlags.Const)); +}; + +const createTemplateLiteral = (templateHead: string, templateSpan: string, templateTail: string) => { + return factory.createTemplateExpression(factory.createTemplateHead(templateHead), [ + factory.createTemplateSpan(factory.createIdentifier(templateSpan), factory.createTemplateTail(templateTail)), + ]); +}; + +export function renderFunctions(definition: FunctionDefinition, appId?: string, backendEnvironmentName?: string | undefined) { + const postImportStatements = []; + const namedImports: Record> = { '@aws-amplify/backend': new Set() }; + namedImports['@aws-amplify/backend'].add('defineFunction'); + + postImportStatements.push( + factory.createExpressionStatement( + factory.createCallExpression(factory.createIdentifier('throw new Error'), undefined, [ + factory.createStringLiteral( + `Source code for this function can be found in your Amplify Gen 1 Directory. See .amplify/migration/amplify/backend/function/${definition.resourceName}/src`, + ), + ]), + ), + ); + + const defineFunctionProperty = createFunctionDefinition(definition, postImportStatements, namedImports, appId, backendEnvironmentName); + + const amplifyGen1EnvStatement = createVariableStatement( + factory.createVariableDeclaration( + amplifyGen1EnvName, + undefined, + undefined, + factory.createIdentifier('process.env.AMPLIFY_GEN_1_ENV_NAME ?? "sandbox"'), + ), + ); + postImportStatements.push(amplifyGen1EnvStatement); + + return renderResourceTsFile({ + exportedVariableName: factory.createIdentifier(definition?.resourceName || 'sayHello'), + functionCallParameter: factory.createObjectLiteralExpression(defineFunctionProperty, true), + backendFunctionConstruct: 'defineFunction', + additionalImportedBackendIdentifiers: namedImports, + postImportStatements, + }); +} + +export function createFunctionDefinition( + definition?: FunctionDefinition, + postImportStatements?: (ts.CallExpression | ts.JSDoc | ts.ExpressionStatement)[], + namedImports?: Record>, + appId?: string, + backendEnvironmentName?: string, +) { + const defineFunctionProperties: ObjectLiteralElementLike[] = []; + + if (definition?.entry) { + defineFunctionProperties.push(createParameter('entry', factory.createStringLiteral('./handler.ts'))); + } + if (definition?.name) { + const splitFuncName = definition.name.split('-'); + const funcNameWithoutBackendEnvName = splitFuncName.slice(0, -1).join('-'); + + const funcNameAssignment = createTemplateLiteral(`${funcNameWithoutBackendEnvName}-`, amplifyGen1EnvName, ''); + + defineFunctionProperties.push(createParameter('name', funcNameAssignment)); + } + if (definition?.timeoutSeconds) { + defineFunctionProperties.push(createParameter('timeoutSeconds', factory.createNumericLiteral(definition.timeoutSeconds))); + } + if (definition?.memoryMB) { + defineFunctionProperties.push(createParameter('memoryMB', factory.createNumericLiteral(definition.memoryMB))); + } + + if (definition?.environment?.Variables) { + defineFunctionProperties.push( + createParameter( + 'environment', + factory.createObjectLiteralExpression( + Object.entries(definition.environment.Variables).map(([key, value]) => { + if (key == 'API_KEY' && value.startsWith(`/amplify/${appId}/${backendEnvironmentName}`)) { + postImportStatements?.push( + factory.createCallExpression(factory.createIdentifier('throw new Error'), undefined, [ + // eslint-disable-next-line spellcheck/spell-checker + factory.createStringLiteral('Secrets need to be reset, use `npx ampx sandbox secret set API_KEY` to set the value'), + ]), + ); + if (namedImports && namedImports['@aws-amplify/backend']) { + namedImports['@aws-amplify/backend'].add('secret'); + } else { + const namedImports: Record> = { '@aws-amplify/backend': new Set() }; + namedImports['@aws-amplify/backend'].add('secret'); + } + return factory.createPropertyAssignment( + key, + factory.createCallExpression(factory.createIdentifier('secret'), undefined, [factory.createStringLiteral('API_KEY')]), + ); + } else if (key == 'ENV') { + const envNameAssignment = createTemplateLiteral('', amplifyGen1EnvName, ''); + return createParameter(key, envNameAssignment); + } + + return createParameter(key, factory.createStringLiteral(value)); + }), + ), + ), + ); + } + + const runtime = definition?.runtime; + if (runtime && runtime.includes('nodejs')) { + let nodeRuntime: number | undefined; + switch (runtime) { + case Runtime.nodejs16x: + nodeRuntime = 16; + break; + case Runtime.nodejs18x: + nodeRuntime = 18; + break; + case Runtime.nodejs20x: + nodeRuntime = 20; + break; + case Runtime.nodejs22x: + nodeRuntime = 22; + break; + default: + throw new Error(`Unsupported nodejs runtime for function: ${runtime}`); + } + assert(nodeRuntime, 'Expected nodejs version to be set'); + + defineFunctionProperties.push(createParameter('runtime', factory.createNumericLiteral(nodeRuntime))); + } + + if (definition?.schedule) { + const rawScheduleExpression = definition.schedule; + let scheduleExpression: string | undefined; + const startIndex = rawScheduleExpression.indexOf('(') + 1; + const endIndex = rawScheduleExpression.lastIndexOf(')'); + const scheduleValue = startIndex > 0 && endIndex > startIndex ? rawScheduleExpression.slice(startIndex, endIndex) : undefined; + if (rawScheduleExpression?.startsWith('rate(')) { + // Convert rate expression to a more readable format + const rateValue = scheduleValue; + if (rateValue) { + const [value, unit] = rateValue.split(' '); + const unitMap: Record = { + minute: 'm', + minutes: 'm', + hour: 'h', + hours: 'h', + day: 'd', + days: 'd', + }; + scheduleExpression = `every ${value}${unitMap[unit]}`; + } + } else if (rawScheduleExpression?.startsWith('cron(')) { + // Extract the cron expression as-is + scheduleExpression = scheduleValue; + } + + if (scheduleExpression) { + defineFunctionProperties.push(createParameter('schedule', factory.createStringLiteral(scheduleExpression))); + } + } + + return defineFunctionProperties; +} diff --git a/packages/amplify-gen2-codegen/src/index.ts b/packages/amplify-gen2-codegen/src/index.ts new file mode 100644 index 00000000000..a9acc94caf1 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/index.ts @@ -0,0 +1,266 @@ +import path from 'path'; +import fs from 'node:fs/promises'; +import { PackageJson, patchNpmPackageJson } from './npm_package/renderer'; +import { RenderPipeline, Renderer } from './render_pipeline'; +import { JsonRenderer } from './renderers/package_json'; +import { TypescriptNodeArrayRenderer } from './renderers/typescript_block_node'; +import { BackendRenderParameters, BackendSynthesizer } from './backend/synthesizer'; +import { EnsureDirectory } from './renderers/ensure_directory'; +import { Lambda } from './function/lambda'; +import { + AuthTriggerEvents, + AuthLambdaTriggers, + AuthDefinition, + renderAuthNode, + SendingAccount, + PolicyOverrides, + PasswordPolicyPath, + UserPoolMfaConfig, + Group, + Attribute, + EmailOptions, + LoginOptions, + StandardAttribute, + StandardAttributes, + CustomAttribute, + CustomAttributes, + MultifactorOptions, + OidcOptions, + OidcEndPoints, + MetadataOptions, + SamlOptions, + Scope, + AttributeMappingRule, + ReferenceAuth, +} from './auth/source_builder'; +import { + StorageRenderParameters, + renderStorage, + AccessPatterns, + Permission, + S3TriggerDefinition, + StorageTriggerEvent, + ServerSideEncryptionConfiguration, +} from './storage/source_builder.js'; + +import { DataDefinition, DataTableMapping, generateDataSource } from './data/source_builder'; + +import { FunctionDefinition, renderFunctions } from './function/source_builder'; +import assert from 'assert'; + +export interface Gen2RenderingOptions { + outputDir: string; + appId?: string; + backendEnvironmentName?: string | undefined; + auth?: AuthDefinition; + storage?: StorageRenderParameters; + data?: DataDefinition; + functions?: FunctionDefinition[]; + customResources?: Map; + unsupportedCategories?: Map; + fileWriter?: (content: string, path: string) => Promise; +} +const createFileWriter = (path: string) => async (content: string) => fs.writeFile(path, content); + +export const createGen2Renderer = ({ + outputDir, + backendEnvironmentName, + auth, + storage, + data, + functions, + customResources, + unsupportedCategories, + fileWriter = (content, path) => createFileWriter(path)(content), +}: Readonly): Renderer => { + const ensureOutputDir = new EnsureDirectory(outputDir); + const ensureAmplifyDirectory = new EnsureDirectory(path.join(outputDir, 'amplify')); + const amplifyPackageJson = new JsonRenderer( + async () => ({ type: 'module' }), + (content) => fileWriter(content, path.join(outputDir, 'amplify', 'package.json')), + ); + const jsonRenderer = new JsonRenderer( + async () => { + let packageJson: PackageJson = { + name: 'my-gen2-app', + }; + try { + const packageJsonContents = await fs.readFile(`./package.json`, { encoding: 'utf-8' }); + packageJson = JSON.parse(packageJsonContents); + } catch (e) { + // File doesn't exist or is inaccessible. Ignore. + } + // Restrict dev dependencies to specific versions based on create-amplify gen2 flow: + // https://github.com/aws-amplify/amplify-backend/blob/2dab201cb9a222c3b8c396a46c17d661411839ab/packages/create-amplify/src/amplify_project_creator.ts#L15-L24 + return patchNpmPackageJson(packageJson, { + 'aws-cdk': '^2', + 'aws-cdk-lib': '^2', + 'ci-info': '^3.8.0', + constructs: '^10.0.0', + typescript: '^5.0.0', + '@types/node': '*', + }); + }, + (content) => fileWriter(content, path.join(outputDir, 'package.json')), + ); + const amplifyTsConfigJson = new JsonRenderer( + async () => ({ + compilerOptions: { + target: 'es2022', + module: 'es2022', + moduleResolution: 'bundler', + resolveJsonModule: true, + // eslint-disable-next-line spellcheck/spell-checker + esModuleInterop: true, + forceConsistentCasingInFileNames: true, + strict: true, + skipLibCheck: true, + paths: { + '$amplify/*': ['../.amplify/generated/*'], + }, + }, + }), + (content) => fileWriter(content, path.join(outputDir, 'amplify', 'tsconfig.json')), + ); + const backendSynthesizer = new BackendSynthesizer(); + const backendRenderOptions: BackendRenderParameters = {}; + + const renderers: Renderer[] = [ensureOutputDir, ensureAmplifyDirectory, amplifyPackageJson, amplifyTsConfigJson, jsonRenderer]; + + if (unsupportedCategories && unsupportedCategories.size >= 1) { + backendRenderOptions.unsupportedCategories = unsupportedCategories; + } + + if (functions && functions.length) { + const functionNamesAndCategory = new Map(); + for (const func of functions) { + if (func.name) { + const resourceName = func.resourceName; + assert(resourceName); + const funcCategory = func.category; + assert(funcCategory); + functionNamesAndCategory.set(resourceName, funcCategory); + const dirPath = path.join(outputDir, 'amplify', funcCategory, resourceName); + renderers.push(new EnsureDirectory(dirPath)); + renderers.push( + new TypescriptNodeArrayRenderer( + async () => renderFunctions(func), + (content) => { + return fileWriter(content, path.join(dirPath, 'resource.ts')).then(() => fileWriter('', path.join(dirPath, 'handler.ts'))); + }, + ), + ); + } + } + + backendRenderOptions.function = { + importFrom: './function/resource', + functionNamesAndCategories: functionNamesAndCategory, + }; + } + + if (auth) { + renderers.push(new EnsureDirectory(path.join(outputDir, 'amplify', 'auth'))); + renderers.push( + new TypescriptNodeArrayRenderer( + async () => renderAuthNode(auth), + (content) => fileWriter(content, path.join(outputDir, 'amplify', 'auth', 'resource.ts')), + ), + ); + backendRenderOptions.auth = { + importFrom: './auth/resource', + userPoolOverrides: auth?.userPoolOverrides, + guestLogin: auth?.guestLogin, + identityPoolName: auth?.identityPoolName, + oAuthFlows: auth?.oAuthFlows, + readAttributes: auth?.readAttributes, + writeAttributes: auth?.writeAttributes, + referenceAuth: auth?.referenceAuth, + userPoolClient: auth?.userPoolClient, + }; + } + + if (data && data.tableMappings && backendEnvironmentName && data.tableMappings[backendEnvironmentName] !== undefined) { + renderers.push(new EnsureDirectory(path.join(outputDir, 'amplify', 'data'))); + renderers.push( + new TypescriptNodeArrayRenderer( + async () => generateDataSource(data), + (content) => fileWriter(content, path.join(outputDir, 'amplify', 'data', 'resource.ts')), + ), + ); + backendRenderOptions.data = { + importFrom: './data/resource', + }; + } + + if (storage) { + const hasS3Bucket = storage?.accessPatterns || storage?.storageIdentifier; + if (hasS3Bucket) { + renderers.push(new EnsureDirectory(path.join(outputDir, 'amplify', 'storage'))); + renderers.push( + new TypescriptNodeArrayRenderer( + async () => renderStorage(storage), + (content) => fileWriter(content, path.join(outputDir, 'amplify', 'storage', 'resource.ts')), + ), + ); + } + backendRenderOptions.storage = { + importFrom: './storage/resource', + dynamoDB: storage.dynamoDB, + accelerateConfiguration: storage.accelerateConfiguration, + versionConfiguration: storage.versioningConfiguration, + hasS3Bucket: hasS3Bucket, + bucketEncryptionAlgorithm: storage.bucketEncryptionAlgorithm, + bucketName: storage.bucketName, + }; + } + + if (customResources && customResources.size > 0) { + backendRenderOptions.customResources = customResources; + } + + const backendRenderer = new TypescriptNodeArrayRenderer( + async () => backendSynthesizer.render(backendRenderOptions), + (content) => fileWriter(content, path.join(outputDir, 'amplify', 'backend.ts')), + ); + + renderers.push(backendRenderer); + + return new RenderPipeline(renderers); +}; +export { + Renderer, + SendingAccount, + UserPoolMfaConfig, + StorageRenderParameters, + AccessPatterns, + Permission, + S3TriggerDefinition, + PasswordPolicyPath, + AuthDefinition, + FunctionDefinition, + PolicyOverrides, + Group, + Attribute, + EmailOptions, + LoginOptions, + StandardAttribute, + StandardAttributes, + CustomAttribute, + CustomAttributes, + MultifactorOptions, + AuthTriggerEvents, + Lambda, + AuthLambdaTriggers, + StorageTriggerEvent, + DataDefinition, + DataTableMapping, + SamlOptions, + OidcEndPoints, + MetadataOptions, + OidcOptions, + Scope, + AttributeMappingRule, + ServerSideEncryptionConfiguration, + ReferenceAuth, +}; diff --git a/packages/amplify-gen2-codegen/src/npm_package/renderer.test.ts b/packages/amplify-gen2-codegen/src/npm_package/renderer.test.ts new file mode 100644 index 00000000000..9a6ae463f7d --- /dev/null +++ b/packages/amplify-gen2-codegen/src/npm_package/renderer.test.ts @@ -0,0 +1,86 @@ +import { AmplifyDependencies, AmplifyDevDependencies, AmplifyPackageVersions, PackageJson, patchNpmPackageJson } from './renderer'; +import assert from 'node:assert'; +const createPackageJson = (): PackageJson => ({ + name: 'my-package', + scripts: { + test: 'echo "hello, world"', + }, + dependencies: { + 'existing-dependency': '^0.0.1', + }, + devDependencies: { + 'existing-dev-dependency': '^0.0.2', + }, +}); +type IsDevDependency = boolean; +const installedDependencies: Record = { + tsx: true, + 'aws-cdk': true, + 'aws-amplify': false, + esbuild: true, + constructs: true, + typescript: true, + 'aws-cdk-lib': true, + '@aws-amplify/backend': true, + '@aws-amplify/backend-cli': true, + '@aws-amplify/backend-data': true, + 'ci-info': true, + '@types/node': true, +}; + +describe('package.json renderer', () => { + describe('package versions', () => { + it('preserves existing dependencies', () => { + const examplePackageJson = createPackageJson(); + const packageJson = patchNpmPackageJson(examplePackageJson, {}); + assert.equal(packageJson.dependencies?.['existing-dependency'], '^0.0.1'); + }); + it('preserves existing dev dependencies', () => { + const examplePackageJson = createPackageJson(); + const packageJson = patchNpmPackageJson(examplePackageJson, {}); + assert.equal(packageJson.devDependencies?.['existing-dev-dependency'], '^0.0.2'); + }); + describe('when a version is defined', () => { + for (const [dependency, isDevDependency] of Object.entries(installedDependencies)) { + it(`sets the version of ${dependency} to the defined version`, () => { + const examplePackageJson = createPackageJson(); + const version = '1.1.1'; + const packageJson = patchNpmPackageJson(examplePackageJson, { + [dependency]: version, + }); + if (isDevDependency) { + const typedDependencyKey = dependency as keyof AmplifyDevDependencies; + assert.equal(packageJson.devDependencies?.[typedDependencyKey], version); + } else { + const typedDependencyKey = dependency as keyof AmplifyDependencies; + assert.equal(packageJson.dependencies?.[typedDependencyKey], version); + } + }); + } + }); + describe('when a version is not defined', () => { + for (const [dependency, isDevDependency] of Object.entries(installedDependencies)) { + it(`sets the version of ${dependency} to *`, () => { + const examplePackageJson = createPackageJson(); + const packageJson = patchNpmPackageJson(examplePackageJson, {}); + if (isDevDependency) { + const typedDependencyKey = dependency as keyof AmplifyDevDependencies; + assert.equal(packageJson.devDependencies?.[typedDependencyKey], '*'); + } else { + const typedDependencyKey = dependency as keyof AmplifyDependencies; + assert.equal(packageJson.dependencies?.[typedDependencyKey], '*'); + } + }); + } + }); + }); + describe('package name', () => { + it('is not overwritten ', async () => { + const examplePackageJson = { + name: 'my-gen2-app', + }; + const packageJson = patchNpmPackageJson(examplePackageJson, {}); + assert.equal(packageJson.name, 'my-gen2-app'); + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/npm_package/renderer.ts b/packages/amplify-gen2-codegen/src/npm_package/renderer.ts new file mode 100644 index 00000000000..b59b780df3a --- /dev/null +++ b/packages/amplify-gen2-codegen/src/npm_package/renderer.ts @@ -0,0 +1,53 @@ +export type AmplifyDevDependencies = { + '@aws-amplify/backend': string; + '@aws-amplify/backend-cli': string; + '@aws-amplify/backend-data': string; + 'aws-cdk': string; + 'aws-cdk-lib': string; + 'ci-info': string; + constructs: string; + esbuild: string; + tsx: string; + typescript: string; + '@types/node': string; +}; +export type AmplifyDependencies = { + 'aws-amplify': string; +}; +export type AmplifyPackageVersions = AmplifyDevDependencies & AmplifyDependencies; + +export type PackageJsonDependencies = { + devDependencies?: Record; + dependencies?: Record; +}; + +export type PackageJson = { + name: string; + scripts?: Record; +} & PackageJsonDependencies; + +const withDefault = (version?: string) => version ?? '*'; + +export const patchNpmPackageJson = (packageJson: PackageJson, packageVersions: Partial = {}): PackageJson => { + return { + ...packageJson, + devDependencies: { + ...(packageJson.devDependencies ?? {}), + '@aws-amplify/backend': withDefault(packageVersions['@aws-amplify/backend']), + '@aws-amplify/backend-cli': withDefault(packageVersions['@aws-amplify/backend-cli']), + '@aws-amplify/backend-data': withDefault(packageVersions['@aws-amplify/backend-data']), + 'aws-cdk': withDefault(packageVersions['aws-cdk']), + 'aws-cdk-lib': withDefault(packageVersions['aws-cdk-lib']), + 'ci-info': withDefault(packageVersions['ci-info']), + constructs: withDefault(packageVersions.constructs), + esbuild: withDefault(packageVersions.esbuild), + tsx: withDefault(packageVersions.tsx), + typescript: withDefault(packageVersions.typescript), + '@types/node': withDefault(packageVersions['@types/node']), + }, + dependencies: { + ...(packageJson.dependencies ?? {}), + 'aws-amplify': withDefault(packageVersions['aws-amplify']), + }, + }; +}; diff --git a/packages/amplify-gen2-codegen/src/render_pipeline.test.ts b/packages/amplify-gen2-codegen/src/render_pipeline.test.ts new file mode 100644 index 00000000000..8961fa23224 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/render_pipeline.test.ts @@ -0,0 +1,60 @@ +import { RenderPipeline, Renderer } from './render_pipeline'; +import assert from 'node:assert'; + +describe('render pipeline', () => { + describe('render errors', () => { + it('returns an error if any renderer in the pipeline returns an error', async () => { + const message = 'my custom error'; + const error = new Error(message); + const errorRenderer: Renderer = { + render: async () => Promise.reject(error), + }; + const renderers = [errorRenderer]; + const pipeline = new RenderPipeline(renderers); + await assert.rejects(pipeline.render, { message }); + }); + it('the entire pipeline fails as soon as a renderer fails', async () => { + const message = 'render error'; + const error = new Error(message); + const mock = jest.fn(); + const successfulRenderer: Renderer = { + render: mock, + }; + const errorRenderer: Renderer = { + render: async () => Promise.reject(error), + }; + const renderers = [errorRenderer, successfulRenderer]; + const pipeline = new RenderPipeline(renderers); + await assert.rejects(pipeline.render, { message }); + assert.equal(mock.mock.calls.length, 0); + }); + it('the render pipeline handles errors uncaught by constituent renderers', async () => { + const message = 'render error'; + const error = new Error(message); + const errorRenderer: Renderer = { + render: async () => Promise.reject(error), + }; + const renderers = [errorRenderer]; + const pipeline = new RenderPipeline(renderers); + await assert.rejects(pipeline.render, { message }); + }); + }); + describe('successful render', () => { + it('calls each renderer exactly once in the pipeline', async () => { + const createSuccessfulRenderer = () => ({ + render: async () => Promise.resolve(), + }); + + const renderers = new Array(10).fill(null).map(createSuccessfulRenderer); + const spies = renderers.map((renderer) => { + return jest.spyOn(renderer, 'render'); + }); + + const pipeline = new RenderPipeline(renderers); + await assert.doesNotReject(pipeline.render); + for (const spy of spies) { + assert.equal(spy.mock.calls.length, 1); + } + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/render_pipeline.ts b/packages/amplify-gen2-codegen/src/render_pipeline.ts new file mode 100644 index 00000000000..7a33f0fc688 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/render_pipeline.ts @@ -0,0 +1,12 @@ +export interface Renderer { + render(): Promise; +} +export class RenderPipeline implements Renderer { + constructor(private renderers: Renderer[]) {} + + render = async (): Promise => { + for (const renderer of this.renderers) { + await renderer.render(); + } + }; +} diff --git a/packages/amplify-gen2-codegen/src/renderers/ensure_directory.test.ts b/packages/amplify-gen2-codegen/src/renderers/ensure_directory.test.ts new file mode 100644 index 00000000000..a272a3047e2 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/renderers/ensure_directory.test.ts @@ -0,0 +1,18 @@ +import fs from 'node:fs/promises'; +import assert from 'node:assert'; +import { EnsureDirectory } from './ensure_directory'; + +describe('Ensure directory', () => { + it('calls mkdir on the provided directory', async () => { + const mkdir = jest.spyOn(fs, 'mkdir'); + mkdir.mockImplementationOnce(async () => undefined); + + const ensureDir = new EnsureDirectory('output'); + await ensureDir.render(); + + assert.equal(mkdir.mock.calls.length, 1); + }); + afterEach(() => { + jest.resetAllMocks(); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/renderers/ensure_directory.ts b/packages/amplify-gen2-codegen/src/renderers/ensure_directory.ts new file mode 100644 index 00000000000..f0857461b03 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/renderers/ensure_directory.ts @@ -0,0 +1,9 @@ +import fs from 'node:fs/promises'; +import { Renderer } from '../render_pipeline.js'; + +export class EnsureDirectory implements Renderer { + constructor(private directory: string) {} + render = async (): Promise => { + await fs.mkdir(this.directory, { recursive: true }); + }; +} diff --git a/packages/amplify-gen2-codegen/src/renderers/package_json.test.ts b/packages/amplify-gen2-codegen/src/renderers/package_json.test.ts new file mode 100644 index 00000000000..efb97298ced --- /dev/null +++ b/packages/amplify-gen2-codegen/src/renderers/package_json.test.ts @@ -0,0 +1,13 @@ +import assert from 'node:assert'; +import { JsonRenderer } from './package_json'; + +describe('PackageJsonRenderer', () => { + it('renders the json object to a string', async () => { + const json = { name: 'my-package', version: 'my-version' }; + const jsonCreator = async () => json; + const testWriter = jest.fn(async () => {}); + const jsonRenderer = new JsonRenderer(jsonCreator, testWriter); + await jsonRenderer.render(); + assert.equal(testWriter.mock.calls[0], JSON.stringify(json, null, 2)); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/renderers/package_json.ts b/packages/amplify-gen2-codegen/src/renderers/package_json.ts new file mode 100644 index 00000000000..11c82dfbc66 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/renderers/package_json.ts @@ -0,0 +1,10 @@ +import { Renderer } from '../render_pipeline.js'; + +export class JsonRenderer implements Renderer { + constructor(private createJson: () => Promise>, private writeFile: (content: string) => Promise) {} + + render = async (): Promise => { + const packageJson = await this.createJson(); + await this.writeFile(JSON.stringify(packageJson, null, 2)); + }; +} diff --git a/packages/amplify-gen2-codegen/src/renderers/typescript_block_node.test.ts b/packages/amplify-gen2-codegen/src/renderers/typescript_block_node.test.ts new file mode 100644 index 00000000000..63d76d8338b --- /dev/null +++ b/packages/amplify-gen2-codegen/src/renderers/typescript_block_node.test.ts @@ -0,0 +1,21 @@ +import ts from 'typescript'; +import assert from 'node:assert'; +import { TypescriptNodeArrayRenderer } from './typescript_block_node'; + +describe('TypescriptBlockNodeRenderer', () => { + const createConsoleLogHelloWorldBlock = () => { + const consoleArgs = [ts.factory.createStringLiteral('hello, world')]; + const consoleIdentifier = ts.factory.createIdentifier('console'); + const consoleLog = ts.factory.createPropertyAccessExpression(consoleIdentifier, 'log'); + const expression = ts.factory.createCallExpression(consoleLog, undefined, consoleArgs); + const statement = ts.factory.createExpressionStatement(expression); + return ts.factory.createNodeArray([statement], true); + }; + it('trims the first and last line brackets', async () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const writer = jest.fn(async (_: string) => {}); + const renderer = new TypescriptNodeArrayRenderer(async () => createConsoleLogHelloWorldBlock(), writer); + await renderer.render(); + assert(writer.mock.calls[0][0].includes('console.log("hello, world");')); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/renderers/typescript_block_node.ts b/packages/amplify-gen2-codegen/src/renderers/typescript_block_node.ts new file mode 100644 index 00000000000..77c0f2bf404 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/renderers/typescript_block_node.ts @@ -0,0 +1,15 @@ +import ts from 'typescript'; +import { Renderer } from '../render_pipeline.js'; +export class TypescriptNodeArrayRenderer implements Renderer { + private printer: ts.Printer; + private sourceFile: ts.SourceFile; + constructor(private blockCreator: () => Promise>, private writer: (content: string) => Promise) { + this.printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + this.sourceFile = ts.createSourceFile('output.ts', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS); + } + render = async (): Promise => { + const block = await this.blockCreator(); + const source = this.printer.printList(ts.ListFormat.MultiLine, block, this.sourceFile); + await this.writer(source); + }; +} diff --git a/packages/amplify-gen2-codegen/src/resource/resource.test.ts b/packages/amplify-gen2-codegen/src/resource/resource.test.ts new file mode 100644 index 00000000000..71e5461ece5 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/resource/resource.test.ts @@ -0,0 +1,75 @@ +import ts from 'typescript'; +import assert from 'node:assert'; +import { printNodeArray } from '../test_utils/ts_node_printer'; +import { renderResourceTsFile, ResourceTsParameters } from './resource'; + +const factory = ts.factory; + +describe('Resource.ts file generation', () => { + describe('imports', () => { + const importedFunctionName = 'helloWorld'; + const additionalImportedBackendIdentifiers: Record> = { 'my-hello-world-package': new Set() }; + additionalImportedBackendIdentifiers['my-hello-world-package'].add(importedFunctionName); + const exportedVariableName = 'goodbyeWorld'; + const render = (parameters?: Partial) => + printNodeArray( + renderResourceTsFile({ + backendFunctionConstruct: importedFunctionName, + additionalImportedBackendIdentifiers, + functionCallParameter: factory.createObjectLiteralExpression(), + exportedVariableName: factory.createIdentifier(exportedVariableName), + ...parameters, + }), + ); + it('calls import with the correct function name', () => { + assert.match(render(), new RegExp(`import \\{ ${importedFunctionName} \\}`)); + }); + it('calls import with additionally import identifiers', () => { + const additionalImport = 'aGoodDayToYou'; + additionalImportedBackendIdentifiers['my-hello-world-package'].add(additionalImport); + assert.match( + render({ additionalImportedBackendIdentifiers }), + new RegExp(`import \\{ ${importedFunctionName}, ${additionalImport} \\} from `), + ); + }); + it('calls import with the correct package name', () => { + assert.match(render(), new RegExp('from\\s+["\']my-hello-world-package["\']')); + }); + it('makes the function call', () => { + assert.match(render(), new RegExp(`${importedFunctionName}\\(\\{\\}\\);`)); + }); + it('exports the variable', () => { + assert.match(render(), new RegExp(`export const ${exportedVariableName} =`)); + }); + it('adds additional statements the define resource function call', () => { + assert.match( + render({ + postExportStatements: [ + factory.createExpressionStatement( + factory.createEquality( + factory.createAdd(factory.createNumericLiteral(1), factory.createNumericLiteral(1)), + factory.createNumericLiteral(2), + ), + ), + ], + }), + /1 \+ 1 == 2/, + ); + }); + it('adds additional statements after import', () => { + assert.match( + render({ + postImportStatements: [ + factory.createExpressionStatement( + factory.createEquality( + factory.createAdd(factory.createNumericLiteral(1), factory.createNumericLiteral(1)), + factory.createNumericLiteral(2), + ), + ), + ], + }), + /1 \+ 1 == 2/, + ); + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/resource/resource.ts b/packages/amplify-gen2-codegen/src/resource/resource.ts new file mode 100644 index 00000000000..92301675eeb --- /dev/null +++ b/packages/amplify-gen2-codegen/src/resource/resource.ts @@ -0,0 +1,114 @@ +import ts from 'typescript'; +import { newLineIdentifier } from '../ts_factory_utils'; +const factory = ts.factory; +export type ResourceTsParameters = { + additionalImportedBackendIdentifiers?: Record>; + backendFunctionConstruct: string; + functionCallParameter: ts.ObjectLiteralExpression; + exportedVariableName: ts.Identifier; + postImportStatements?: ts.Node[]; + postExportStatements?: ts.Node[]; +}; +export function renderResourceTsFile({ + additionalImportedBackendIdentifiers = {}, + backendFunctionConstruct, + functionCallParameter, + exportedVariableName, + postImportStatements, + postExportStatements, +}: ResourceTsParameters): ts.NodeArray { + const backendFunctionIdentifier = factory.createIdentifier(backendFunctionConstruct); + const importStatements = renderImportStatements(additionalImportedBackendIdentifiers); + const functionCall = factory.createCallExpression(backendFunctionIdentifier, undefined, [functionCallParameter]); + const exportedVariable = factory.createVariableDeclaration(exportedVariableName, undefined, undefined, functionCall); + const exportStatement = factory.createVariableStatement( + [factory.createModifier(ts.SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList([exportedVariable], ts.NodeFlags.Const), + ); + + return factory.createNodeArray([ + ...importStatements, + ...(postImportStatements !== undefined && postImportStatements.length > 0 ? [newLineIdentifier, ...postImportStatements] : []), + newLineIdentifier, + exportStatement, + ...(postExportStatements !== undefined && postExportStatements.length > 0 ? [newLineIdentifier, ...postExportStatements] : []), + ]); +} + +export type ResourceTsParametersList = { + additionalImportedBackendIdentifiers?: Record>; + backendFunctionConstruct: string; + functionCallParameter: ts.ObjectLiteralExpression[]; + exportedVariableName: ts.Identifier[]; + postImportStatements?: ts.Node[]; + postExportStatements?: ts.Node[]; +}; + +export function renderResourceTsFilesForFunction({ + additionalImportedBackendIdentifiers = {}, + backendFunctionConstruct, + functionCallParameter, + exportedVariableName, + postImportStatements, + postExportStatements, +}: ResourceTsParametersList): ts.NodeArray { + const importStatements = renderImportStatements(additionalImportedBackendIdentifiers); + const exportStatements = renderExportStatementsForFunctions(backendFunctionConstruct, functionCallParameter, exportedVariableName); + + return factory.createNodeArray([ + ...importStatements, + ...(postImportStatements !== undefined && postImportStatements.length > 0 ? [newLineIdentifier, ...postImportStatements] : []), + ...(exportStatements ? [newLineIdentifier, ...exportStatements] : []), + ...(postExportStatements !== undefined && postExportStatements.length > 0 ? [newLineIdentifier, ...postExportStatements] : []), + ]); +} + +function renderImportStatements(additionalImportedBackendIdentifiers: Record>) { + const importStatements: ts.ImportDeclaration[] = []; + for (const [packageName, identifiers] of Object.entries(additionalImportedBackendIdentifiers)) { + const importSpecifiers: ts.ImportSpecifier[] = []; + + identifiers.forEach((identifier) => { + importSpecifiers.push(factory.createImportSpecifier(false, undefined, factory.createIdentifier(identifier))); + }); + + const importStatement = factory.createImportDeclaration( + undefined, + factory.createImportClause(false, undefined, factory.createNamedImports(importSpecifiers)), + factory.createStringLiteral(packageName), + ); + + importStatements.push(importStatement); + } + + return importStatements; +} + +function renderExportStatementsForFunctions( + backendFunctionConstruct: string, + functionCallParameter: ts.ObjectLiteralExpression[], + exportedVariableName: ts.Identifier[], +) { + const exportStatementList: ts.VariableStatement[] = []; + let i = 0; + for (const functionCallParam of functionCallParameter) { + const backendFunctionIdentifier = factory.createIdentifier(backendFunctionConstruct); + const functionCall = factory.createCallExpression(backendFunctionIdentifier, undefined, [functionCallParam]); + const exportedVariable = factory.createVariableDeclaration(exportedVariableName[i], undefined, undefined, functionCall); + const exportStatement = factory.createVariableStatement( + [factory.createModifier(ts.SyntaxKind.ExportKeyword)], + factory.createVariableDeclarationList([exportedVariable], ts.NodeFlags.Const), + ); + exportStatementList.push( + ts.addSyntheticLeadingComment( + exportStatement, + ts.SyntaxKind.MultiLineCommentTrivia, + `\nSource code for this function can be found in your Amplify Gen 1 Directory.\nSee amplify/backend/function/${exportedVariableName[i].escapedText}/src \n`, + true, + ), + ); + i++; + } + + return exportStatementList; +} diff --git a/packages/amplify-gen2-codegen/src/storage/access.ts b/packages/amplify-gen2-codegen/src/storage/access.ts new file mode 100644 index 00000000000..3dd4a0a00fc --- /dev/null +++ b/packages/amplify-gen2-codegen/src/storage/access.ts @@ -0,0 +1,75 @@ +import ts, { CallExpression, Identifier } from 'typescript'; +import { AccessPatterns, Permission } from './source_builder.js'; +const factory = ts.factory; + +/** + * /public/, /protected/{cognito:sub}/, and /private/{cognito:sub}/ + * @see https://docs.amplify.aws/gen1/react/build-a-backend/storage/configure-storage/#s3-access-permissions + */ + +type AccessPath = 'public/*' | 'private/{entity_id}/*' | 'protected/{entity_id}/*'; + +type UserLevel = 'guest' | 'authenticated' | `entity('identity')` | `groups(['${string}'])`; + +const createAllowPattern = (allowIdentifier: Identifier, userLevel: UserLevel, permissions: Permission[]) => { + return factory.createCallExpression( + factory.createPropertyAccessExpression(allowIdentifier, factory.createIdentifier(`${userLevel}.to`)), + undefined, + [factory.createArrayLiteralExpression(permissions.map((p) => factory.createStringLiteral(p)))], + ); +}; + +export const getAccessPatterns = (accessPatterns: AccessPatterns): ts.PropertyAssignment => { + const accessIdentifier = factory.createIdentifier('access'); + const allowIdentifier = factory.createIdentifier('allow'); + + const publicPathAccess = []; + const privatePathAccess = []; + const protectedPathAccess = []; + + if (accessPatterns.guest && accessPatterns.guest.length) { + publicPathAccess.push(createAllowPattern(allowIdentifier, 'guest', accessPatterns.guest ?? [])); + } + if (accessPatterns.auth && accessPatterns.auth.length) { + const accessPattern = createAllowPattern(allowIdentifier, 'authenticated', accessPatterns.auth ?? []); + publicPathAccess.push(accessPattern); + protectedPathAccess.push(accessPattern); + privatePathAccess.push(accessPattern); + } + if (accessPatterns.groups && Object.keys(accessPatterns.groups).length) { + Object.entries(accessPatterns.groups).forEach(([key, value]) => { + publicPathAccess.push(createAllowPattern(allowIdentifier, `groups(['${key}'])`, value)); + privatePathAccess.push(createAllowPattern(allowIdentifier, `groups(['${key}'])`, value)); + protectedPathAccess.push(createAllowPattern(allowIdentifier, `groups(['${key}'])`, value)); + }); + } + + const publicPath: AccessPath = 'public/*'; + const privatePath: AccessPath = 'private/{entity_id}/*'; + const protectedPath: AccessPath = 'protected/{entity_id}/*'; + + const allowAssignments: ts.PropertyAssignment[] = []; + + const createAccessPropertyAssignment = (bucketPath: string, accessArray: CallExpression[]) => + factory.createPropertyAssignment(factory.createStringLiteral(bucketPath), factory.createArrayLiteralExpression(accessArray)); + + if (publicPathAccess.length) { + allowAssignments.push(createAccessPropertyAssignment(publicPath, publicPathAccess)); + } + if (protectedPathAccess.length) { + allowAssignments.push(createAccessPropertyAssignment(protectedPath, protectedPathAccess)); + } + if (privatePathAccess.length) { + allowAssignments.push(createAccessPropertyAssignment(privatePath, privatePathAccess)); + } + + const accessFunction = factory.createArrowFunction( + undefined, + undefined, + [factory.createParameterDeclaration(undefined, undefined, allowIdentifier)], + undefined, + undefined, + factory.createParenthesizedExpression(factory.createObjectLiteralExpression(allowAssignments, true)), + ); + return factory.createPropertyAssignment(accessIdentifier, accessFunction); +}; diff --git a/packages/amplify-gen2-codegen/src/storage/source_builder.test.ts b/packages/amplify-gen2-codegen/src/storage/source_builder.test.ts new file mode 100644 index 00000000000..541100ced9e --- /dev/null +++ b/packages/amplify-gen2-codegen/src/storage/source_builder.test.ts @@ -0,0 +1,94 @@ +import assert from 'node:assert'; +import { AccessPatterns, Permission, renderStorage, StorageTriggerEvent } from './source_builder'; +import { printNodeArray } from '../test_utils/ts_node_printer'; +import { Lambda } from '../function/lambda'; + +describe('Storage source generation', () => { + describe('storage triggers', () => { + const triggers: Record = { + onDelete: { source: 'amplify/backend/storage/onDelete/' }, + onUpload: { source: 'amplify/backend/storage/onUpload' }, + }; + for (const [key, value] of Object.entries(triggers)) { + it(`${key} trigger is rendered`, () => { + const rendered = renderStorage({ triggers }); + const output = printNodeArray(rendered); + assert.match(output, new RegExp(`${key}: ${value.source.split('/')[3]}`)); + assert.match(output, /triggers: /); + }); + } + }); + describe('imports', () => { + it('renders the defineStorage import', () => { + const rendered = renderStorage(); + const output = printNodeArray(rendered); + assert.match(output, /import\s?\{\s?defineStorage\s?\}\s?from\s?"\@aws-amplify\/backend"/); + }); + }); + describe('defineStorage', () => { + describe('parameters', () => { + it('does not render `name` if `storageIdentifier` is undefined', () => { + const rendered = renderStorage(); + const output = printNodeArray(rendered); + + assert(!output.includes(`name:`)); + }); + it('renders `name` if the `storageIdentifier` is passed', () => { + const storageIdentifier = 'my-cool-bucket-dev'; + const rendered = renderStorage({ storageIdentifier }); + const output = printNodeArray(rendered); + + assert(output.includes('name: `my-cool-bucket-${AMPLIFY_GEN_1_ENV_NAME}`')); + }); + const permissions: Permission[] = ['read', 'write', 'delete']; + + describe('access parameters', () => { + describe('groups', () => { + it(`renders a comment when group permissions are present in Gen 1`, () => { + const groupName = 'manager'; + const accessPatterns: AccessPatterns = { + groups: { [groupName]: ['read'] }, + }; + const rendered = renderStorage({ accessPatterns }); + const output = printNodeArray(rendered); + assert.match(output, new RegExp(`Your project uses group permissions.`)); + }); + it(`does not render a comment when group permissions are not present in Gen 1`, () => { + const accessPatterns: AccessPatterns = {}; + const rendered = renderStorage({ accessPatterns }); + const output = printNodeArray(rendered); + assert.doesNotMatch(output, new RegExp(`Your project uses group permissions.`)); + }); + }); + const accessLevels = ['private', 'public', 'protected']; + for (const accessLevel of accessLevels) { + describe('authenticated', () => { + for (const permission of permissions) { + it(`grants ${permission} to ${accessLevel} path for authenticated user`, () => { + const accessPatterns: AccessPatterns = { + auth: [permission], + }; + const rendered = renderStorage({ accessPatterns }); + const output = printNodeArray(rendered); + assert.match(output, new RegExp(`${accessLevel}.*?allow.authenticated.to\\(\\["${permission}"\\]\\)`)); + }); + } + }); + } + describe('guest', () => { + for (const permission of permissions) { + it(`grants ${permission} to public path to guest`, async () => { + const accessPatterns: AccessPatterns = { + guest: [permission], + }; + const rendered = renderStorage({ accessPatterns }); + const output = printNodeArray(rendered); + + assert.match(output, new RegExp(`public.*?allow.guest.to\\(\\["${permission}"\\]\\)`)); + }); + } + }); + }); + }); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/storage/source_builder.ts b/packages/amplify-gen2-codegen/src/storage/source_builder.ts new file mode 100644 index 00000000000..c3e3ce018db --- /dev/null +++ b/packages/amplify-gen2-codegen/src/storage/source_builder.ts @@ -0,0 +1,108 @@ +import ts, { VariableDeclaration, VariableStatement } from 'typescript'; +import { getAccessPatterns } from './access'; +import { renderResourceTsFile } from '../resource/resource'; +import { createTriggersProperty, Lambda } from '../function/lambda'; +import { BucketAccelerateStatus, BucketVersioningStatus, ServerSideEncryptionByDefault } from '@aws-sdk/client-s3'; +const factory = ts.factory; + +const amplifyGen1EnvName = 'AMPLIFY_GEN_1_ENV_NAME'; + +export type S3TriggerDefinition = Record; +export type Permission = 'read' | 'write' | 'create' | 'delete'; +export type GroupPermissions = { + [Key in G[number]]: Permission[]; +}; + +export type StorageTriggerEvent = 'onDelete' | 'onUpload'; +export type AccessPatterns = { + auth?: Permission[]; + guest?: Permission[]; + groups?: Record; +}; + +export type ServerSideEncryptionConfiguration = { + serverSideEncryptionByDefault: ServerSideEncryptionByDefault; + bucketKeyEnabled: boolean; +}; + +export interface StorageRenderParameters { + bucketName?: string; + triggers?: Partial>; + accessPatterns?: AccessPatterns; + storageIdentifier?: string; + lambdas?: S3TriggerDefinition[]; + bucketEncryptionAlgorithm?: ServerSideEncryptionConfiguration; + dynamoDB?: string; + accelerateConfiguration?: BucketAccelerateStatus; + versioningConfiguration?: BucketVersioningStatus; +} + +const createVariableStatement = (variableDeclaration: VariableDeclaration): VariableStatement => { + return factory.createVariableStatement([], factory.createVariableDeclarationList([variableDeclaration], ts.NodeFlags.Const)); +}; + +const createTemplateLiteral = (templateHead: string, templateSpan: string, templateTail: string) => { + return factory.createTemplateExpression(factory.createTemplateHead(templateHead), [ + factory.createTemplateSpan(factory.createIdentifier(templateSpan), factory.createTemplateTail(templateTail)), + ]); +}; + +export const renderStorage = (storageParams: StorageRenderParameters = {}) => { + const propertyAssignments: ts.PropertyAssignment[] = []; + const namedImports: Record> = { '@aws-amplify/backend': new Set() }; + namedImports['@aws-amplify/backend'].add('defineStorage'); + const triggers = storageParams.triggers || {}; + + const postImportStatements = []; + const amplifyGen1EnvStatement = createVariableStatement( + factory.createVariableDeclaration( + amplifyGen1EnvName, + undefined, + undefined, + factory.createIdentifier('process.env.AMPLIFY_GEN_1_ENV_NAME ?? "sandbox"'), + ), + ); + postImportStatements.push(amplifyGen1EnvStatement); + + if (storageParams.storageIdentifier) { + const splitStorageIdentifier = storageParams.storageIdentifier.split('-'); + const storageNameWithoutBackendEnvName = splitStorageIdentifier.slice(0, -1).join('-'); + + const storageNameAssignment = createTemplateLiteral(`${storageNameWithoutBackendEnvName}-`, amplifyGen1EnvName, ''); + propertyAssignments.push(factory.createPropertyAssignment(factory.createIdentifier('name'), storageNameAssignment)); + } + if (storageParams.accessPatterns) { + propertyAssignments.push(getAccessPatterns(storageParams.accessPatterns)); + } + if (storageParams.accessPatterns?.groups) { + postImportStatements.push( + factory.createJSDocComment( + factory.createNodeArray([ + factory.createJSDocText('TODO: Your project uses group permissions. Group permissions have changed in Gen 2. '), + factory.createJSDocText( + 'In order to grant permissions to groups in Gen 2, please refer to https://docs.amplify.aws/react/build-a-backend/storage/authorization/#for-gen-1-public-protected-and-private-access-pattern.', + ), + ]), + ), + ); + } + + if (Object.keys(triggers).length) { + propertyAssignments.push(createTriggersProperty(triggers)); + for (const value of Object.values(triggers)) { + const functionName = value.source.split('/')[3]; + if (!namedImports[`./${functionName}/resource`]) { + namedImports[`./${functionName}/resource`] = new Set(); + } + namedImports[`./${functionName}/resource`].add(functionName); + } + } + const storageArgs = factory.createObjectLiteralExpression(propertyAssignments); + return renderResourceTsFile({ + backendFunctionConstruct: 'defineStorage', + exportedVariableName: factory.createIdentifier('storage'), + functionCallParameter: storageArgs, + postImportStatements, + additionalImportedBackendIdentifiers: namedImports, + }); +}; diff --git a/packages/amplify-gen2-codegen/src/test_utils/import_regex.ts b/packages/amplify-gen2-codegen/src/test_utils/import_regex.ts new file mode 100644 index 00000000000..2dbcae92550 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/test_utils/import_regex.ts @@ -0,0 +1,2 @@ +export const getImportRegex = (importIdentifier: string, importPackage: string) => + new RegExp(`import[\\s\\{a-zA-Z,]*${importIdentifier}[\\s,a-zA-Z]*\\} from "${importPackage}";`); diff --git a/packages/amplify-gen2-codegen/src/test_utils/ts_node_printer.ts b/packages/amplify-gen2-codegen/src/test_utils/ts_node_printer.ts new file mode 100644 index 00000000000..30d588bb1d0 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/test_utils/ts_node_printer.ts @@ -0,0 +1,14 @@ +import ts from 'typescript'; + +export const printNode = (node: ts.Node) => { + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const sourceFile = ts.createSourceFile('output.ts', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS); + const source = printer.printNode(ts.EmitHint.Unspecified, node, sourceFile); + return source; +}; +export const printNodeArray = (nodeArray: ts.NodeArray) => { + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const sourceFile = ts.createSourceFile('output.ts', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS); + const source = printer.printList(ts.ListFormat.MultiLine, nodeArray, sourceFile); + return source; +}; diff --git a/packages/amplify-gen2-codegen/src/todo_error.test.ts b/packages/amplify-gen2-codegen/src/todo_error.test.ts new file mode 100644 index 00000000000..1e7ce5dffd5 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/todo_error.test.ts @@ -0,0 +1,15 @@ +import assert from 'node:assert'; +import { printNode } from './test_utils/ts_node_printer'; +import { createTodoError } from './todo_error'; + +describe('TODO error', () => { + it('prepends TODO: to the message text', () => { + const message = 'helloWorld'; + const source = printNode(createTodoError(message)); + assert.match(source, new RegExp(`TODO: ${message}`)); + }); + it('creates the correct throws syntax', () => { + const source = printNode(createTodoError('')); + assert.match(source, /throw new Error\("TODO: "\)/); + }); +}); diff --git a/packages/amplify-gen2-codegen/src/todo_error.ts b/packages/amplify-gen2-codegen/src/todo_error.ts new file mode 100644 index 00000000000..07ea383bed2 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/todo_error.ts @@ -0,0 +1,7 @@ +import ts from 'typescript'; +const factory = ts.factory; + +export const createTodoError = (todoMessage: string) => + factory.createThrowStatement( + factory.createNewExpression(factory.createIdentifier('Error'), undefined, [factory.createStringLiteral(`TODO: ${todoMessage}`)]), + ); diff --git a/packages/amplify-gen2-codegen/src/ts_factory_utils.ts b/packages/amplify-gen2-codegen/src/ts_factory_utils.ts new file mode 100644 index 00000000000..b394ea02ec6 --- /dev/null +++ b/packages/amplify-gen2-codegen/src/ts_factory_utils.ts @@ -0,0 +1,5 @@ +import ts from 'typescript'; + +const factory = ts.factory; + +export const newLineIdentifier = factory.createIdentifier('\n'); diff --git a/packages/amplify-gen2-codegen/tsconfig.json b/packages/amplify-gen2-codegen/tsconfig.json new file mode 100644 index 00000000000..0d3b1836861 --- /dev/null +++ b/packages/amplify-gen2-codegen/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "extends": "../../tsconfig.base.json" +} diff --git a/packages/amplify-go-function-runtime-provider/CHANGELOG.md b/packages/amplify-go-function-runtime-provider/CHANGELOG.md index 38bddc82d9c..91a007f50bd 100644 --- a/packages/amplify-go-function-runtime-provider/CHANGELOG.md +++ b/packages/amplify-go-function-runtime-provider/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.3.52-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-go-function-runtime-provider@2.3.51-next-7.0...amplify-go-function-runtime-provider@2.3.52-next-11.0) (2025-05-01) + +**Note:** Version bump only for package amplify-go-function-runtime-provider + + + + + ## [2.3.51](https://github.com/aws-amplify/amplify-cli/compare/amplify-go-function-runtime-provider@2.3.50...amplify-go-function-runtime-provider@2.3.51) (2025-04-17) **Note:** Version bump only for package amplify-go-function-runtime-provider diff --git a/packages/amplify-go-function-runtime-provider/package.json b/packages/amplify-go-function-runtime-provider/package.json index b8704f5c7b3..5e05623f955 100644 --- a/packages/amplify-go-function-runtime-provider/package.json +++ b/packages/amplify-go-function-runtime-provider/package.json @@ -1,6 +1,6 @@ { "name": "amplify-go-function-runtime-provider", - "version": "2.3.51", + "version": "2.3.52-next-11.0", "description": "Provides functionality related to functions in Go 1.x on AWS", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "archiver": "^5.3.0", "execa": "^5.1.1", diff --git a/packages/amplify-graphiql-explorer/CHANGELOG.md b/packages/amplify-graphiql-explorer/CHANGELOG.md index ab6f1736b20..41e75b13449 100644 --- a/packages/amplify-graphiql-explorer/CHANGELOG.md +++ b/packages/amplify-graphiql-explorer/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.6.2-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-graphiql-explorer@2.6.1-next-7.0...@aws-amplify/amplify-graphiql-explorer@2.6.2-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-graphiql-explorer + + + + + ## [2.6.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-graphiql-explorer@2.6.0...@aws-amplify/amplify-graphiql-explorer@2.6.1) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-graphiql-explorer diff --git a/packages/amplify-graphiql-explorer/package.json b/packages/amplify-graphiql-explorer/package.json index e69eb0eff4a..b658b201e10 100644 --- a/packages/amplify-graphiql-explorer/package.json +++ b/packages/amplify-graphiql-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-graphiql-explorer", - "version": "2.6.1", + "version": "2.6.2-next-11.0", "private": true, "dependencies": { "@babel/core": "^7.23.2", diff --git a/packages/amplify-java-function-runtime-provider/CHANGELOG.md b/packages/amplify-java-function-runtime-provider/CHANGELOG.md index cbfc9e0303b..a9a2a5149a0 100644 --- a/packages/amplify-java-function-runtime-provider/CHANGELOG.md +++ b/packages/amplify-java-function-runtime-provider/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.3.52-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-java-function-runtime-provider@2.3.51-next-7.0...amplify-java-function-runtime-provider@2.3.52-next-11.0) (2025-05-01) + +**Note:** Version bump only for package amplify-java-function-runtime-provider + + + + + ## [2.3.51](https://github.com/aws-amplify/amplify-cli/compare/amplify-java-function-runtime-provider@2.3.50...amplify-java-function-runtime-provider@2.3.51) (2025-04-17) **Note:** Version bump only for package amplify-java-function-runtime-provider diff --git a/packages/amplify-java-function-runtime-provider/package.json b/packages/amplify-java-function-runtime-provider/package.json index 5d7e9bbfaf2..e95539d3ed2 100644 --- a/packages/amplify-java-function-runtime-provider/package.json +++ b/packages/amplify-java-function-runtime-provider/package.json @@ -1,6 +1,6 @@ { "name": "amplify-java-function-runtime-provider", - "version": "2.3.51", + "version": "2.3.52-next-11.0", "description": "Provides functionality related to functions in JAVA on AWS", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "execa": "^5.1.1", "fs-extra": "^8.1.0", diff --git a/packages/amplify-migration-e2e/.gitignore b/packages/amplify-migration-e2e/.gitignore new file mode 100644 index 00000000000..ff0ed341184 --- /dev/null +++ b/packages/amplify-migration-e2e/.gitignore @@ -0,0 +1,2 @@ +output/ +junit.xml diff --git a/packages/amplify-migration-e2e/CHANGELOG.md b/packages/amplify-migration-e2e/CHANGELOG.md new file mode 100644 index 00000000000..407a5719f23 --- /dev/null +++ b/packages/amplify-migration-e2e/CHANGELOG.md @@ -0,0 +1,182 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-10.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-11.0) (2025-05-01) + + +### Bug Fixes + +* add junit xml to .gitignore ([a150cdd](https://github.com/aws-amplify/amplify-cli/commit/a150cddcf1ccd539d4fa5e015197e342b94fcf47)) +* **amplify-migration-e2e:** account for stack already deleted before next poll ([ee5a251](https://github.com/aws-amplify/amplify-cli/commit/ee5a251a0771f585780ad38e7f8fc500efa43a14)) +* migration test file extension ([74b58eb](https://github.com/aws-amplify/amplify-cli/commit/74b58ebf250b9a0ffbf7469fae76ca758a7b077a)) +* remove junit.xml ([04a08d6](https://github.com/aws-amplify/amplify-cli/commit/04a08d6f626cc3e81726a0b32031b0b09470494f)) +* revert gen2 migration e2e test ([f96799f](https://github.com/aws-amplify/amplify-cli/commit/f96799f23ccd1b78a39214f2b006f465430c396c)) + + + + + +# [0.1.0-next-10.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-9.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-10.0) (2025-04-24) + + +### Bug Fixes + +* lint errors in migration e2e ([094e7a7](https://github.com/aws-amplify/amplify-cli/commit/094e7a75d2b83c30d200375d297ab2329541160f)) +* remove extraneous console log ([2f87078](https://github.com/aws-amplify/amplify-cli/commit/2f8707850db40a274508e1b423c0d62def8fc2a5)) + + + + + +# [0.1.0-next-9.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-7.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-9.0) (2025-04-22) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-6.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-7.0) (2025-04-19) + + +### Bug Fixes + +* use non-tagged package versions ([a9c772a](https://github.com/aws-amplify/amplify-cli/commit/a9c772a2e88518fe05e7ba639e4c506d2d411010)) + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-5.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-6.0) (2025-03-21) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-4.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-5.0) (2025-03-19) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# [0.1.0-next-4.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-3.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-4.0) (2025-03-12) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# [0.1.0-next-3.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next-2.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-3.0) (2025-03-05) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# [0.1.0-next-2.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-next.0...@aws-amplify/amplify-migration-e2e@0.1.0-next-2.0) (2025-02-26) + + +### Bug Fixes + +* **migrate:** update e2e core version ([efb07d0](https://github.com/aws-amplify/amplify-cli/commit/efb07d068990255e1ad8ec743c4802b20c5ec6da)) + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-beta-latest.0...@aws-amplify/amplify-migration-e2e@0.1.0-next.0) (2025-02-14) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-alpha.1...@aws-amplify/amplify-migration-e2e@0.1.0-beta-latest.0) (2025-02-12) + + +### Bug Fixes + +* merge issues ([4fb77af](https://github.com/aws-amplify/amplify-cli/commit/4fb77afbcd85dee95603808ad9610b3e93980046)) + + + + + +# [0.1.0-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-alpha.0...@aws-amplify/amplify-migration-e2e@0.1.0-alpha.1) (2024-12-05) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# [0.1.0-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-gen2-migrations-alpha.0...@aws-amplify/amplify-migration-e2e@0.1.0-alpha.0) (2024-11-21) + + +### Bug Fixes + +* add function comments for better readability ([b9326c0](https://github.com/aws-amplify/amplify-cli/commit/b9326c076be9d1e35fa659e97fae92b3ea8732d1)) +* add necessary comment for removal of fields from resource property ([bd7aa34](https://github.com/aws-amplify/amplify-cli/commit/bd7aa34ecd6f607bb1f9a5892b3cda4fe6a1ce43)) +* add necessary comments ([f0e6b9e](https://github.com/aws-amplify/amplify-cli/commit/f0e6b9e2cbda85fedaa5f9a50e55f2c458273b0f)) +* remove typo comments ([07949c4](https://github.com/aws-amplify/amplify-cli/commit/07949c43fcb60d03a07dcfabf7a1995460ad623c)) +* resolve lint errors ([d8066c2](https://github.com/aws-amplify/amplify-cli/commit/d8066c2405159bf5375947ae3634d98960be4d6e)) + + +### Features + +* add identitypool and userpoolclient resource assertions ([dbf44b4](https://github.com/aws-amplify/amplify-cli/commit/dbf44b417f56692be3608d684dc8f31f985b6df8)) +* add identitypool and userpoolclient resource assertions ([b18d35f](https://github.com/aws-amplify/amplify-cli/commit/b18d35fd9b76e7419d9bba591aaa6273ece69f80)) +* added tests for rollback commands of templategen command ([7098ec9](https://github.com/aws-amplify/amplify-cli/commit/7098ec950ba335658ddc4739f7ddc88534d534aa)) +* templategen command e2e integration tests ([8f2ede1](https://github.com/aws-amplify/amplify-cli/commit/8f2ede1045f09f7ab5d21d0bc91eb6eee6455761)) + + + + + +# [0.1.0-gen2-migrations-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-gen2-migration-test-alpha.0...@aws-amplify/amplify-migration-e2e@0.1.0-gen2-migrations-alpha.0) (2024-10-10) + + +### Bug Fixes + +* modify function names and refactor code ([fbc7f7a](https://github.com/aws-amplify/amplify-cli/commit/fbc7f7acc5958bad6297289495adfe5a1215df24)) +* yarn.lock changes and code scanning warnings resolution ([b97f58a](https://github.com/aws-amplify/amplify-cli/commit/b97f58a783512eecd0943a7eaf49c28055962cc8)) + + +### Features + +* add data category integration test ([cf325ba](https://github.com/aws-amplify/amplify-cli/commit/cf325ba9d0efcd45b8812db08c6476734617c914)) +* basic e2e integration test flow for migration tool ([23a93d4](https://github.com/aws-amplify/amplify-cli/commit/23a93d4be16fa31516703d9083483fae18fd5db7)) +* storage and functions codegen integration tests ([0ecad3f](https://github.com/aws-amplify/amplify-cli/commit/0ecad3ff993fa3af67633f674b9abc064845d2e0)) + + + + + +# [0.1.0-gen2-migration-test-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-e2e@0.1.0-gen2-migrations-test.0...@aws-amplify/amplify-migration-e2e@0.1.0-gen2-migration-test-alpha.0) (2024-09-26) + +**Note:** Version bump only for package @aws-amplify/amplify-migration-e2e + + + + + +# 0.1.0-gen2-migrations-test.0 (2024-09-23) + + +### Bug Fixes + +* make gen2 migration packages public ([a7832cb](https://github.com/aws-amplify/amplify-cli/commit/a7832cb622cabf3eec3f770393477256117ea47d)) + + +### Features + +* **cli:** initial migration merge ([#13856](https://github.com/aws-amplify/amplify-cli/issues/13856)) ([ebe5cd0](https://github.com/aws-amplify/amplify-cli/commit/ebe5cd046cfb18c38ffdce17610ed3a133cc9d44)) diff --git a/packages/amplify-migration-e2e/jest.config.js b/packages/amplify-migration-e2e/jest.config.js new file mode 100644 index 00000000000..d645fc5cf64 --- /dev/null +++ b/packages/amplify-migration-e2e/jest.config.js @@ -0,0 +1,44 @@ +module.exports = { + preset: 'ts-jest', + verbose: false, + testRunner: '@aws-amplify/amplify-e2e-core/runner', + testEnvironment: '@aws-amplify/amplify-e2e-core/environment', + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + diagnostics: false, + }, + ], + }, + testEnvironmentOptions: { + url: 'http://localhost', + }, + testRegex: 'src/__tests__/.*\\.test\\.ts$', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/__tests__/**', '!src/**/*.test.(ts|tsx|js|jsx)$', '!**/*.d.ts'], + reporters: [ + 'default', + 'jest-junit', + [ + '@aws-amplify/amplify-e2e-core/reporter', + { + publicPath: './amplify-migration-e2e-reports', + filename: 'index.html', + expand: true, + }, + ], + [ + '@aws-amplify/amplify-e2e-core/failed-test-reporter', + { + reportPath: './amplify-migration-e2e-reports/amplify-migration-e2e-failed-test.txt', + }, + ], + ], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + setupFilesAfterEnv: ['/src/setup-tests.ts'], + moduleNameMapper: { + '^uuid$': require.resolve('uuid'), + '^yaml$': require.resolve('yaml'), + }, +}; diff --git a/packages/amplify-migration-e2e/package.json b/packages/amplify-migration-e2e/package.json new file mode 100644 index 00000000000..30d1744b711 --- /dev/null +++ b/packages/amplify-migration-e2e/package.json @@ -0,0 +1,59 @@ +{ + "name": "@aws-amplify/amplify-migration-e2e", + "private": true, + "version": "0.1.0-next-11.0", + "main": "index.js", + "dependencies": { + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-e2e-core": "5.7.5-next-11.0", + "@aws-amplify/amplify-gen2-codegen": "0.1.0-next-9.0", + "@aws-sdk/client-appsync": "^3.666.0", + "@aws-sdk/client-cloudcontrol": "^3.658.1", + "@aws-sdk/client-cloudformation": "^3.787.0", + "@aws-sdk/client-cognito-identity": "^3.670.0", + "@aws-sdk/client-s3": "^3.674.0", + "execa": "^5.1.1", + "fs-extra": "^8.1.0", + "lodash": "^4.17.21" + }, + "devDependencies": { + "jest": "^29.5.0", + "ts-node": "^10.4.0", + "typescript": "^5.4.5" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "exit 0", + "pretest": "mkdir -p coverage", + "e2e-migration": "yarn setup-profile && jest --verbose --config=jest.config.js", + "setup-profile": "ts-node ./src/configure_tests.ts" + }, + "author": "", + "license": "Apache-2.0", + "description": "" +} diff --git a/packages/amplify-migration-e2e/src/__tests__/migration_codegen_e2e.test.ts b/packages/amplify-migration-e2e/src/__tests__/migration_codegen_e2e.test.ts new file mode 100644 index 00000000000..b8f563c9aab --- /dev/null +++ b/packages/amplify-migration-e2e/src/__tests__/migration_codegen_e2e.test.ts @@ -0,0 +1,124 @@ +import path from 'node:path'; +import assert from 'node:assert'; +import { createNewProjectDir, generateRandomShortId, getSocialProviders, npmInstall } from '@aws-amplify/amplify-e2e-core'; +import { createGen2Renderer } from '@aws-amplify/amplify-gen2-codegen'; +import { copyFunctionFile, removeErrorThrowsFromFunctionFile } from '../function_utils'; +import { + cleanupProjects, + setupAndPushDefaultGen1Project, + setupAndPushAuthWithMaxOptionsGen1Project, + setupAndPushStorageWithMaxOptionsGen1Project, + runCodegenCommand, + runGen2SandboxCommand, + extractFunctionResourceName, + updateAmplifyBackendPackagesVersion, +} from '..'; +import { + assertStorageWithMaxOptionsGen1Setup, + assertAuthWithMaxOptionsGen1Setup, + assertDefaultGen1Setup, + assertAuthResource, + assertStorageResource, + assertFunctionResource, + assertDataResource, +} from '../assertions'; +import { removeErrorThrowsFromAuthResourceFile } from '../auth_utils'; +import { toggleSandboxSecrets } from '../secrets'; + +void describe('Gen 2 Codegen E2E tests', () => { + void describe('render pipeline', () => { + void it('renders a project with no parameters', async () => { + const pipeline = createGen2Renderer({ + outputDir: path.join(process.env.INIT_CWD ?? './', 'output'), + auth: { + loginOptions: { + email: true, + }, + }, + }); + await assert.doesNotReject(pipeline.render); + }); + }); + void describe('Full Migration Gen 2 Codegen Flow', () => { + let projRoot: string; + let projName: string; + + beforeEach(async () => { + const baseDir = process.env.INIT_CWD ?? process.cwd(); + projRoot = await createNewProjectDir('codegen_e2e_flow_test', path.join(baseDir, '..', '..')); + projName = `test${generateRandomShortId()}`; + }); + + afterEach(async () => { + await cleanupProjects(projRoot, projName); + }); + + void it('should init a project & add auth, function, storage, api with defaults & perform full migration codegen flow', async () => { + // Arrange + await setupAndPushDefaultGen1Project(projRoot, projName); + // Act + const { gen1UserPoolId, gen1ClientIds, gen1IdentityPoolId, gen1FunctionName, gen1BucketName, gen1GraphqlApiId, gen1Region, envName } = + await assertDefaultGen1Setup(projRoot); + runCodegenCommand(projRoot); + copyFunctionFile(projRoot, 'function', gen1FunctionName); + removeErrorThrowsFromFunctionFile(projRoot, 'function', extractFunctionResourceName(gen1FunctionName, envName)); + updateAmplifyBackendPackagesVersion(projRoot); + npmInstall(projRoot); + const gen2StackName = await runGen2SandboxCommand(projRoot, projName); + + // Assert + await assertAuthResource(projRoot, gen1UserPoolId, gen1ClientIds, gen1IdentityPoolId, gen1Region); + await assertStorageResource(projRoot, gen1BucketName, gen1Region); + await assertFunctionResource(projRoot, gen2StackName, gen1FunctionName, gen1Region); + await assertDataResource(projRoot, gen2StackName, gen1GraphqlApiId, gen1Region); + }); + + void it('should init a project where all possible auth options are selected and perform full migration codegen flow ', async () => { + // Arrange + const socialProviders = getSocialProviders(); + Object.entries(socialProviders).forEach(([socialProvider, value]) => { + // we expect APPLE_PRIVATE_KEY_2 in process.env but getSocialProviders returns as APPLE_PRIVATE_KEY + if (socialProvider === 'APPLE_PRIVATE_KEY') { + socialProvider = 'APPLE_PRIVATE_KEY_2'; + } + process.env[socialProvider] = process.env[socialProvider] ?? value; + }); + await setupAndPushAuthWithMaxOptionsGen1Project(projRoot, projName); + + // Act + const { gen1UserPoolId, gen1ClientIds, gen1IdentityPoolId, gen1FunctionName, gen1Region, envName } = + await assertAuthWithMaxOptionsGen1Setup(projRoot); + runCodegenCommand(projRoot); + copyFunctionFile(projRoot, 'auth', gen1FunctionName); + removeErrorThrowsFromAuthResourceFile(projRoot); + updateAmplifyBackendPackagesVersion(projRoot); + npmInstall(projRoot); + await toggleSandboxSecrets(projRoot, projName, 'set'); + removeErrorThrowsFromFunctionFile(projRoot, 'auth', extractFunctionResourceName(gen1FunctionName, envName)); + const gen2StackName = await runGen2SandboxCommand(projRoot, projName); + await toggleSandboxSecrets(projRoot, projName, 'remove'); + + // Assert + await assertAuthResource(projRoot, gen1UserPoolId, gen1ClientIds, gen1IdentityPoolId, gen1Region); + await assertFunctionResource(projRoot, gen2StackName, gen1FunctionName, gen1Region); + }); + + void it('should init a project where default auth, all possible s3 bucket resource options are selected and perform full migration codegen flow ', async () => { + // Arrange + await setupAndPushStorageWithMaxOptionsGen1Project(projRoot, projName); + + // Act + const { gen1UserPoolId, gen1ClientIds, gen1BucketName, gen1IdentityPoolId, gen1Region, gen1FunctionName, envName } = + await assertStorageWithMaxOptionsGen1Setup(projRoot); + runCodegenCommand(projRoot); + updateAmplifyBackendPackagesVersion(projRoot); + npmInstall(projRoot); + removeErrorThrowsFromFunctionFile(projRoot, 'storage', extractFunctionResourceName(gen1FunctionName, envName)); + await runGen2SandboxCommand(projRoot, projName); + + // Assert + await assertAuthResource(projRoot, gen1UserPoolId, gen1ClientIds, gen1IdentityPoolId, gen1Region); + await assertStorageResource(projRoot, gen1BucketName, gen1Region); + }); + }); +}); diff --git a/packages/amplify-migration-e2e/src/__tests__/migration_templategen_e2e.test.ts b/packages/amplify-migration-e2e/src/__tests__/migration_templategen_e2e.test.ts new file mode 100644 index 00000000000..499c97079de --- /dev/null +++ b/packages/amplify-migration-e2e/src/__tests__/migration_templategen_e2e.test.ts @@ -0,0 +1,69 @@ +import path from 'node:path'; +import assert from 'node:assert'; +import { createNewProjectDir, npmInstall, generateRandomShortId } from '@aws-amplify/amplify-e2e-core'; +import { assertDefaultGen1Setup } from '../assertions'; +import { + setupAndPushDefaultGen1Project, + runCodegenCommand, + runGen2SandboxCommand, + cleanupProjects, + extractFunctionResourceName, + updateAmplifyBackendPackagesVersion, +} from '..'; +import { copyFunctionFile, removeErrorThrowsFromFunctionFile } from '../function_utils'; +import { + assertExecuteCommand, + assertRevertCommand, + RefactorCategory, + runExecuteCommand, + runGen2DeployPostExecute, + runRevertCommand, +} from '../templategen'; + +const CATEGORIES_TO_MOVE: RefactorCategory[] = ['auth', 'storage']; + +void describe('Templategen E2E tests', () => { + void describe('Full Migration Templategen Flow', () => { + let projRoot: string; + let projName: string; + let gen2StackName: string; + + beforeEach(async () => { + const baseDir = process.env.INIT_CWD ?? process.cwd(); + projRoot = await createNewProjectDir('templategen_e2e_flow_test', path.join(baseDir, '..', '..')); + projName = `test${generateRandomShortId()}`; + }); + + afterEach(async () => { + await cleanupProjects(projRoot, projName, true, gen2StackName); + }); + + void it('should init a project with auth, function, storage, api & perform execute followed by revert', async () => { + // Arrange + await setupAndPushDefaultGen1Project(projRoot, projName); + + // Act + const { gen1StackName, gen1FunctionName, envName } = await assertDefaultGen1Setup(projRoot); + assert(gen1StackName); + runCodegenCommand(projRoot); + copyFunctionFile(projRoot, 'function', gen1FunctionName); + removeErrorThrowsFromFunctionFile(projRoot, 'function', extractFunctionResourceName(gen1FunctionName, envName)); + updateAmplifyBackendPackagesVersion(projRoot); + npmInstall(projRoot); + // Below env is only needed for CI/CD deployments and is expected to be set by customers for their app + // To emulate the migration in sandbox, we set it explicitly. + process.env.AMPLIFY_GEN_1_ENV_NAME = envName; + gen2StackName = await runGen2SandboxCommand(projRoot, projName); + assert(gen2StackName); + + runExecuteCommand(projRoot, gen1StackName, gen2StackName); + await runGen2DeployPostExecute(projRoot, projName, envName, CATEGORIES_TO_MOVE); + + // Assert + await assertExecuteCommand(projRoot, CATEGORIES_TO_MOVE); + + runRevertCommand(projRoot, gen1StackName, gen2StackName); + await assertRevertCommand(projRoot, CATEGORIES_TO_MOVE); + }); + }); +}); diff --git a/packages/amplify-migration-e2e/src/api_utils.ts b/packages/amplify-migration-e2e/src/api_utils.ts new file mode 100644 index 00000000000..aaff2394cb6 --- /dev/null +++ b/packages/amplify-migration-e2e/src/api_utils.ts @@ -0,0 +1,29 @@ +import { getProjectSchema } from '@aws-amplify/amplify-e2e-core'; +import { removeErrorThrows } from './index'; +import * as fs from 'fs-extra'; +import path from 'node:path'; + +export function copyGen1Schema(projRoot: string, projName: string) { + const gen1Schema = getProjectSchema(path.join(projRoot, '.amplify', 'migration'), projName); + + const dataResourcePath = path.join(projRoot, 'amplify', 'data', 'resource.ts'); + const dataResourceContent = fs.readFileSync(dataResourcePath, 'utf-8'); + + const backendPath = path.join(projRoot, 'amplify', 'backend.ts'); + let backendContent = fs.readFileSync(backendPath, 'utf-8'); + + const schemaRegex = /"TODO: Add your existing graphql schema here"/; + const updatedContent = dataResourceContent.replace(schemaRegex, `\`${gen1Schema.trim()}\``); + + const finalContent = removeErrorThrows(updatedContent); + + fs.writeFileSync(dataResourcePath, finalContent, 'utf-8'); + + const linesToAdd = ` + const todoTable = backend.data.resources.cfnResources.additionalCfnResources['Todo']; + todoTable.addOverride('Properties.sseSpecification', { sseEnabled: false }); + `; + + backendContent += linesToAdd; + fs.writeFileSync(backendPath, backendContent, 'utf-8'); +} diff --git a/packages/amplify-migration-e2e/src/assertions.ts b/packages/amplify-migration-e2e/src/assertions.ts new file mode 100644 index 00000000000..50ab3d610f6 --- /dev/null +++ b/packages/amplify-migration-e2e/src/assertions.ts @@ -0,0 +1,355 @@ +import { + getProjectMeta, + getUserPool, + checkIfBucketExists, + getFunction, + getAppSyncApi, + describeCloudFormationStack, + getUserPoolClients, +} from '@aws-amplify/amplify-e2e-core'; +import { getProjectOutputs } from './projectOutputs'; +import { getAppSyncDataSource, getIdentityPool, getResourceDetails } from './sdk_calls'; +import { removeProperties } from '.'; +import { $TSAny } from '@aws-amplify/amplify-cli-core'; +import assert from 'node:assert'; + +const DATA_SOURCE_PROPS_TO_REMOVE = ['dataSourceArn', 'serviceRoleArn', 'dynamodbConfig']; + +export async function assertUserPool(gen1Meta: $TSAny, gen1Region: string) { + const { UserPoolId: gen1UserPoolId } = Object.keys(gen1Meta.auth).map((key) => gen1Meta.auth[key])[0].output; + const cloudUserPool = await getUserPool(gen1UserPoolId, gen1Region); + expect(cloudUserPool.UserPool).toBeDefined(); + return { gen1UserPoolId }; +} + +export async function assertUserPoolClients(gen1Meta: $TSAny, gen1Region: string) { + const { + UserPoolId: userPoolId, + AppClientIDWeb: appClientIdWeb, + AppClientID: appClientId, + } = Object.keys(gen1Meta.auth).map((key) => gen1Meta.auth[key])[0].output; + const gen1ClientIds = [appClientIdWeb, appClientId]; + const clients = await getUserPoolClients(userPoolId, gen1ClientIds, gen1Region); + expect(clients).toHaveLength(2); + return { gen1ClientIds }; +} + +export async function assertIdentityPool(gen1Meta: $TSAny, gen1Region: string) { + const { IdentityPoolId: gen1IdentityPoolId } = Object.keys(gen1Meta.auth).map((key) => gen1Meta.auth[key])[0].output; + const cloudIdentityPool = await getIdentityPool(gen1IdentityPoolId, gen1Region); + expect(cloudIdentityPool).toBeDefined(); + return { gen1IdentityPoolId }; +} + +async function assertFunction(gen1Meta: $TSAny, gen1Region: string) { + const { Arn: gen1FunctionArn, Name: gen1FunctionName } = Object.keys(gen1Meta.function).map((key) => gen1Meta.function[key])[0].output; + expect(gen1FunctionArn).toBeDefined(); + expect(gen1FunctionName).toBeDefined(); + assert(typeof gen1FunctionName === 'string'); + const cloudFunction = await getFunction(gen1FunctionName, gen1Region); + expect(cloudFunction.Configuration?.FunctionArn).toEqual(gen1FunctionArn); + return { gen1FunctionName }; +} + +export async function assertStorage(gen1Meta: $TSAny, gen1Region: string) { + const { BucketName: gen1BucketName } = Object.keys(gen1Meta.storage).map((key) => gen1Meta.storage[key])[0].output; + expect(gen1BucketName).toBeDefined(); + assert(typeof gen1BucketName === 'string'); + const bucketExists = await checkIfBucketExists(gen1BucketName, gen1Region); + expect(bucketExists).toMatchObject({}); + return { gen1BucketName }; +} + +async function assertAPI(gen1Meta: $TSAny, gen1Region: string) { + const { + GraphQLAPIIdOutput: gen1GraphqlApiId, + GraphQLAPIEndpointOutput, + GraphQLAPIKeyOutput, + } = Object.keys(gen1Meta.api).map((key) => gen1Meta.api[key])[0].output; + const { graphqlApi } = await getAppSyncApi(gen1GraphqlApiId, gen1Region); + + expect(gen1GraphqlApiId).toBeDefined(); + expect(GraphQLAPIEndpointOutput).toBeDefined(); + expect(GraphQLAPIKeyOutput).toBeDefined(); + + expect(graphqlApi).toBeDefined(); + expect(graphqlApi?.apiId).toEqual(gen1GraphqlApiId); + return { gen1GraphqlApiId }; +} + +async function assertUserPoolGroups(gen1Meta: $TSAny) { + const { userPoolGroups } = gen1Meta.auth; + expect(userPoolGroups.service).toEqual('Cognito-UserPool-Groups'); + expect(userPoolGroups.providerPlugin).toEqual('awscloudformation'); + expect(userPoolGroups.dependsOn.length).toBe(1); + expect(userPoolGroups.dependsOn[0].category).toBe('auth'); + expect(userPoolGroups.dependsOn[0].attributes.length).toBe(4); + expect(userPoolGroups.dependsOn[0].attributes).toContain('UserPoolId'); + expect(userPoolGroups.dependsOn[0].attributes).toContain('AppClientIDWeb'); + expect(userPoolGroups.dependsOn[0].attributes).toContain('AppClientID'); + expect(userPoolGroups.dependsOn[0].attributes).toContain('IdentityPoolId'); +} + +export async function assertDefaultGen1Setup(projRoot: string) { + const gen1Meta = getProjectMeta(projRoot); + const gen1StackName = gen1Meta.providers.awscloudformation.StackName; + const gen1Region = gen1Meta.providers.awscloudformation.Region; + assert(gen1StackName && typeof gen1StackName === 'string', 'Gen1 stack name not found in meta file'); + const envName = gen1StackName.split('-')[2]; + const { gen1UserPoolId } = await assertUserPool(gen1Meta, gen1Region); + const { gen1FunctionName } = await assertFunction(gen1Meta, gen1Region); + assert.doesNotMatch(gen1FunctionName, /PostConfirmation/); + const { gen1BucketName } = await assertStorage(gen1Meta, gen1Region); + const { gen1GraphqlApiId } = await assertAPI(gen1Meta, gen1Region); + const { gen1IdentityPoolId } = await assertIdentityPool(gen1Meta, gen1Region); + const { gen1ClientIds } = await assertUserPoolClients(gen1Meta, gen1Region); + return { + gen1StackName, + gen1UserPoolId, + gen1ClientIds, + gen1IdentityPoolId, + gen1FunctionName, + gen1BucketName, + gen1GraphqlApiId, + gen1Region, + envName, + }; +} + +export async function assertAuthWithMaxOptionsGen1Setup(projRoot: string) { + const gen1Meta = getProjectMeta(projRoot); + const gen1StackName = gen1Meta.providers.awscloudformation.StackName; + const gen1Region = gen1Meta.providers.awscloudformation.Region; + const { gen1UserPoolId } = await assertUserPool(gen1Meta, gen1Region); + const { gen1FunctionName } = await assertFunction(gen1Meta, gen1Region); + assert.match(gen1FunctionName, /PostConfirmation/); + const { gen1IdentityPoolId } = await assertIdentityPool(gen1Meta, gen1Region); + const { gen1ClientIds } = await assertUserPoolClients(gen1Meta, gen1Region); + await assertUserPoolGroups(gen1Meta); + const envName = gen1StackName.split('-')[2]; + + return { gen1UserPoolId, gen1ClientIds, gen1IdentityPoolId, gen1FunctionName, gen1Region, envName }; +} + +export async function assertStorageWithMaxOptionsGen1Setup(projRoot: string) { + const gen1Meta = getProjectMeta(projRoot); + const gen1StackName = gen1Meta.providers.awscloudformation.StackName; + const gen1Region = gen1Meta.providers.awscloudformation.Region; + const { gen1BucketName } = await assertStorage(gen1Meta, gen1Region); + const { gen1UserPoolId } = await assertUserPool(gen1Meta, gen1Region); + const { gen1FunctionName } = await assertFunction(gen1Meta, gen1Region); + assert.match(gen1FunctionName, /S3Trigger/); + const { gen1ClientIds } = await assertUserPoolClients(gen1Meta, gen1Region); + const { gen1IdentityPoolId } = await assertIdentityPool(gen1Meta, gen1Region); + const envName = gen1StackName.split('-')[2]; + + return { gen1UserPoolId, gen1ClientIds, gen1BucketName, gen1IdentityPoolId, gen1Region, gen1FunctionName, envName }; +} + +const extractUserPoolNamePrefix = (userPoolName: string) => { + const [userPoolNamePrefix] = userPoolName.split('-'); + return userPoolNamePrefix; +}; + +async function assertUserPoolResource(projRoot: string, gen1UserPoolId: string, gen1Region: string) { + const gen1Resource = await getResourceDetails('AWS::Cognito::UserPool', gen1UserPoolId, gen1Region); + removeProperties(gen1Resource, ['ProviderURL', 'ProviderName', 'UserPoolId', 'Arn', 'LambdaConfig.PostConfirmation']); + // TODO: remove below line after EmailMessage, EmailSubject, SmsMessage, SmsVerificationMessage, EmailVerificationMessage, EmailVerificationSubject, AccountRecoverySetting inconsistency is fixed + removeProperties(gen1Resource, [ + 'UserPoolTags', + 'VerificationMessageTemplate.EmailMessage', + 'VerificationMessageTemplate.EmailSubject', + 'EmailVerificationSubject', + 'AccountRecoverySetting', + 'EmailVerificationMessage', + ]); + const gen2Meta = getProjectOutputs(projRoot); + const gen2UserPoolId = gen2Meta.auth.user_pool_id; + const gen2Region = gen2Meta.auth.aws_region; + const gen2Resource = await getResourceDetails('AWS::Cognito::UserPool', gen2UserPoolId, gen2Region); + assert(typeof gen1Resource.UserPoolName === 'string'); + assert(typeof gen2Resource.UserPoolName === 'string'); + gen1Resource.UserPoolName = extractUserPoolNamePrefix(gen1Resource.UserPoolName); + gen2Resource.UserPoolName = extractUserPoolNamePrefix(gen2Resource.UserPoolName); + if ( + 'LambdaConfig' in gen2Resource && + gen2Resource.LambdaConfig && + typeof gen2Resource.LambdaConfig === 'object' && + 'PostConfirmation' in gen2Resource.LambdaConfig + ) + assert(gen2Resource.LambdaConfig.PostConfirmation); + removeProperties(gen2Resource, ['ProviderURL', 'ProviderName', 'UserPoolId', 'Arn', 'LambdaConfig.PostConfirmation']); + // TODO: remove below line after EmailMessage, EmailSubject, SmsMessage, SmsVerificationMessage, EmailVerificationMessage, EmailVerificationSubject, AccountRecoverySetting inconsistency is fixed + removeProperties(gen2Resource, [ + 'UserPoolTags', + 'VerificationMessageTemplate.EmailMessage', + 'VerificationMessageTemplate.SmsMessage', + 'VerificationMessageTemplate.EmailSubject', + 'SmsVerificationMessage', + 'EmailVerificationSubject', + 'AccountRecoverySetting', + 'EmailVerificationMessage', + ]); + expect(gen2Resource).toEqual(gen1Resource); +} + +async function assertUserPoolClientsResource(projRoot: string, gen1UserPoolId: string, gen1ClientIds: $TSAny[], gen1Region: string) { + const gen1Resource = await getResourceDetails('AWS::Cognito::UserPoolClient', `${gen1UserPoolId}|${gen1ClientIds[1]}`, gen1Region); + removeProperties(gen1Resource, ['Name', 'ClientName', 'UserPoolId', 'ClientId']); + // TODO: remove below line after all the inconsistencies are fixed + removeProperties(gen1Resource, [ + 'CallbackURLs', + 'AllowedOAuthScopes', + 'TokenValidityUnits', + 'AllowedOAuthFlowsUserPoolClient', + 'SupportedIdentityProviders', + 'AllowedOAuthFlows', + 'ExplicitAuthFlows', + ]); + const gen2Meta = getProjectOutputs(projRoot); + const gen2ClientId = gen2Meta.auth.user_pool_client_id; + const gen2UserPoolId = gen2Meta.auth.user_pool_id; + const gen2Region = gen2Meta.auth.aws_region; + const gen2Resource = await getResourceDetails('AWS::Cognito::UserPoolClient', `${gen2UserPoolId}|${gen2ClientId}`, gen2Region); + removeProperties(gen2Resource, ['Name', 'ClientName', 'UserPoolId', 'ClientId']); + // TODO: remove below line after all the inconsistencies are fixed + removeProperties(gen2Resource, [ + 'CallbackURLs', + 'AllowedOAuthScopes', + 'TokenValidityUnits', + 'AllowedOAuthFlowsUserPoolClient', + 'SupportedIdentityProviders', + 'AllowedOAuthFlows', + 'ExplicitAuthFlows', + 'PreventUserExistenceErrors', + ]); + expect(gen2Resource).toEqual(gen1Resource); +} + +async function assertIdentityPoolResource(projRoot: string, gen1IdentityPoolId: string, gen1Region: string) { + const gen1Resource = await getResourceDetails('AWS::Cognito::IdentityPool', gen1IdentityPoolId, gen1Region); + assert(gen1Resource); + removeProperties(gen1Resource, ['CognitoIdentityProviders', 'Id', 'IdentityPoolName', 'IdentityPoolTags', 'Name']); + // TODO: remove below line after SupportedLoginProviders inconsistency is fixed + removeProperties(gen1Resource, ['SupportedLoginProviders']); + const gen2Meta = getProjectOutputs(projRoot); + const gen2IdentityPoolId = gen2Meta.auth.identity_pool_id; + const gen2Region = gen2Meta.auth.aws_region; + const gen2Resource = await getResourceDetails('AWS::Cognito::IdentityPool', gen2IdentityPoolId, gen2Region); + assert(gen2Resource); + removeProperties(gen2Resource, ['CognitoIdentityProviders', 'Id', 'IdentityPoolName', 'IdentityPoolTags', 'Name']); + // TODO: remove below line after SupportedLoginProviders inconsistency is fixed + removeProperties(gen2Resource, ['SupportedLoginProviders']); + expect(gen2Resource).toEqual(gen1Resource); +} + +export async function assertAuthResource( + projRoot: string, + gen1UserPoolId: string, + gen1ClientIds: $TSAny[], + gen1IdentityPoolId: string, + gen1Region: string, +) { + await assertUserPoolResource(projRoot, gen1UserPoolId, gen1Region); + await assertUserPoolClientsResource(projRoot, gen1UserPoolId, gen1ClientIds, gen1Region); + await assertIdentityPoolResource(projRoot, gen1IdentityPoolId, gen1Region); +} + +export async function assertStorageResource(projRoot: string, gen1BucketName: string, gen1Region: string) { + const gen1Resource = await getResourceDetails('AWS::S3::Bucket', gen1BucketName, gen1Region); + assert(gen1Resource); + removeProperties(gen1Resource, [ + 'DualStackDomainName', + 'DomainName', + 'BucketName', + 'Arn', + 'RegionalDomainName', + 'Tags', + 'WebsiteURL', + 'NotificationConfiguration.LambdaConfigurations[0].Function', + 'NotificationConfiguration.LambdaConfigurations[1].Function', + ]); + // TODO: remove below line after CorsConfiguration.CorsRules[0].Id inconsistency is fixed + removeProperties(gen1Resource, ['CorsConfiguration.CorsRules[0].Id']); + + const gen2Meta = getProjectOutputs(projRoot); + const gen2BucketName = gen2Meta.storage.bucket_name; + const gen2Region = gen2Meta.storage.aws_region; + const gen2Resource = await getResourceDetails('AWS::S3::Bucket', gen2BucketName, gen2Region); + assert(gen2Resource); + if ( + gen1Resource.NotificationConfiguration && + gen2Resource.NotificationConfiguration && + typeof gen2Resource.NotificationConfiguration === 'object' && + 'LambdaConfigurations' in gen2Resource.NotificationConfiguration && + Array.isArray(gen2Resource.NotificationConfiguration.LambdaConfigurations) + ) { + assert(gen2Resource.NotificationConfiguration.LambdaConfigurations[0].Function); + assert(gen2Resource.NotificationConfiguration.LambdaConfigurations[1].Function); + } + removeProperties(gen2Resource, [ + 'DualStackDomainName', + 'DomainName', + 'BucketName', + 'Arn', + 'RegionalDomainName', + 'Tags', + 'WebsiteURL', + 'NotificationConfiguration.LambdaConfigurations[0].Function', + 'NotificationConfiguration.LambdaConfigurations[1].Function', + ]); + + expect(gen2Resource).toEqual(gen1Resource); +} + +export async function assertFunctionResource(projRoot: string, gen2StackName: string, gen1FunctionName: string, gen1Region: string) { + const gen1Resource = await getResourceDetails('AWS::Lambda::Function', gen1FunctionName, gen1Region); + assert(gen1Resource); + removeProperties(gen1Resource, ['Arn', 'FunctionName', 'LoggingConfig.LogGroup', 'Role']); + // TODO: remove below line after Tags inconsistency is fixed + removeProperties(gen1Resource, ['Tags', 'Environment']); + + const gen2Meta = getProjectOutputs(projRoot); + const gen2Region = gen2Meta.auth.aws_region; + const outputs = (await describeCloudFormationStack(gen2StackName, gen2Region)).Outputs; + const gen2FunctionName = JSON.parse(outputs?.find((output) => output.OutputKey === 'definedFunctions')?.OutputValue ?? '[]')[0]; + const gen2Resource = await getResourceDetails('AWS::Lambda::Function', gen2FunctionName, gen2Region); + assert(gen2Resource); + assert(gen2Resource.FunctionName); + removeProperties(gen2Resource, ['Arn', 'FunctionName', 'LoggingConfig.LogGroup', 'Role']); + // TODO: remove below line after Environment.Variables.AMPLIFY_SSM_ENV_CONFIG, Tags inconsistency is fixed + removeProperties(gen2Resource, ['Environment.Variables.AMPLIFY_SSM_ENV_CONFIG', 'Tags', 'Environment']); + + expect(gen2Resource).toEqual(gen1Resource); +} + +export async function assertDataResource(projRoot: string, gen2StackName: string, gen1GraphqlApiId: string, gen1Region: string) { + const gen1Resource = await getAppSyncApi(gen1GraphqlApiId, gen1Region); + const gen1DataSource = (await getAppSyncDataSource(gen1GraphqlApiId, 'TodoTable', gen1Region)) as Record; + removeProperties(gen1DataSource, DATA_SOURCE_PROPS_TO_REMOVE); + removeProperties(gen1Resource.graphqlApi as Record, ['name', 'apiId', 'arn', 'uris', 'tags', 'dns']); + // TODO: remove below line after authenticationType inconsistency is fixed + removeProperties(gen1Resource.graphqlApi as Record, ['authenticationType']); + + const gen2Meta = getProjectOutputs(projRoot); + const gen2Region = gen2Meta.data.aws_region; + const outputs = (await describeCloudFormationStack(gen2StackName, gen2Region)).Outputs; + const gen2GraphqlApiId = outputs?.find((output) => output.OutputKey === 'awsAppsyncApiId')?.OutputValue ?? ''; + const gen2Resource = await getAppSyncApi(gen2GraphqlApiId, gen2Region); + const gen2DataSource = (await getAppSyncDataSource(gen2GraphqlApiId, 'TodoTable', gen1Region)) as Record; + removeProperties(gen2DataSource, DATA_SOURCE_PROPS_TO_REMOVE); + removeProperties(gen2Resource.graphqlApi as Record, [ + 'name', + 'apiId', + 'arn', + 'uris', + 'tags', + 'additionalAuthenticationProviders', + 'dns', + ]); + // TODO: remove below line after authenticationType, userPoolConfig inconsistency is fixed + removeProperties(gen2Resource.graphqlApi as Record, ['authenticationType', 'userPoolConfig']); + + expect(gen2DataSource).toEqual(gen1DataSource); + expect(gen2Resource).toEqual(gen2Resource); +} diff --git a/packages/amplify-migration-e2e/src/auth_utils.ts b/packages/amplify-migration-e2e/src/auth_utils.ts new file mode 100644 index 00000000000..cbcf0d1ad0e --- /dev/null +++ b/packages/amplify-migration-e2e/src/auth_utils.ts @@ -0,0 +1,10 @@ +import path from 'node:path'; +import * as fs from 'fs-extra'; +import { removeErrorThrows } from './index'; + +export function removeErrorThrowsFromAuthResourceFile(projRoot: string) { + const authResourcePath = path.join(projRoot, 'amplify', 'auth', 'resource.ts'); + const authResourceContent = fs.readFileSync(authResourcePath, 'utf-8'); + const finalContent = removeErrorThrows(authResourceContent); + fs.writeFileSync(authResourcePath, finalContent, 'utf-8'); +} diff --git a/packages/amplify-migration-e2e/src/configure_tests.ts b/packages/amplify-migration-e2e/src/configure_tests.ts new file mode 100644 index 00000000000..dc4e6ce37dc --- /dev/null +++ b/packages/amplify-migration-e2e/src/configure_tests.ts @@ -0,0 +1,33 @@ +import { amplifyConfigure as configure, injectSessionToken, isCI } from '@aws-amplify/amplify-e2e-core'; + +async function setupAmplify() { + if (isCI()) { + const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID; + const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY; + const REGION = process.env.CLI_REGION; + if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY || !REGION) { + throw new Error('Please set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and CLI_REGION in .env'); + } + await configure(null, { + accessKeyId: AWS_ACCESS_KEY_ID, + secretAccessKey: AWS_SECRET_ACCESS_KEY, + profileName: 'amplify-integ-test-user', + region: REGION, + }); + if (process.env.AWS_SESSION_TOKEN) { + injectSessionToken('amplify-integ-test-user'); + } + } else { + console.log('AWS Profile is already configured'); + } +} + +process.nextTick(async () => { + try { + await setupAmplify(); + } catch (e) { + console.log(e.stack); + process.exit(1); + } + process.exit(); +}); diff --git a/packages/amplify-migration-e2e/src/envVariables.ts b/packages/amplify-migration-e2e/src/envVariables.ts new file mode 100644 index 00000000000..f31d295dfaa --- /dev/null +++ b/packages/amplify-migration-e2e/src/envVariables.ts @@ -0,0 +1,8 @@ +export const envVariable = { + set: (name: string, value: string): void => { + process.env[name] = value; + }, + delete: (name: string): void => { + delete process.env[name]; + }, +}; diff --git a/packages/amplify-migration-e2e/src/function_utils.ts b/packages/amplify-migration-e2e/src/function_utils.ts new file mode 100644 index 00000000000..04bc2686800 --- /dev/null +++ b/packages/amplify-migration-e2e/src/function_utils.ts @@ -0,0 +1,33 @@ +import path from 'node:path'; +import * as fs from 'fs-extra'; +import { removeErrorThrows } from './index'; + +type BackendCategory = 'auth' | 'storage' | 'function'; +export function copyFunctionFile(projRoot: string, category: string, gen1FunctionName: string) { + const sourcePath = path.join( + projRoot, + '.amplify', + 'migration', + 'amplify', + 'backend', + 'function', + gen1FunctionName.split('-')[0], + 'src', + 'index.js', + ); + const destinationPath = path.join(projRoot, 'amplify', category, gen1FunctionName.split('-')[0], 'handler.ts'); + let content = fs.readFileSync(sourcePath, 'utf8'); + + // Replace the first occurrence of 'event' with 'event: any' + content = content.replace(/(exports\.handler\s*=\s*async\s*\(\s*)event(\s*\))/, '$1event: any$2'); + content = content.replace(/(exports\.handler\s*=\s*async\s*\()(\w+)(\s*,\s*)(\w+)(\s*\))/, '$1$2: any$3$4: any$5'); + content = content.replace(/(const\s+moduleNames\s*=\s*process\.env\.MODULES)(.split\(','\);)/, '$1!$2'); + fs.writeFileSync(destinationPath, content, 'utf8'); +} + +export function removeErrorThrowsFromFunctionFile(projRoot: string, category: BackendCategory, functionResourceName: string) { + const resourcePath = path.join(projRoot, 'amplify', category, functionResourceName, 'resource.ts'); + const resourceContent = fs.readFileSync(resourcePath, 'utf-8'); + const finalContent = removeErrorThrows(resourceContent); + fs.writeFileSync(resourcePath, finalContent, 'utf-8'); +} diff --git a/packages/amplify-migration-e2e/src/gen1ResourceDetailsFetcher.ts b/packages/amplify-migration-e2e/src/gen1ResourceDetailsFetcher.ts new file mode 100644 index 00000000000..02b4cd19f39 --- /dev/null +++ b/packages/amplify-migration-e2e/src/gen1ResourceDetailsFetcher.ts @@ -0,0 +1,41 @@ +import path from 'path'; +import { RefactorCategory } from './templategen'; +import { getProjectMeta } from '@aws-amplify/amplify-e2e-core'; +import { assertIdentityPool, assertStorage, assertUserPool, assertUserPoolClients } from './assertions'; + +const AMPLIFY_GEN1_BACKUP_ROOT_DIR = '.amplify'; +const AMPLIFY_GEN1_BACKUP_MIGRATION_DIR = 'migration'; + +const resolveGen1RootPath = (projRoot: string, isRevert: boolean) => + isRevert ? projRoot : path.join(projRoot, AMPLIFY_GEN1_BACKUP_ROOT_DIR, AMPLIFY_GEN1_BACKUP_MIGRATION_DIR); + +async function getGen1AuthResourceDetails(projRoot: string, isRevert = false) { + const gen1ProjRoot = resolveGen1RootPath(projRoot, isRevert); + const gen1Meta = getProjectMeta(gen1ProjRoot); + const gen1Region = gen1Meta.providers.awscloudformation.Region; + const { gen1UserPoolId } = await assertUserPool(gen1Meta, gen1Region); + const { gen1IdentityPoolId } = await assertIdentityPool(gen1Meta, gen1Region); + const { gen1ClientIds } = await assertUserPoolClients(gen1Meta, gen1Region); + const gen1ClientIdWeb = gen1ClientIds[0]; + const gen1ResourceIds = [gen1UserPoolId, gen1IdentityPoolId, gen1ClientIdWeb]; + + return { gen1ResourceIds }; +} + +async function getGen1StorageResourceDetails(projRoot: string, isRevert = false) { + const gen1ProjRoot = resolveGen1RootPath(projRoot, isRevert); + const gen1Meta = getProjectMeta(gen1ProjRoot); + const gen1Region = gen1Meta.providers.awscloudformation.Region; + const { gen1BucketName } = await assertStorage(gen1Meta, gen1Region); + const gen1ResourceIds = [gen1BucketName]; + return { gen1ResourceIds }; +} + +export async function getGen1ResourceDetails(projRoot: string, category: RefactorCategory, isRevert = false) { + if (category === 'auth') { + return await getGen1AuthResourceDetails(projRoot, isRevert); + } else if (category === 'storage') { + return await getGen1StorageResourceDetails(projRoot, isRevert); + } + throw new Error(`Invalid category for getting Gen 1 resource details ${category}`); +} diff --git a/packages/amplify-migration-e2e/src/gen2ResourceDetailsFetcher.ts b/packages/amplify-migration-e2e/src/gen2ResourceDetailsFetcher.ts new file mode 100644 index 00000000000..f5bae5730d9 --- /dev/null +++ b/packages/amplify-migration-e2e/src/gen2ResourceDetailsFetcher.ts @@ -0,0 +1,28 @@ +import { getProjectOutputs } from './projectOutputs'; +import { RefactorCategory } from './templategen'; + +async function getGen2AuthResourceDetails(projRoot: string) { + const gen2Meta = getProjectOutputs(projRoot); + const gen2UserPoolId = gen2Meta.auth.user_pool_id; + const gen2ClientIdWeb = gen2Meta.auth.user_pool_client_id; + const gen2IdentityPoolId = gen2Meta.auth.identity_pool_id; + const gen2ResourceIds = [gen2UserPoolId, gen2IdentityPoolId, gen2ClientIdWeb]; + + return { gen2ResourceIds }; +} + +async function getGen2StorageResourceDetails(projRoot: string) { + const gen2Meta = getProjectOutputs(projRoot); + const gen2BucketName = gen2Meta.storage.bucket_name; + const gen2ResourceIds = [gen2BucketName]; + return { gen2ResourceIds }; +} + +export async function getGen2ResourceDetails(projRoot: string, category: RefactorCategory) { + if (category === 'auth') { + return await getGen2AuthResourceDetails(projRoot); + } else if (category === 'storage') { + return await getGen2StorageResourceDetails(projRoot); + } + throw new Error(`Invalid category for getting Gen 2 resource details ${category}`); +} diff --git a/packages/amplify-migration-e2e/src/index.ts b/packages/amplify-migration-e2e/src/index.ts new file mode 100644 index 00000000000..1a66e18cdcf --- /dev/null +++ b/packages/amplify-migration-e2e/src/index.ts @@ -0,0 +1,123 @@ +import { + deleteProject as deleteGen1Project, + deleteProjectDir, + initJSProjectWithProfile, + addAuthWithDefault, + amplifyPush, + getNpxPath, + addS3WithGuestAccess, + addFunction, + functionBuild, + addApiWithoutSchema, + updateApiSchema, + amplifyPushForce, + addFeatureFlag, + amplifyPushAuth, + addAuthWithGroupTrigger, + updateAuthAddUserGroups, + updateAuthToAddSignInSignOutUrlAfterPull, + addS3WithTrigger, +} from '@aws-amplify/amplify-e2e-core'; +import path from 'node:path'; +import { unset } from 'lodash'; +import execa from 'execa'; +import { deleteGen2Sandbox, deleteGen2SandboxStack } from './sandbox'; +import assert from 'node:assert'; +import { updatePackageDependency } from './updatePackageJson'; + +export * from './sdk_calls'; +export * from './assertions'; +export * from './projectOutputs'; +export * from './updatePackageJson'; +export * from './sandbox'; + +export const pushTimeoutMS = 1000 * 60 * 20; // 20 minutes; + +export const MIGRATE_TOOL_VERSION = '0.1.0-next-10.0'; +export const BACKEND_DATA_VERSION = '0.0.0-test-20250416182614'; + +export async function setupAndPushDefaultGen1Project(projRoot: string, projName: string) { + await initJSProjectWithProfile(projRoot, { name: projName, disableAmplifyAppCreation: false }); + await addAuthWithDefault(projRoot); + await addFunction(projRoot, { functionTemplate: 'Hello World' }, 'nodejs'); + await functionBuild(projRoot); + await addS3WithGuestAccess(projRoot); + await addApiWithoutSchema(projRoot, { transformerVersion: 2 }); + updateApiSchema(projRoot, projName, 'simple_model.graphql'); + await amplifyPush(projRoot, true); + addFeatureFlag(projRoot, 'graphqltransformer', 'enablegen2migration', true); + await amplifyPushForce(projRoot, true); +} + +export async function setupAndPushAuthWithMaxOptionsGen1Project(projRoot: string, projName: string) { + await initJSProjectWithProfile(projRoot, { name: projName, disableAmplifyAppCreation: false }); + await addAuthWithGroupTrigger(projRoot); + await updateAuthAddUserGroups(projRoot, ['Admins', 'Users']); + await updateAuthToAddSignInSignOutUrlAfterPull(projRoot, { + signinUrl: 'https://signin1.com/', + signoutUrl: 'https://signout1.com/', + testingWithLatestCodebase: true, + updateSigninUrl: 'https://updatesignin1.com/', + updateSignoutUrl: 'https://updatesignout1.com/', + }); + await amplifyPushAuth(projRoot, true); +} + +export async function setupAndPushStorageWithMaxOptionsGen1Project(projRoot: string, projName: string) { + await initJSProjectWithProfile(projRoot, { name: projName, disableAmplifyAppCreation: false }); + await addAuthWithDefault(projRoot); + await addS3WithTrigger(projRoot); + await amplifyPushAuth(projRoot, true); +} + +export function runCodegenCommand(cwd: string) { + console.log(`running codegen command in ${cwd}`); + const processResult = execa.sync(getNpxPath(), [`@aws-amplify/migrate@${MIGRATE_TOOL_VERSION}`, 'to-gen-2', 'prepare'], { + cwd, + env: { ...process.env, npm_config_user_agent: 'npm' }, + encoding: 'utf-8', + }); + if (processResult.exitCode !== 0) { + throw new Error(`Codegen command exit code: ${processResult.exitCode}, message: ${processResult.stderr}`); + } +} + +export async function cleanupProjects(cwd: string, projName: string, isRevert = false, gen2StackName: string | undefined = undefined) { + console.log(`cleaning up project ${projName} at ${cwd}...`); + if (isRevert) { + await deleteGen1Project(cwd); + assert(gen2StackName, 'gen2StackName is required for revert'); + await deleteGen2SandboxStack(cwd, gen2StackName); + } else { + await deleteGen1Project(path.join(cwd, '.amplify', 'migration')); + await deleteGen2Sandbox(cwd, projName); + } + deleteProjectDir(cwd); + console.log(`cleaned up project ${projName} at ${cwd}`); +} + +export function removeProperties(obj: Record, propertiesToRemove: string[]) { + propertiesToRemove.forEach((prop) => unset(obj, prop)); +} + +export function removeErrorThrows(content: string): string { + const lines = content.split('\n'); + const result = lines.filter((line) => { + const trimmedLine = line.trim(); + return !trimmedLine.startsWith('throw new Error('); + }); + return result.join('\n'); +} + +export function extractFunctionResourceName(functionName: string, envName: string): string { + const functionResourceName = functionName.split(`-${envName}`)[0]; + assert(functionResourceName, 'Function resource name not available'); + return functionResourceName; +} + +export function updateAmplifyBackendPackagesVersion(projRoot: string) { + updatePackageDependency(projRoot, '@aws-amplify/backend-data', BACKEND_DATA_VERSION); + updatePackageDependency(projRoot, '@aws-amplify/backend', BACKEND_DATA_VERSION); +} + +export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/packages/amplify-migration-e2e/src/migrationReadmeParser.ts b/packages/amplify-migration-e2e/src/migrationReadmeParser.ts new file mode 100644 index 00000000000..497ad04bcb5 --- /dev/null +++ b/packages/amplify-migration-e2e/src/migrationReadmeParser.ts @@ -0,0 +1,77 @@ +import path from 'node:path'; +import * as fs from 'fs-extra'; +import { RefactorCategory } from './templategen'; + +function extractContent(readmeContent: string, startRegex: string, endRegex: string) { + const pattern = new RegExp(`${startRegex}([\\s\\S]*?)${endRegex}`, 'i'); + const match = readmeContent.match(pattern); + + if (match && match[1]) { + return match[1].trim(); + } + throw new Error('README file parsing failed to get the stack refactor commands'); +} + +function extractCommands(readmeContent: string) { + const pattern = /```([\s\S]*?)```/g; + const matches = readmeContent.matchAll(pattern); + const commands = []; + + for (const match of matches) { + if (match[1]) { + commands.push(match[1].trim()); + } + } + if (commands.length === 0) { + throw new Error('README file parsing failed to get the stack refactor commands'); + } + return commands; +} + +export function readMigrationReadmeFile(projRoot: string, category: RefactorCategory, encoding: BufferEncoding = 'utf8') { + const readmeFilePath = path.join(projRoot, '.amplify', 'migration', 'templates', category, 'MIGRATION_README.md'); + const readmeContent = fs.readFileSync(readmeFilePath, encoding); + return readmeContent; +} + +/** + * Sample from the README file for STEP 1: + * + * ### STEP 1: UPDATE GEN-1 AUTH STACK + * + * It is a non-disruptive update since the template only replaces resource references with their resolved values. This is a required step to execute cloudformation stack refactor later. + * + * ``` + * aws cloudformation update-stack \ + * --stack-name my-auth-stack-name \ + * --template-body file://path/to/template.json \ + * --parameters '[{"ParameterKey":"authRoleArn","ParameterValue":"arn:aws:iam::123456789012:role/my-auth-role"},{"ParameterKey":"autoVerifiedAttributes","ParameterValue":"email"},{"ParameterKey":"allowUnauthenticatedIdentities","ParameterValue":"false"},{"ParameterKey":"smsVerificationMessage","ParameterValue":"Your verification code is {####}"}]' \ + * --capabilities CAPABILITY_NAMED_IAM --tags '[]' + * ``` + * + * ``` + * aws cloudformation describe-stacks \ + * --stack-name my-auth-stack-name + * ``` + */ +export function getStackRefactorCommandsFromReadme(readmeContent: string) { + const step1Content = extractContent(readmeContent, '### STEP 1', '#### Rollback step'); + const step2Content = extractContent(readmeContent, '### STEP 2', '#### Rollback step'); + const step3Content = extractContent(readmeContent, '### STEP 3', '#### Rollback step'); + const step1Commands = extractCommands(step1Content); + const step2commands = extractCommands(step2Content); + const step3Commands = extractCommands(step3Content); + // Pop first command from step3Commands to keep it consistent with step3RollbackCommands + step3Commands.shift(); + return { step1Commands, step2commands, step3Commands }; +} + +export function getRollbackCommandsFromReadme(readmeContent: string) { + const step1Content = extractContent(readmeContent, '#### Rollback step', '### STEP 2'); + const step2Content = extractContent(readmeContent, '### STEP 2[\\s\\S]*?#### Rollback step', '### STEP 3'); + const step3Content = extractContent(readmeContent, '#### Rollback step for refactor:', '### STEP 4'); + const step1RollbackCommands = extractCommands(step1Content); + const step2RollbackCommands = extractCommands(step2Content); + const step3RollbackCommands = extractCommands(step3Content); + return { step1RollbackCommands, step2RollbackCommands, step3RollbackCommands }; +} diff --git a/packages/amplify-migration-e2e/src/projectOutputs.ts b/packages/amplify-migration-e2e/src/projectOutputs.ts new file mode 100644 index 00000000000..b7e1e4065e9 --- /dev/null +++ b/packages/amplify-migration-e2e/src/projectOutputs.ts @@ -0,0 +1,10 @@ +import path from 'node:path'; +import { $TSAny } from '@aws-amplify/amplify-cli-core'; +import { readJsonFile } from '@aws-amplify/amplify-e2e-core'; + +const getProjectOutputsPath = (projectRoot: string) => path.join(projectRoot, 'amplify_outputs.json'); + +export const getProjectOutputs = (projectRoot: string): $TSAny => { + const metaFilePath: string = getProjectOutputsPath(projectRoot); + return readJsonFile(metaFilePath); +}; diff --git a/packages/amplify-migration-e2e/src/sandbox.ts b/packages/amplify-migration-e2e/src/sandbox.ts new file mode 100644 index 00000000000..3f8686d72a1 --- /dev/null +++ b/packages/amplify-migration-e2e/src/sandbox.ts @@ -0,0 +1,43 @@ +import { getNpxPath, nspawn as spawn } from '@aws-amplify/amplify-e2e-core'; +import { deleteStack, getProjectOutputs, pushTimeoutMS } from '.'; +import execa from 'execa'; + +export async function runGen2SandboxCommand(cwd: string, identifier: string) { + const processResult = execa.sync(getNpxPath(), ['ampx', 'sandbox', '--identifier', identifier, '--once'], { + cwd, + env: { ...process.env, npm_config_user_agent: 'npm' }, + encoding: 'utf-8', + }); + console.log('runGen2SandboxCommand', processResult); + if (processResult.exitCode === 0) { + console.log(processResult.stdout); + const match = processResult.stdout.match(/Stack:\s+(.+)/); + console.log('sandbox stack name', match?.[1]); + if (match) { + return match[1]; + } else { + throw new Error('Stack name not found in the command output'); + } + } else { + throw new Error(`Sandbox command exit code: ${processResult.exitCode}, message: ${processResult.stderr}`); + } +} + +export function deleteGen2Sandbox(cwd: string, identifier: string) { + return spawn(getNpxPath(), ['ampx', 'sandbox', 'delete', '--identifier', identifier], { + cwd, + stripColors: true, + noOutputTimeout: pushTimeoutMS, + env: { ...process.env, npm_config_user_agent: 'npm' }, + }) + .wait("Are you sure you want to delete all the resources in your sandbox environment (This can't be undone)?") + .sendConfirmYes() + .wait('Finished deleting.') + .runAsync(); +} + +export async function deleteGen2SandboxStack(projRoot: string, stackName: string) { + const gen2Meta = getProjectOutputs(projRoot); + const region = gen2Meta.auth.aws_region; + await deleteStack(stackName, region); +} diff --git a/packages/amplify-migration-e2e/src/sdk_calls.ts b/packages/amplify-migration-e2e/src/sdk_calls.ts new file mode 100644 index 00000000000..dec9ce8d2e1 --- /dev/null +++ b/packages/amplify-migration-e2e/src/sdk_calls.ts @@ -0,0 +1,104 @@ +import { CloudControlClient, GetResourceCommand } from '@aws-sdk/client-cloudcontrol'; +import { AppSyncClient, GetDataSourceCommand } from '@aws-sdk/client-appsync'; +import { CognitoIdentityClient, DescribeIdentityPoolCommand } from '@aws-sdk/client-cognito-identity'; +import { + CloudFormationClient, + DescribeStackResourcesCommand, + DeleteStackCommand, + DescribeStacksCommand, +} from '@aws-sdk/client-cloudformation'; +import assert from 'node:assert'; +import { delay } from './index'; + +const MAX_ATTEMPTS = 5; +const FIXED_DELAY = 1000; +const CFN_IN_PROGRESS_STATUS = 'IN_PROGRESS'; +const CFN_DELETE_COMPLETE_STATUS = 'DELETE_COMPLETE'; + +export async function getAppSyncDataSource(apiId: string, dataSourceName: string, region: string) { + const client = new AppSyncClient({ region }); + const command = new GetDataSourceCommand({ + apiId: apiId, + name: dataSourceName, + }); + const response = await client.send(command); + return response.dataSource; +} + +export async function getResourceDetails( + typeName: string, + identifier: string, + region: string, + attempts = MAX_ATTEMPTS, +): Promise> { + if (attempts <= 0) { + throw new Error( + `All attempts exhausted while getting resource details from CloudControl API for ${typeName} and ${identifier} identifier in ${region} region`, + ); + } + const client = new CloudControlClient({ region }); + const command = new GetResourceCommand({ + TypeName: typeName, + Identifier: identifier, + }); + try { + const response = await client.send(command); + const resourceProperties = response.ResourceDescription?.Properties; + assert(resourceProperties); + return JSON.parse(resourceProperties); + } catch (e) { + // account for eventual consistency with retries + if (typeof e === 'object' && e !== null && 'message' in e && typeof e.message === 'string' && e.message.includes('NotFound')) { + console.log( + `Attempting to get resource details using CloudControl API for ${typeName} type and ${identifier} identifier in ${region} region: ${ + attempts - 1 + } attempts remaining.`, + ); + await delay(2 ** (MAX_ATTEMPTS - attempts) * FIXED_DELAY); + return getResourceDetails(typeName, identifier, region, attempts - 1); + } + throw e; + } +} + +export async function getIdentityPool(identityPoolId: string, region: string) { + const client = new CognitoIdentityClient({ region }); + const command = new DescribeIdentityPoolCommand({ + IdentityPoolId: identityPoolId, + }); + return await client.send(command); +} +export async function describeStackResources(stackName: string, region: string) { + const cloudformation = new CloudFormationClient({ region }); + const response = await cloudformation.send(new DescribeStackResourcesCommand({ StackName: stackName })); + const stackResources = response.StackResources; + assert(stackResources); + return stackResources; +} + +export async function deleteStack(stackName: string, region: string) { + const cloudformation = new CloudFormationClient({ region }); + await cloudformation.send(new DeleteStackCommand({ StackName: stackName })); + let stackStatus = ''; + do { + try { + const response = await cloudformation.send(new DescribeStacksCommand({ StackName: stackName })); + stackStatus = response.Stacks?.[0].StackStatus ?? ''; + assert(stackStatus !== '', 'Expected stackStatus to be defined'); + await delay(1000); + } catch (e) { + // Need to account for the case where stack is already deleted by the next poll. + if (typeof e === 'object' && e !== null && 'message' in e && typeof e.message === 'string' && e.message.includes('does not exist')) { + logStackDeleteSuccess(stackName, region); + return; + } + throw e; + } + } while (stackStatus.endsWith(CFN_IN_PROGRESS_STATUS)); + + assert(stackStatus === CFN_DELETE_COMPLETE_STATUS, `Expected stack ${stackName} to be deleted but it's in ${stackStatus} state.`); + logStackDeleteSuccess(stackName, region); +} + +const logStackDeleteSuccess = (stackName: string, region: string) => + console.log(`Stack ${stackName} deleted successfully in ${region} region`); diff --git a/packages/amplify-migration-e2e/src/secrets.ts b/packages/amplify-migration-e2e/src/secrets.ts new file mode 100644 index 00000000000..0455864eab3 --- /dev/null +++ b/packages/amplify-migration-e2e/src/secrets.ts @@ -0,0 +1,37 @@ +import { getNpxPath, nspawn as spawn, getSocialProviders } from '@aws-amplify/amplify-e2e-core'; + +export async function toggleSandboxSecrets(cwd: string, identifier: string, option: string) { + const socialProviders = getSocialProviders(true); + const secretsToSet = { + FACEBOOK_CLIENT_ID: socialProviders.FACEBOOK_APP_ID, + FACEBOOK_CLIENT_SECRET: socialProviders.FACEBOOK_APP_SECRET, + GOOGLE_CLIENT_ID: socialProviders.GOOGLE_APP_ID, + GOOGLE_CLIENT_SECRET: socialProviders.GOOGLE_APP_SECRET, + LOGINWITHAMAZON_CLIENT_ID: socialProviders.AMAZON_APP_ID, + LOGINWITHAMAZON_CLIENT_SECRET: socialProviders.AMAZON_APP_SECRET, + SIWA_CLIENT_ID: socialProviders.APPLE_APP_ID, + SIWA_KEY_ID: socialProviders.APPLE_KEY_ID, + SIWA_TEAM_ID: socialProviders.APPLE_TEAM_ID, + SIWA_PRIVATE_KEY: socialProviders.APPLE_PRIVATE_KEY, + }; + + for (const [secretName, secretValue] of Object.entries(secretsToSet)) { + if (secretValue) { + const spawnProcess = await spawn(getNpxPath(), ['ampx', 'sandbox', 'secret', option, secretName, '--identifier', identifier], { + cwd, + stripColors: true, + env: { ...process.env, npm_config_user_agent: 'npm' }, + }); + + if (option === 'set') { + await spawnProcess + .wait('Enter secret value') + .sendLine(secretValue) + .wait(/Successfully created */) + .runAsync(); + } else { + await spawnProcess.wait(/Successfully removed */).runAsync(); + } + } + } +} diff --git a/packages/amplify-migration-e2e/src/setup-tests.ts b/packages/amplify-migration-e2e/src/setup-tests.ts new file mode 100644 index 00000000000..1aea1056cdd --- /dev/null +++ b/packages/amplify-migration-e2e/src/setup-tests.ts @@ -0,0 +1,24 @@ +const removeYarnPaths = () => { + // Remove yarn's temporary PATH modifications as they affect the yarn version used by jest tests when building the lambda functions + process.env.PATH = process.env.PATH.split(':') + .filter((p) => !p.includes('/xfs-') && !p.includes('\\Temp\\xfs-')) + .join(':'); +}; + +removeYarnPaths(); + +const JEST_TIMEOUT = 1000 * 60 * 60; // 1 hour +jest.setTimeout(JEST_TIMEOUT); +if (process.env.CIRCLECI) { + jest.retryTimes(1); +} + +beforeEach(async () => { + if (process.env.CLI_REGION) { + console.log(`CLI_REGION set to: ${process.env.CLI_REGION}. Overwriting AWS_REGION and AWS_DEFAULT_REGION`); + process.env.AWS_REGION = process.env.CLI_REGION; + process.env.AWS_DEFAULT_REGION = process.env.CLI_REGION; + } else { + console.log('No CLI_REGION variable found'); + } +}); diff --git a/packages/amplify-migration-e2e/src/templategen.ts b/packages/amplify-migration-e2e/src/templategen.ts new file mode 100644 index 00000000000..0d01a9a0880 --- /dev/null +++ b/packages/amplify-migration-e2e/src/templategen.ts @@ -0,0 +1,136 @@ +import assert from 'node:assert'; +import execa from 'execa'; +import path from 'node:path'; +import * as fs from 'fs-extra'; +import { getNpxPath, getProjectMeta } from '@aws-amplify/amplify-e2e-core'; +import { runGen2SandboxCommand } from './sandbox'; +import { getGen1ResourceDetails } from './gen1ResourceDetailsFetcher'; +import { getGen2ResourceDetails } from './gen2ResourceDetailsFetcher'; +import { describeStackResources, MIGRATE_TOOL_VERSION } from '.'; + +export type RefactorCategory = 'auth' | 'storage'; + +export function runExecuteCommand(cwd: string, gen1StackName: string, gen2StackName: string) { + console.log(`running execute command in ${cwd} for ${gen1StackName}->${gen2StackName}`); + const parentDir = path.resolve(cwd, '..'); + const processResult = execa.sync( + getNpxPath(), + [ + '--prefix', + parentDir, + `@aws-amplify/migrate@${MIGRATE_TOOL_VERSION}`, + 'to-gen-2', + 'execute', + '--from', + gen1StackName, + '--to', + gen2StackName, + ], + { + cwd, + env: { ...process.env, npm_config_user_agent: 'npm' }, + encoding: 'utf-8', + }, + ); + console.log(processResult); + + if (processResult.exitCode !== 0) { + throw new Error(`Execute command exit code: ${processResult.exitCode}, message: ${processResult.stderr}`); + } +} + +export function runRevertCommand(cwd: string, gen1StackName: string, gen2StackName: string) { + console.log(`running revert command in ${cwd} for ${gen2StackName}->${gen1StackName}`); + const parentDir = path.resolve(cwd, '..'); + const processResult = execa.sync( + getNpxPath(), + [ + '--prefix', + parentDir, + `@aws-amplify/migrate@${MIGRATE_TOOL_VERSION}`, + 'to-gen-2', + 'revert', + '--from', + gen2StackName, + '--to', + gen1StackName, + ], + { + cwd, + env: { ...process.env, npm_config_user_agent: 'npm' }, + encoding: 'utf-8', + }, + ); + console.log(processResult); + + if (processResult.exitCode !== 0) { + throw new Error(`Revert command exit code: ${processResult.exitCode}, message: ${processResult.stderr}`); + } +} + +function uncommentS3BucketLineFromBackendFile(projRoot: string) { + const backendFilePath = path.join(projRoot, 'amplify', 'backend.ts'); + const backendFileContent = fs.readFileSync(backendFilePath, 'utf8'); + const regex = /^\s*\/\/\s*(s3Bucket\.bucketName)/m; + const updatedBackendFileContent = backendFileContent.replace(regex, '$1'); + fs.writeFileSync(backendFilePath, updatedBackendFileContent); +} + +function uncommentTagsLineFromBackendFile(projRoot: string) { + const backendFilePath = path.join(projRoot, 'amplify', 'backend.ts'); + const backendFileContent = fs.readFileSync(backendFilePath, 'utf8'); + const regex = /^\s*\/\/\s*(Tags\.of)/m; + const updatedBackendFileContent = backendFileContent.replace(regex, '$1'); + fs.writeFileSync(backendFilePath, updatedBackendFileContent); +} + +export async function runGen2DeployPostExecute(projRoot: string, projName: string, envName: string, categories: RefactorCategory[]) { + if (categories.includes('storage')) { + uncommentS3BucketLineFromBackendFile(projRoot); + } + uncommentTagsLineFromBackendFile(projRoot); + await runGen2SandboxCommand(projRoot, projName); +} + +export async function assertExecuteCommand(projRoot: string, categories: RefactorCategory[]) { + for (const category of categories) { + console.log(`Asserting post execute for ${category}...`); + + const { gen1ResourceIds } = await getGen1ResourceDetails(projRoot, category); + const { gen2ResourceIds } = await getGen2ResourceDetails(projRoot, category); + + assert.deepEqual(gen1ResourceIds, gen2ResourceIds); + console.log(`Asserted post execute for ${category}`); + } +} + +export async function assertRevertCommand(projRoot: string, categories: RefactorCategory[]) { + const gen1Meta = getProjectMeta(projRoot); + const gen1StackName = gen1Meta.providers.awscloudformation.StackName; + const region = gen1Meta.providers.awscloudformation.Region; + const gen1RootStackResources = await describeStackResources(gen1StackName, region); + for (const category of categories) { + console.log(`Asserting post revert for ${category}...`); + + const { gen1ResourceIds } = await getGen1ResourceDetails(projRoot, category, true); + + assert(gen1StackName); + assert(region); + const gen1CategoryStackResource = gen1RootStackResources.find( + (gen1RootStackResource) => + gen1RootStackResource.ResourceType === 'AWS::CloudFormation::Stack' && + gen1RootStackResource.LogicalResourceId?.startsWith(category), + ); + assert(gen1CategoryStackResource); + const gen1CategoryStackName = gen1CategoryStackResource.PhysicalResourceId; + assert(gen1CategoryStackName); + const gen1StackResources = await describeStackResources(gen1CategoryStackName, region); + for (const gen1ResourceId of gen1ResourceIds) { + assert(gen1ResourceId && typeof gen1ResourceId === 'string'); + const movedGen1Resource = gen1StackResources.some((gen1StackResource) => gen1StackResource.PhysicalResourceId === gen1ResourceId); + assert(movedGen1Resource, `Expected ${gen1ResourceId} to be moved to Gen 1 ${category} stack after revert`); + } + + console.log(`Asserted post revert for ${category}`); + } +} diff --git a/packages/amplify-migration-e2e/src/updatePackageJson.ts b/packages/amplify-migration-e2e/src/updatePackageJson.ts new file mode 100644 index 00000000000..84d85013a56 --- /dev/null +++ b/packages/amplify-migration-e2e/src/updatePackageJson.ts @@ -0,0 +1,14 @@ +import { readJsonFile } from '@aws-amplify/amplify-e2e-core'; +import * as fs from 'fs-extra'; +import path from 'node:path'; + +export function updatePackageDependency(cwd: string, dependencyName: string, version = '0.0.0-test-20241018150827') { + const packageJsonPath = path.join(cwd, 'package.json'); + const packageJson = readJsonFile(packageJsonPath); + + packageJson.devDependencies = packageJson.devDependencies || {}; + packageJson.devDependencies[dependencyName] = version; + + const updatedContent = JSON.stringify(packageJson, null, 2); + fs.writeFileSync(packageJsonPath, updatedContent, 'utf-8'); +} diff --git a/packages/amplify-migration-e2e/tsconfig.json b/packages/amplify-migration-e2e/tsconfig.json new file mode 100644 index 00000000000..60659909dd8 --- /dev/null +++ b/packages/amplify-migration-e2e/tsconfig.json @@ -0,0 +1,16 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "extends": "../../tsconfig.base.json", + "references": [ + { + "path": "../amplify-gen1-codegen-auth-adapter" + }, + { + "path": "../amplify-gen1-codegen-storage-adapter" + }, + { + "path": "../amplify-gen2-codegen" + } + ] +} diff --git a/packages/amplify-migration-template-gen/.gitignore b/packages/amplify-migration-template-gen/.gitignore new file mode 100644 index 00000000000..c3af857904e --- /dev/null +++ b/packages/amplify-migration-template-gen/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/amplify-migration-template-gen/API.md b/packages/amplify-migration-template-gen/API.md new file mode 100644 index 00000000000..30a9e3c5bbd --- /dev/null +++ b/packages/amplify-migration-template-gen/API.md @@ -0,0 +1,32 @@ +## API Report File for "@aws-amplify/migrate-template-gen" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; +import { SSMClient } from '@aws-sdk/client-ssm'; + +// @public (undocumented) +export interface ResourceMapping { + // (undocumented) + Destination: ResourceMappingLocation; + // Warning: (ae-forgotten-export) The symbol "ResourceMappingLocation" needs to be exported by the entry point index.d.ts + // + // (undocumented) + Source: ResourceMappingLocation; +} + +// @public (undocumented) +export class TemplateGenerator { + constructor(fromStack: string, toStack: string, accountId: string, cfnClient: CloudFormationClient, ssmClient: SSMClient, cognitoIdpClient: CognitoIdentityProviderClient, appId: string, environmentName: string); + // (undocumented) + generate(customResourceMap?: ResourceMapping[]): Promise; + // (undocumented) + revert(): Promise; +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/amplify-migration-template-gen/CHANGELOG.md b/packages/amplify-migration-template-gen/CHANGELOG.md new file mode 100644 index 00000000000..f869e8ad1b5 --- /dev/null +++ b/packages/amplify-migration-template-gen/CHANGELOG.md @@ -0,0 +1,199 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-next-10.0...@aws-amplify/migrate-template-gen@0.1.0-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/migrate-template-gen + + + + + +# [0.1.0-next-10.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-next-8.0...@aws-amplify/migrate-template-gen@0.1.0-next-10.0) (2025-04-24) + + +### Bug Fixes + +* remove extraneous console log ([2f87078](https://github.com/aws-amplify/amplify-cli/commit/2f8707850db40a274508e1b423c0d62def8fc2a5)) + + + + + +# [0.1.0-next-8.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-next-7.0...@aws-amplify/migrate-template-gen@0.1.0-next-8.0) (2025-04-21) + + +### Bug Fixes + +* **migrate-template-gen:** initialize non-custom category generator without custom predicate ([84e1933](https://github.com/aws-amplify/amplify-cli/commit/84e193373ba0b3c05b1d65d69a323ac00d8c0f06)) + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-next-6.0...@aws-amplify/migrate-template-gen@0.1.0-next-7.0) (2025-04-19) + + +### Bug Fixes + +* add domain removal statement ([341cdb1](https://github.com/aws-amplify/amplify-cli/commit/341cdb19260cac829317518731cbae61c7394723)) +* custom resource error handling, formatting messages ([199b138](https://github.com/aws-amplify/amplify-cli/commit/199b13815df81846abbbcd41793d1cccabae9533)) +* lint errors ([00b25b7](https://github.com/aws-amplify/amplify-cli/commit/00b25b756915e40001d07eceb795aa1b35289cb3)) +* **migrate-template-gen:** add start call for ora ([1926c8b](https://github.com/aws-amplify/amplify-cli/commit/1926c8b0b2511843ed9a94a718f4022bcdbc1e7c)) +* **migrate-template-gen:** replace console logs with ora for async tasks ([f12636c](https://github.com/aws-amplify/amplify-cli/commit/f12636ce7f3db7741ab30aa2cbe24a3fb8b33991)) +* refactor customResourceMappings in execute command ([000d660](https://github.com/aws-amplify/amplify-cli/commit/000d6608ade3ee4c65724df59200ffc00584e84c)) +* remove s3 deletion policy ([7c5abf3](https://github.com/aws-amplify/amplify-cli/commit/7c5abf310df3c9c9ae60fd367d49bf402d248a97)) + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-next-5.0...@aws-amplify/migrate-template-gen@0.1.0-next-6.0) (2025-03-21) + + +### Bug Fixes + +* **amplify-gen2-codegen:** remove extraneous newline ([aaf6dba](https://github.com/aws-amplify/amplify-cli/commit/aaf6dba696091933bc99583a2262f23dc96ea0ec)) +* migration qa feedback ([0f50d1c](https://github.com/aws-amplify/amplify-cli/commit/0f50d1c0ff2607c63cd3bcdd1589d38940a2b6d4)) + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-next-2.0...@aws-amplify/migrate-template-gen@0.1.0-next-5.0) (2025-03-19) + + +### Bug Fixes + +* **migrate-template-gen:** return early if output is not Fn:GetAtt or a Ref ([5a985f0](https://github.com/aws-amplify/amplify-cli/commit/5a985f0ea48a8b32ed51d04af83fbbf6baa29528)) + + + + + +# [0.1.0-next-2.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-next.0...@aws-amplify/migrate-template-gen@0.1.0-next-2.0) (2025-02-26) + + +### Bug Fixes + +* **migrate-template-gen:** add explicit public modifier for describeStack ([dffc611](https://github.com/aws-amplify/amplify-cli/commit/dffc611ba019f7c08cce77f500d77f3becb86b86)) +* **migrate-template-gen:** clone array before reverse ([9564e16](https://github.com/aws-amplify/amplify-cli/commit/9564e16ed0485e2937ffc2cbe17e5168dbb56672)) +* **migrate-template-gen:** format error ([13e68f1](https://github.com/aws-amplify/amplify-cli/commit/13e68f1ae0f30b72f588ccc2a064632f361bedfa)) +* **migrate-template-gen:** remove public modifier for describeStack ([9a42e41](https://github.com/aws-amplify/amplify-cli/commit/9a42e41fe9348e0d5ea8094b990d8cf8686c2433)) +* **migrate-template-gen:** remove unused enum member ([a7d3f92](https://github.com/aws-amplify/amplify-cli/commit/a7d3f92363b5ea5ca83430bd0e446942162510ca)) +* **migrate-template-gen:** update API.md ([70406a3](https://github.com/aws-amplify/amplify-cli/commit/70406a372fe6cde47332372f82efd8f8e1ab8246)) +* **migrate-template-gen:** update API.md ([0cd9423](https://github.com/aws-amplify/amplify-cli/commit/0cd9423bf9a491d15743e715c28d08e0318b2664)) +* **migrate:** lint errors ([98dced2](https://github.com/aws-amplify/amplify-cli/commit/98dced209aeea4c26aec86d3d5aba19830091b4a)) + + +### Features + +* **migrate:** add revert, userpool group refactor ([38eed7e](https://github.com/aws-amplify/amplify-cli/commit/38eed7e57e785cece232ce967ddc9171390af312)) + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-beta-latest.0...@aws-amplify/migrate-template-gen@0.1.0-next.0) (2025-02-14) + + +### Bug Fixes + +* add tag to force deployment post refactor in gen2 codegen ([dc953af](https://github.com/aws-amplify/amplify-cli/commit/dc953afd376eb6d7f36729580c9b2cc0a1a09652)) +* **migrate-template-gen:** add support for refactoring native client ([4dfc7e0](https://github.com/aws-amplify/amplify-cli/commit/4dfc7e0887eb2e608894fb5f60ec44a133b42772)) +* **migrate-template-gen:** extract conditionals into variables ([00de376](https://github.com/aws-amplify/amplify-cli/commit/00de3763843366746788931f9d149546b9d8038b)) +* **migrate-template-gen:** lint errors in category template generator test ([971a0c8](https://github.com/aws-amplify/amplify-cli/commit/971a0c80afe1293eb519a0f9ffd57d06d5ddbefe)) + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-alpha.1...@aws-amplify/migrate-template-gen@0.1.0-beta-latest.0) (2025-02-12) + + +### Bug Fixes + +* add uncomment instructions in readme ([b1ca1b1](https://github.com/aws-amplify/amplify-cli/commit/b1ca1b1efe70425b97c9083f5ac47d71c32aaeb7)) +* **migrate-template-gen:** don't generate README on failure ([a9b2e5a](https://github.com/aws-amplify/amplify-cli/commit/a9b2e5aa7a52ea0e534537f50cec81cd0649a984)) +* **migrate-template-gen:** lint, api extract ([8f9bef1](https://github.com/aws-amplify/amplify-cli/commit/8f9bef120c282fdd49a2c5c9b321c3541e1bd163)) +* **migrate-template-gen:** no need to reconstruct arn for iam role output ([790a290](https://github.com/aws-amplify/amplify-cli/commit/790a29056685278afe8ff99f41830f4b602334e8)) +* **migrate-template-gen:** support Get:Att in output resolver for roles ([15f8cbe](https://github.com/aws-amplify/amplify-cli/commit/15f8cbe87c156f3d66dfcf544be9375f3549f80d)) + + +### Features + +* add refactor operation to gen2 migration ([9f2752b](https://github.com/aws-amplify/amplify-cli/commit/9f2752b9b116b81267cb6ac5f7fd0877781c9e7f)) + + + + + +# [0.1.0-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-alpha.0...@aws-amplify/migrate-template-gen@0.1.0-alpha.1) (2024-12-05) + + +### Bug Fixes + +* increase default poll attemps and poll interval ([c5c92f6](https://github.com/aws-amplify/amplify-cli/commit/c5c92f65940a17f275a489477afa6b8a497e0654)) +* **migrate-template-gen:** lint warnings ([b1b32cc](https://github.com/aws-amplify/amplify-cli/commit/b1b32cc798c025c936305b5fae56636916dde77f)) +* **migrate-template-gen:** remove extraneous return ([32ffd8a](https://github.com/aws-amplify/amplify-cli/commit/32ffd8ab22170ae9f7fbebede42c779731f0e838)) +* remove dead code from migrate readme ([634dcd3](https://github.com/aws-amplify/amplify-cli/commit/634dcd3f7301d8db5e80db3f206b94dc03895068)) +* set only required oauth params ([18f5ec3](https://github.com/aws-amplify/amplify-cli/commit/18f5ec39e3cc4cdbc06aa7f16546a1cf70034445)) +* update lock and api.md ([a8d09df](https://github.com/aws-amplify/amplify-cli/commit/a8d09df214c5f1182066a47183163e3a15a6f10f)) + + +### Features + +* automate preprocessing steps for refactor ([339c4cd](https://github.com/aws-amplify/amplify-cli/commit/339c4cd35fc7ecf4d579b1685676fb1eafdb2df5)) +* **migrate-template-gen:** retrieve oauth values for gen1 stack update ([3604b3e](https://github.com/aws-amplify/amplify-cli/commit/3604b3e86c01b300dd4d3480e900646875bba0f7)) +* **migrate-template-gen:** use nested file protocol prop using @ ([b6655c3](https://github.com/aws-amplify/amplify-cli/commit/b6655c38f62b1bfc039950fc9c555aa227d7d90d)) + + + + + +# [0.1.0-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate-template-gen@0.1.0-gen2-migrations-alpha.0...@aws-amplify/migrate-template-gen@0.1.0-alpha.0) (2024-11-21) + + +### Bug Fixes + +* **migrate-template-gen:** resolve stack name pseudo ref ([5caaf0e](https://github.com/aws-amplify/amplify-cli/commit/5caaf0eb77b22355cb57d71af7f29d2b017fd39d)) +* **migrate-template-gen:** typo in step1 command ([003bc2d](https://github.com/aws-amplify/amplify-cli/commit/003bc2d60383dfeb406b76ca7ee14df352191e31)) +* **migrate-template-gen:** update gen1 stack with empty tags to support refactor ([8a5c406](https://github.com/aws-amplify/amplify-cli/commit/8a5c4069d041df9e7903abb767a8bd544dd23933)) +* remove delete pseudo param ([2b85032](https://github.com/aws-amplify/amplify-cli/commit/2b85032b06555788869938b7552823da59a627c2)) + + +### Features + +* **migrate-template-gen:** add rollback step for refactor ([5ec3cf9](https://github.com/aws-amplify/amplify-cli/commit/5ec3cf96582a8a7669ec1305cee73aec9e9099c1)) + + + + + +# 0.1.0-gen2-migrations-alpha.0 (2024-10-10) + + +### Bug Fixes + +* **migrate-template-gen:** add API md ([bee27fe](https://github.com/aws-amplify/amplify-cli/commit/bee27fedb976468bd8cfef0f476f1dc9913dd679)) +* **migrate-template-gen:** add types changes for condition resolver ([498c491](https://github.com/aws-amplify/amplify-cli/commit/498c491505279b768455699bc4e3db93d7e8e0f8)) +* **migrate-template-gen:** convert gen1 deps to gen2 counterparts ([fecb791](https://github.com/aws-amplify/amplify-cli/commit/fecb7917747784ccef2d8a06aac2e7ad63d34282)) +* **migrate-template-gen:** fix lint on any ([a2586bd](https://github.com/aws-amplify/amplify-cli/commit/a2586bd658031039ba2c431451b33c780ffa17d4)) +* **migrate-template-gen:** remove extraneous assert in condition resolver ([06e4318](https://github.com/aws-amplify/amplify-cli/commit/06e4318b20a638ec27a4d6df8663ca069fde8773)) +* **migrate-template-gen:** resolve all outputs ([b9fced5](https://github.com/aws-amplify/amplify-cli/commit/b9fced542958d94167ca80efe80f1d43b20931eb)) +* package.json ([43943ef](https://github.com/aws-amplify/amplify-cli/commit/43943ef1746a2e5d1562faff867b71070d3cc39e)) +* remove unused imports for template gen tests ([6049c83](https://github.com/aws-amplify/amplify-cli/commit/6049c833350025155160bfec5ebdd0355cc125c1)) +* remove unused imports in category template generator ([d6af7dc](https://github.com/aws-amplify/amplify-cli/commit/d6af7dcbe12c25e18fca4e6f6ac675b3dab505de)) + + +### Features + +* **migrate-template-gen:** add category template generator ([4833b27](https://github.com/aws-amplify/amplify-cli/commit/4833b2765b43df67523fe2ef733121da40f196e0)) +* **migrate-template-gen:** add cfn dependency resolver ([81e83fc](https://github.com/aws-amplify/amplify-cli/commit/81e83fc91d5d7c61188da1dae44e627827933f6f)) +* **migrate-template-gen:** add cfn parameter resolver ([738acf2](https://github.com/aws-amplify/amplify-cli/commit/738acf25b8c1502ed740ba6fca5aa21b5575862c)) +* **migrate-template-gen:** add condition resolver ([db7c9f9](https://github.com/aws-amplify/amplify-cli/commit/db7c9f980553bc14f00971a6d45c14183bc9ec0e)) +* **migrate-template-gen:** add output resolver ([a983597](https://github.com/aws-amplify/amplify-cli/commit/a98359783aa85d8c0a5a47d5b8bc424f08f5f478)) +* **migrate-template-gen:** add readme generator ([ebd02ef](https://github.com/aws-amplify/amplify-cli/commit/ebd02efb22f187c163db694f4eabd584a43d9873)) +* **migrate-template-gen:** add template generator ([af5064a](https://github.com/aws-amplify/amplify-cli/commit/af5064af4400c2282fc26ed3c490a3dd55ffdb32)) diff --git a/packages/amplify-migration-template-gen/package.json b/packages/amplify-migration-template-gen/package.json new file mode 100644 index 00000000000..25e8aec75f9 --- /dev/null +++ b/packages/amplify-migration-template-gen/package.json @@ -0,0 +1,56 @@ +{ + "name": "@aws-amplify/migrate-template-gen", + "version": "0.1.0-next-11.0", + "type": "commonjs", + "main": "lib/index.js", + "devDependencies": { + "@jest/globals": "^29.7.0", + "jest": "^29.5.0", + "typescript": "^5.4.5" + }, + "dependencies": { + "@aws-sdk/client-cloudformation": "^3.744.0", + "@aws-sdk/client-cognito-identity-provider": "^3.592.0", + "@aws-sdk/client-ssm": "^3.592.0", + "ora": "^4.0.3" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": "ts-jest" + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true, + "setupFilesAfterEnv": [ + "/src/setup-jest.ts" + ] + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "pretest": "mkdir -p coverage", + "test": "jest --logHeapUsage", + "build": "tsc", + "watch": "tsc -w", + "extract-api": "ts-node ../../scripts/extract-api.ts" + }, + "author": "", + "license": "Apache-2.0", + "description": "" +} diff --git a/packages/amplify-migration-template-gen/src/category-template-generator.test.ts b/packages/amplify-migration-template-gen/src/category-template-generator.test.ts new file mode 100644 index 00000000000..87b54dd9e01 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/category-template-generator.test.ts @@ -0,0 +1,891 @@ +import CategoryTemplateGenerator from './category-template-generator'; +import { CFN_AUTH_TYPE, CFN_PSEUDO_PARAMETERS_REF, CFN_S3_TYPE, CFNTemplate } from './types'; +import { + CloudFormationClient, + DescribeStacksCommand, + DescribeStackResourcesCommand, + DescribeStacksOutput, + GetTemplateCommand, + GetTemplateOutput, + Parameter, +} from '@aws-sdk/client-cloudformation'; +import { GetParameterCommand, GetParameterCommandOutput, SSMClient } from '@aws-sdk/client-ssm'; +import { + CognitoIdentityProviderClient, + DescribeIdentityProviderCommand, + DescribeIdentityProviderResponse, +} from '@aws-sdk/client-cognito-identity-provider'; + +// We use 'stub' to indicate fake implementation. If we are asserting a fake, it becomes a 'mock'. +// Ref: https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices#lets-speak-the-same-language +const stubCfnClientSend = jest.fn(); +const stubSsmClientSend = jest.fn(); +const stubCognitoClientSend = jest.fn(); + +const GEN1_STORAGE_CATEGORY_STACK_NAME = 'amplify-testauth-dev-12345-storage-ABCDE'; +const GEN1_CATEGORY_STACK_ID = `arn:aws:cloudformation:us-east-1:1234567890:stack/${GEN1_STORAGE_CATEGORY_STACK_NAME}/12345`; +const GEN2_CATEGORY_STACK_ID = 'arn:aws:cloudformation:us-east-1:1234567890:stack/amplify-mygen2app-test-sandbox-12345-storage-ABCDE/12345'; +const GEN1_AUTH_CATEGORY_STACK_NAME = 'amplify-testauth-dev-12345-auth-ABCDE'; +const GEN1_AUTH_CATEGORY_STACK_ID = `arn:aws:cloudformation:us-east-1:1234567890:stack/${GEN1_AUTH_CATEGORY_STACK_NAME}/12345`; +const GEN2_AUTH_CATEGORY_STACK_ID = + 'arn:aws:cloudformation:us-east-1:1234567890:stack/amplify-mygen2app-test-sandbox-12345-auth-ABCDE/12345'; +const GEN1_S3_BUCKET_LOGICAL_ID = 'S3Bucket'; +const GEN2_S3_BUCKET_LOGICAL_ID = 'Gen2S3Bucket'; +const GEN1_ANOTHER_S3_BUCKET_LOGICAL_ID = 'MyOtherS3Bucket'; +const GEN2_ANOTHER_S3_BUCKET_LOGICAL_ID = 'MyOtherGen2S3Bucket'; +const MOCK_APP_ID = 'd123456'; +const ENV_NAME = 'test'; +const GEN1_AUTH_USER_POOL_LOGICAL_ID = 'UserPool'; +const GEN1_AUTH_IDENTITY_POOL_LOGICAL_ID = 'IdentityPool'; +const GEN1_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID = 'UserPoolClient'; +const GEN1_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID = 'UserPoolClientWeb'; +const GEN2_AUTH_USER_POOL_LOGICAL_ID = 'Gen2UserPool'; +const GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID = 'Gen2IdentityPool'; +const GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID = 'UserPoolAppClient'; +const GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID = 'UserPoolNativeAppClient'; + +const oldGen1Template: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + Environment: { + Type: 'String', + Description: 'Environment', + }, + hostedUIProviderMeta: { + Type: 'String', + Description: 'HostedUIProviderMeta', + }, + hostedUIProviderCreds: { + Type: 'String', + Description: 'HostedUIProviderCreds', + NoEcho: true, + }, + }, + Outputs: { + BucketNameOutputRef: { + Description: 'Bucket name', + Value: { Ref: GEN1_S3_BUCKET_LOGICAL_ID }, + }, + UserPoolId: { + Description: 'User pool id', + Value: 'userPoolId', + }, + }, + Resources: { + [GEN1_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: { 'Fn::Join': ['-', ['my-test-bucket', { Ref: 'Environment' }, { Ref: CFN_PSEUDO_PARAMETERS_REF.StackName }]] }, + }, + }, + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: { 'Fn::GetAtt': [GEN1_S3_BUCKET_LOGICAL_ID, 'Arn'] }, + }, + ], + }, + }, + }, + [GEN1_ANOTHER_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: 'my-other-s3-bucket', + }, + DependsOn: [GEN1_S3_BUCKET_LOGICAL_ID], + }, + }, +}; + +const newGen1Template: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + Environment: { + Type: 'String', + Description: 'Environment', + }, + hostedUIProviderMeta: { + Type: 'String', + Description: 'HostedUIProviderMeta', + }, + hostedUIProviderCreds: { + Type: 'String', + Description: 'HostedUIProviderCreds', + NoEcho: true, + }, + }, + Outputs: { + BucketNameOutputRef: { + Description: 'Bucket name', + Value: 'my-test-bucket-dev', + }, + UserPoolId: { + Description: 'User pool id', + Value: 'userPoolId', + }, + }, + Resources: { + [GEN1_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: { 'Fn::Join': ['-', ['my-test-bucket', 'dev', GEN1_STORAGE_CATEGORY_STACK_NAME]] }, + }, + }, + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: 'arn:aws:s3:::my-test-bucket-dev', + }, + ], + }, + }, + }, + [GEN1_ANOTHER_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: 'my-other-s3-bucket', + }, + DependsOn: [GEN1_S3_BUCKET_LOGICAL_ID], + }, + }, +}; + +const newGen1TemplateWithPredicate: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + Environment: { + Type: 'String', + Description: 'Environment', + }, + hostedUIProviderMeta: { + Type: 'String', + Description: 'HostedUIProviderMeta', + }, + hostedUIProviderCreds: { + Type: 'String', + Description: 'HostedUIProviderCreds', + NoEcho: true, + }, + }, + Outputs: { + BucketNameOutputRef: { + Description: 'Bucket name', + Value: 'my-test-bucket-dev', + }, + UserPoolId: { + Description: 'User pool id', + Value: 'userPoolId', + }, + }, + Resources: { + [GEN1_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: { 'Fn::Join': ['-', ['my-test-bucket', 'dev', GEN1_STORAGE_CATEGORY_STACK_NAME]] }, + }, + }, + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: 'arn:aws:s3:::my-test-bucket-dev', + }, + ], + }, + }, + }, + [GEN1_ANOTHER_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: 'my-other-s3-bucket', + }, + DependsOn: [], + }, + }, +}; + +const oldGen2Template = { + ...oldGen1Template, + Outputs: { + BucketNameOutputRef: { + Description: 'Bucket name', + Value: { Ref: GEN2_S3_BUCKET_LOGICAL_ID }, + }, + }, + Resources: { + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: { 'Fn::GetAtt': [GEN2_S3_BUCKET_LOGICAL_ID, 'Arn'] }, + }, + ], + }, + }, + }, + [GEN2_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: { 'Fn::Join': ['-', ['my-test-bucket', { Ref: 'Environment' }]] }, + }, + }, + [GEN2_ANOTHER_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: 'my-other-s3-bucket-2', + }, + DependsOn: [GEN2_S3_BUCKET_LOGICAL_ID], + }, + }, +}; + +const newGen2Template: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: oldGen2Template.Parameters, + Outputs: { + BucketNameOutputRef: { + Description: 'Bucket name', + Value: 'my-test-bucket-dev', + }, + }, + Resources: { + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: 'arn:aws:s3:::my-test-bucket-dev', + }, + ], + }, + }, + }, + }, +}; + +const gen1Params: Parameter[] = [ + { + ParameterKey: 'Environment', + ParameterValue: 'dev', + }, + { + ParameterKey: 'hostedUIProviderMeta', + ParameterValue: JSON.stringify([ + { + ProviderName: 'Facebook', + }, + { + ProviderName: 'SignInWithApple', + }, + ]), + }, + { + ParameterKey: 'hostedUIProviderCreds', + ParameterValue: JSON.stringify([ + { + ProviderName: 'Facebook', + client_id: 'fbClientId', + client_secret: 'fbClientSecret', + }, + { + ProviderName: 'SignInWithApple', + teamId: 'appleTeamId', + keyId: 'appleKeyId', + privateKey: 'applePrivateKey', + clientId: 'appleClientId', + }, + ]), + }, +]; + +const refactoredGen1Template: CFNTemplate = { + ...newGen1Template, + Resources: { + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: 'arn:aws:s3:::my-test-bucket-dev', + }, + ], + }, + }, + }, + }, +}; + +const refactoredGen2Template: CFNTemplate = { + ...newGen2Template, + Resources: { + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: 'arn:aws:s3:::my-test-bucket-dev', + }, + ], + }, + }, + }, + [GEN2_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: { 'Fn::Join': ['-', ['my-test-bucket', 'dev', GEN1_STORAGE_CATEGORY_STACK_NAME]] }, + }, + }, + [GEN2_ANOTHER_S3_BUCKET_LOGICAL_ID]: { + Type: CFN_S3_TYPE.Bucket, + Properties: { + BucketName: 'my-other-s3-bucket', + }, + DependsOn: [GEN2_S3_BUCKET_LOGICAL_ID], + }, + }, +}; + +const oldGen1TemplateWithoutS3Bucket = JSON.parse(JSON.stringify(oldGen1Template)) as CFNTemplate; +delete oldGen1TemplateWithoutS3Bucket.Resources[GEN1_S3_BUCKET_LOGICAL_ID]; +delete oldGen1TemplateWithoutS3Bucket.Resources[GEN1_ANOTHER_S3_BUCKET_LOGICAL_ID]; + +const oldGen2TemplateWithoutS3Bucket = JSON.parse(JSON.stringify(oldGen2Template)) as CFNTemplate; +delete oldGen2TemplateWithoutS3Bucket.Resources[GEN2_S3_BUCKET_LOGICAL_ID]; +delete oldGen2TemplateWithoutS3Bucket.Resources[GEN2_ANOTHER_S3_BUCKET_LOGICAL_ID]; + +const oldGen1AuthTemplate: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + Environment: { + Type: 'String', + Description: 'Environment', + }, + hostedUIProviderMeta: { + Type: 'String', + Description: 'HostedUIProviderMeta', + }, + hostedUIProviderCreds: { + Type: 'String', + Description: 'HostedUIProviderCreds', + NoEcho: true, + }, + }, + Outputs: { + UserPoolId: { + Description: 'User pool id', + Value: 'userPoolId', + }, + }, + Resources: { + [GEN1_AUTH_USER_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPool, + Properties: { + UserPoolName: { 'Fn::Join': ['-', 'my-user-pool', { Ref: 'Environment' }] }, + }, + }, + [GEN1_AUTH_IDENTITY_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.IdentityPool, + Properties: { + IdentityPoolName: { 'Fn::Join': ['-', 'my-identity-pool', { Ref: 'Environment' }] }, + }, + }, + [GEN1_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'WebClient', + }, + }, + [GEN1_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'NativeClient', + }, + }, + DummyResource: { + Type: 'AWS::CloudFormation::WaitConditionHandle', + Properties: {}, + }, + }, +}; + +const oldGen2AuthTemplate: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Outputs: { + UserPoolId: { + Description: 'User pool id', + Value: 'userPoolId', + }, + }, + Resources: { + [GEN2_AUTH_USER_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPool, + Properties: { + UserPoolName: 'my-gen2-user-pool', + }, + }, + [GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.IdentityPool, + Properties: { + IdentityPoolName: 'my-gen2-identity-pool', + }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'Gen2WebClient', + }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'Gen2NativeClient', + }, + }, + AuthRole: { + Type: 'AWS::IAM::Role', + Properties: {}, + }, + }, +}; + +const newGen1AuthTemplate: CFNTemplate = { + ...oldGen1AuthTemplate, + Resources: { + [GEN1_AUTH_USER_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPool, + Properties: { + UserPoolName: 'my-user-pool-dev', + }, + }, + [GEN1_AUTH_IDENTITY_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.IdentityPool, + Properties: { + IdentityPoolName: 'my-identity-pool-dev', + }, + }, + [GEN1_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'WebClient', + }, + }, + [GEN1_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'NativeClient', + }, + }, + }, +}; + +const newGen2AuthTemplate: CFNTemplate = { + ...oldGen2AuthTemplate, + Resources: { + AuthRole: { + Type: 'AWS::IAM::Role', + Properties: {}, + }, + }, +}; + +const refactoredGen1AuthTemplate: CFNTemplate = { + ...newGen1AuthTemplate, + Resources: { + DummyResource: { + Type: 'AWS::CloudFormation::WaitConditionHandle', + Properties: {}, + }, + }, +}; + +const refactoredGen2AuthTemplate: CFNTemplate = { + ...newGen2AuthTemplate, + Resources: { + ...oldGen2AuthTemplate.Resources, + [GEN2_AUTH_USER_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPool, + Properties: { + UserPoolName: { 'Fn::Join': ['-', 'my-user-pool', 'dev'] }, + }, + }, + [GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.IdentityPool, + Properties: { + IdentityPoolName: { 'Fn::Join': ['-', 'my-identity-pool', 'dev'] }, + }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'WebClient', + }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'NativeClient', + }, + }, + }, +}; + +const generateDescribeStacksResponse = (command: DescribeStacksCommand): DescribeStacksOutput => ({ + Stacks: [ + { + StackId: command.input.StackName, + StackName: command.input.StackName, + Capabilities: ['CAPABILITY_NAMED_IAM'], + Tags: [ + { + Key: 'amplify:category-stack', + Value: GEN1_AUTH_CATEGORY_STACK_NAME, + }, + ], + CreationTime: new Date(), + LastUpdatedTime: new Date(), + StackStatus: 'CREATE_COMPLETE', + Parameters: gen1Params, + Outputs: [ + { + OutputKey: 'BucketNameOutputRef', + OutputValue: 'my-test-bucket-dev', + Description: 'My bucket', + }, + { + OutputKey: 'UserPoolId', + OutputValue: 'userPoolId', + Description: 'My user pool', + }, + ], + }, + ], +}); + +const generateGetTemplateResponse = (command: GetTemplateCommand): GetTemplateOutput => { + const stackName = command.input.StackName; + let templateBody; + switch (stackName) { + case GEN1_CATEGORY_STACK_ID: { + templateBody = JSON.stringify(oldGen1Template); + break; + } + case GEN2_CATEGORY_STACK_ID: { + templateBody = JSON.stringify(oldGen2Template); + break; + } + case GEN1_AUTH_CATEGORY_STACK_ID: { + templateBody = JSON.stringify(oldGen1AuthTemplate); + break; + } + case GEN2_AUTH_CATEGORY_STACK_ID: { + templateBody = JSON.stringify(oldGen2AuthTemplate); + break; + } + default: + throw new Error(`Unknown stack name: ${stackName}`); + } + return { + TemplateBody: templateBody, + }; +}; + +const generateDescribeIdentityProviderResponse = ({ input }: DescribeIdentityProviderCommand): DescribeIdentityProviderResponse => ({ + IdentityProvider: { + ProviderName: input.ProviderName, + ProviderDetails: { + client_id: 'client_id', + client_secret: 'client_secret', + team_id: input.ProviderName === 'SignInWithApple' ? 'team_id' : '', + key_id: input.ProviderName === 'SignInWithApple' ? 'key_id' : '', + }, + UserPoolId: input.UserPoolId, + }, +}); + +const generateGetParameterResponse = (command: GetParameterCommand): GetParameterCommandOutput => ({ + Parameter: { + Name: command.input.Name, + Value: 'private_key', + }, + $metadata: {}, +}); + +jest.mock('@aws-sdk/client-cloudformation', () => { + return { + ...jest.requireActual('@aws-sdk/client-cloudformation'), + CloudFormationClient: function () { + return { + send: stubCfnClientSend.mockImplementation((command) => { + if (command instanceof DescribeStacksCommand) { + return Promise.resolve(generateDescribeStacksResponse(command)); + } else if (command instanceof GetTemplateCommand) { + return Promise.resolve(generateGetTemplateResponse(command)); + } else if (command instanceof DescribeStackResourcesCommand) { + return Promise.resolve({ + StackResources: [ + { + StackId: command.input.StackName, + StackName: command.input.StackName, + LogicalResourceId: GEN1_S3_BUCKET_LOGICAL_ID, + PhysicalResourceId: GEN1_S3_BUCKET_LOGICAL_ID, + ResourceType: CFN_S3_TYPE.Bucket, + ResourceStatus: 'CREATE_COMPLETE', + Timestamp: new Date(), + }, + ], + }); + } + return Promise.resolve({}); + }), + }; + }, + }; +}); + +jest.mock('@aws-sdk/client-cognito-identity-provider', () => { + return { + ...jest.requireActual('@aws-sdk/client-cognito-identity-provider'), + CognitoIdentityProviderClient: function () { + return { + send: stubCognitoClientSend.mockImplementation((command) => { + if (command instanceof DescribeIdentityProviderCommand) { + return Promise.resolve(generateDescribeIdentityProviderResponse(command)); + } + return Promise.resolve({}); + }), + }; + }, + }; +}); + +jest.mock('@aws-sdk/client-ssm', () => { + return { + ...jest.requireActual('@aws-sdk/client-ssm'), + SSMClient: function () { + return { + send: stubSsmClientSend.mockImplementation((command) => { + if (command instanceof GetParameterCommand) { + return Promise.resolve(generateGetParameterResponse(command)); + } + return Promise.resolve({}); + }), + }; + }, + }; +}); + +describe('CategoryTemplateGenerator', () => { + const s3TemplateGenerator = new CategoryTemplateGenerator( + GEN1_CATEGORY_STACK_ID, + GEN2_CATEGORY_STACK_ID, + 'us-east-1', + '12345', + new CloudFormationClient(), + new SSMClient(), + new CognitoIdentityProviderClient(), + MOCK_APP_ID, + ENV_NAME, + [CFN_S3_TYPE.Bucket], + ); + + const s3TemplateGeneratorWithPredicate = new CategoryTemplateGenerator( + GEN1_CATEGORY_STACK_ID, + GEN2_CATEGORY_STACK_ID, + 'us-east-1', + '12345', + new CloudFormationClient(), + new SSMClient(), + new CognitoIdentityProviderClient(), + MOCK_APP_ID, + ENV_NAME, + [CFN_S3_TYPE.Bucket], + // decide which resources to move based on resource properties + (resourcesToMove, resourceEntry) => resourcesToMove.includes(CFN_S3_TYPE.Bucket) && resourceEntry[0] === GEN1_S3_BUCKET_LOGICAL_ID, + ); + + const noGen1ResourcesToMoveS3TemplateGenerator = new CategoryTemplateGenerator( + GEN1_CATEGORY_STACK_ID, + GEN2_CATEGORY_STACK_ID, + 'us-east-1', + '12345', + new CloudFormationClient(), + new SSMClient(), + new CognitoIdentityProviderClient(), + MOCK_APP_ID, + ENV_NAME, + [CFN_S3_TYPE.Bucket], + ); + + const authTemplateGenerator = new CategoryTemplateGenerator( + GEN1_AUTH_CATEGORY_STACK_ID, + GEN2_AUTH_CATEGORY_STACK_ID, + 'us-east-1', + '12345', + new CloudFormationClient(), + new SSMClient(), + new CognitoIdentityProviderClient(), + MOCK_APP_ID, + ENV_NAME, + [CFN_AUTH_TYPE.UserPoolClient, CFN_AUTH_TYPE.UserPool, CFN_AUTH_TYPE.IdentityPool, CFN_AUTH_TYPE.UserPoolDomain], + ); + + it('should preprocess gen1 template prior to refactor', async () => { + await expect(s3TemplateGenerator.generateGen1PreProcessTemplate()).resolves.toEqual({ + oldTemplate: oldGen1Template, + newTemplate: newGen1Template, + parameters: gen1Params, + }); + }); + + it('should preprocess gen1 template with predicate prior to refactor', async () => { + await expect(s3TemplateGeneratorWithPredicate.generateGen1PreProcessTemplate()).resolves.toEqual({ + oldTemplate: oldGen1Template, + newTemplate: newGen1TemplateWithPredicate, + parameters: gen1Params, + }); + }); + + it('should remove gen2 resources from gen2 stack prior to refactor', async () => { + await expect(s3TemplateGenerator.generateGen2ResourceRemovalTemplate()).resolves.toEqual({ + oldTemplate: oldGen2Template, + newTemplate: newGen2Template, + parameters: gen1Params, + }); + }); + + it('should refactor gen1 resources into gen2 stack', async () => { + const { newTemplate: newGen1Template } = await s3TemplateGenerator.generateGen1PreProcessTemplate(); + const { newTemplate: newGen2Template } = await s3TemplateGenerator.generateGen2ResourceRemovalTemplate(); + const { sourceTemplate, destinationTemplate, logicalIdMapping } = s3TemplateGenerator.generateStackRefactorTemplates( + newGen1Template, + newGen2Template, + ); + expect(sourceTemplate).toEqual(refactoredGen1Template); + expect(destinationTemplate).toEqual(refactoredGen2Template); + expect(logicalIdMapping).toEqual( + new Map([ + [GEN1_S3_BUCKET_LOGICAL_ID, GEN2_S3_BUCKET_LOGICAL_ID], + [GEN1_ANOTHER_S3_BUCKET_LOGICAL_ID, GEN2_ANOTHER_S3_BUCKET_LOGICAL_ID], + ]), + ); + }); + + it('should refactor auth gen1 resources into gen2 stack', async () => { + const { newTemplate: newGen1Template } = await authTemplateGenerator.generateGen1PreProcessTemplate(); + const { newTemplate: newGen2Template } = await authTemplateGenerator.generateGen2ResourceRemovalTemplate(); + const { sourceTemplate, destinationTemplate, logicalIdMapping } = authTemplateGenerator.generateStackRefactorTemplates( + newGen1Template, + newGen2Template, + ); + expect(sourceTemplate).toEqual(refactoredGen1AuthTemplate); + expect(destinationTemplate).toEqual(refactoredGen2AuthTemplate); + expect(logicalIdMapping).toEqual( + new Map([ + [GEN1_AUTH_USER_POOL_LOGICAL_ID, GEN2_AUTH_USER_POOL_LOGICAL_ID], + [GEN1_AUTH_IDENTITY_POOL_LOGICAL_ID, GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID], + [GEN1_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID, GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID], + [GEN1_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID, GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID], + ]), + ); + }); + + it('should throw error when there are no resources to move in Gen1 stack', async () => { + const sendFailureMock = (command: any) => { + if (command instanceof DescribeStacksCommand) { + return Promise.resolve(generateDescribeStacksResponse(command)); + } else if (command instanceof GetTemplateCommand) { + return Promise.resolve({ + TemplateBody: + command.input.StackName === GEN1_CATEGORY_STACK_ID + ? JSON.stringify(oldGen1TemplateWithoutS3Bucket) + : JSON.stringify(oldGen2Template), + }); + } + return Promise.resolve({}); + }; + stubCfnClientSend.mockImplementationOnce(sendFailureMock).mockImplementationOnce(sendFailureMock); + await expect(noGen1ResourcesToMoveS3TemplateGenerator.generateGen1PreProcessTemplate()).rejects.toThrowError( + 'No resources to move in Gen1 stack.', + ); + }); + + it('should throw error when there are no resources to move in Gen2 stack', async () => { + const sendFailureMock = (command: any) => { + if (command instanceof DescribeStacksCommand) { + return Promise.resolve(generateDescribeStacksResponse(command)); + } else if (command instanceof GetTemplateCommand) { + return Promise.resolve({ + TemplateBody: + command.input.StackName === GEN1_CATEGORY_STACK_ID + ? JSON.stringify(oldGen1Template) + : JSON.stringify(oldGen2TemplateWithoutS3Bucket), + }); + } else if (command instanceof DescribeStackResourcesCommand) { + return Promise.resolve({ + StackResources: [ + { + StackId: command.input.StackName, + StackName: command.input.StackName, + LogicalResourceId: GEN1_S3_BUCKET_LOGICAL_ID, + PhysicalResourceId: GEN1_S3_BUCKET_LOGICAL_ID, + ResourceType: CFN_S3_TYPE.Bucket, + ResourceStatus: 'CREATE_COMPLETE', + Timestamp: new Date(), + }, + ], + }); + } + return Promise.resolve({}); + }; + stubCfnClientSend + .mockImplementationOnce(sendFailureMock) + .mockImplementationOnce(sendFailureMock) + .mockImplementationOnce(sendFailureMock) + .mockImplementationOnce(sendFailureMock) + .mockImplementationOnce(sendFailureMock); + await noGen1ResourcesToMoveS3TemplateGenerator.generateGen1PreProcessTemplate(); + await expect(noGen1ResourcesToMoveS3TemplateGenerator.generateGen2ResourceRemovalTemplate()).rejects.toThrowError( + 'No resources to remove in Gen2 stack.', + ); + }); +}); diff --git a/packages/amplify-migration-template-gen/src/category-template-generator.ts b/packages/amplify-migration-template-gen/src/category-template-generator.ts new file mode 100644 index 00000000000..359dca34d89 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/category-template-generator.ts @@ -0,0 +1,295 @@ +import { + CloudFormationClient, + DescribeStacksCommand, + DescribeStackResourcesCommand, + GetTemplateCommand, + Stack, + Parameter, +} from '@aws-sdk/client-cloudformation'; +import { SSMClient } from '@aws-sdk/client-ssm'; +import assert from 'node:assert'; +import { + CFN_AUTH_TYPE, + CFN_CATEGORY_TYPE, + CFN_IAM_TYPE, + CFNChangeTemplateWithParams, + CFNResource, + CFNStackRefactorTemplates, + CFNTemplate, +} from './types'; +import CFNConditionResolver from './resolvers/cfn-condition-resolver'; +import CfnParameterResolver from './resolvers/cfn-parameter-resolver'; +import CfnOutputResolver from './resolvers/cfn-output-resolver'; +import CfnDependencyResolver from './resolvers/cfn-dependency-resolver'; +import extractStackNameFromId from './cfn-stack-name-extractor'; +import retrieveOAuthValues from './oauth-values-retriever'; +import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; + +export const HOSTED_PROVIDER_META_PARAMETER_NAME = 'hostedUIProviderMeta'; +const HOSTED_PROVIDER_CREDENTIALS_PARAMETER_NAME = 'hostedUIProviderCreds'; +const USER_POOL_ID_OUTPUT_KEY_NAME = 'UserPoolId'; +const GEN1_WEB_APP_CLIENT = 'UserPoolClientWeb'; +const GEN2_NATIVE_APP_CLIENT = 'UserPoolNativeAppClient'; +const RESOURCE_TYPES_WITH_MULTIPLE_RESOURCES = [ + CFN_AUTH_TYPE.UserPoolClient.valueOf(), + CFN_AUTH_TYPE.UserPoolGroup.valueOf(), + CFN_IAM_TYPE.Role.valueOf(), +]; + +class CategoryTemplateGenerator { + private gen1DescribeStacksResponse: Stack | undefined; + private gen2DescribeStacksResponse: Stack | undefined; + public gen1ResourcesToMove: Map; + public gen2ResourcesToRemove: Map; + public gen2Template: CFNTemplate | undefined; + public gen2StackParameters: Parameter[] | undefined; + constructor( + private readonly gen1StackId: string, + private readonly gen2StackId: string, + private readonly region: string, + private readonly accountId: string, + private readonly cfnClient: CloudFormationClient, + private readonly ssmClient: SSMClient, + private readonly cognitoIdpClient: CognitoIdentityProviderClient, + private readonly appId: string, + private readonly environmentName: string, + private readonly resourcesToMove: CFNCategoryType[], + private readonly resourcesToMovePredicate?: (resourcesToMove: CFN_CATEGORY_TYPE[], resourceEntry: [string, CFNResource]) => boolean, + ) { + this.gen1ResourcesToMove = new Map(); + this.gen2ResourcesToRemove = new Map(); + } + + public async generateGen1PreProcessTemplate(): Promise { + this.gen1DescribeStacksResponse = await this.describeStack(this.gen1StackId); + assert(this.gen1DescribeStacksResponse); + const { Parameters, Outputs } = this.gen1DescribeStacksResponse; + assert(Parameters); + assert(Outputs); + const oldGen1Template = await this.readTemplate(this.gen1StackId); + this.gen1ResourcesToMove = new Map( + Object.entries(oldGen1Template.Resources).filter(([logicalId, value]) => { + return ( + this.resourcesToMovePredicate?.(this.resourcesToMove, [logicalId, value]) ?? + this.resourcesToMove.some((resourceToMove) => resourceToMove.valueOf() === value.Type) + ); + }), + ); + // validate empty resources + if (this.gen1ResourcesToMove.size === 0) throw new Error('No resources to move in Gen1 stack.'); + const logicalResourceIds = [...this.gen1ResourcesToMove.keys()]; + const gen1ParametersResolvedTemplate = new CfnParameterResolver(oldGen1Template, extractStackNameFromId(this.gen1StackId)).resolve( + Parameters, + ); + + const stackResources = await this.describeStackResources(this.gen1StackId); + const gen1TemplateWithOutputsResolved = new CfnOutputResolver(gen1ParametersResolvedTemplate, this.region, this.accountId).resolve( + logicalResourceIds, + Outputs, + stackResources, + ); + const gen1TemplateWithDepsResolved = new CfnDependencyResolver(gen1TemplateWithOutputsResolved).resolve(logicalResourceIds); + const gen1TemplateWithConditionsResolved = new CFNConditionResolver(gen1TemplateWithDepsResolved).resolve(Parameters); + const oAuthProvidersParam = Parameters.find((param) => param.ParameterKey === HOSTED_PROVIDER_META_PARAMETER_NAME); + if (oAuthProvidersParam) { + const userPoolId = Outputs.find((op) => op.OutputKey === USER_POOL_ID_OUTPUT_KEY_NAME)?.OutputValue; + assert(userPoolId); + const oAuthValues = await retrieveOAuthValues({ + ssmClient: this.ssmClient, + cognitoIdpClient: this.cognitoIdpClient, + appId: this.appId, + environmentName: this.environmentName, + oAuthParameter: oAuthProvidersParam, + userPoolId, + }); + const oAuthProviderCredentialsParam = Parameters.find((param) => param.ParameterKey === HOSTED_PROVIDER_CREDENTIALS_PARAMETER_NAME); + assert(oAuthProviderCredentialsParam); + oAuthProviderCredentialsParam.ParameterValue = JSON.stringify(oAuthValues); + } + return { + oldTemplate: oldGen1Template, + newTemplate: gen1TemplateWithConditionsResolved, + parameters: Parameters, + }; + } + + public async generateGen2ResourceRemovalTemplate(): Promise { + this.gen2DescribeStacksResponse = await this.describeStack(this.gen2StackId); + assert(this.gen2DescribeStacksResponse); + const { Parameters, Outputs } = this.gen2DescribeStacksResponse; + assert(Outputs); + this.gen2StackParameters = Parameters; + const oldGen2Template = await this.readTemplate(this.gen2StackId); + this.gen2Template = oldGen2Template; + this.gen2ResourcesToRemove = new Map( + Object.entries(oldGen2Template.Resources).filter(([logicalId, value]) => { + return ( + this.resourcesToMovePredicate?.(this.resourcesToMove, [logicalId, value]) ?? + this.resourcesToMove.some((resourceToMove) => resourceToMove.valueOf() === value.Type) + ); + }), + ); + // validate empty resources + if (this.gen2ResourcesToRemove.size === 0) throw new Error('No resources to remove in Gen2 stack.'); + const logicalResourceIds = [...this.gen2ResourcesToRemove.keys()]; + const updatedGen2Template = await this.removeGen2ResourcesFromGen2Stack(oldGen2Template, logicalResourceIds); + return { + oldTemplate: oldGen2Template, + newTemplate: updatedGen2Template, + parameters: Parameters, + }; + } + + public generateStackRefactorTemplates(gen1Template: CFNTemplate, gen2Template: CFNTemplate): CFNStackRefactorTemplates { + return this.generateRefactorTemplates(this.gen1ResourcesToMove, this.gen2ResourcesToRemove, gen1Template, gen2Template); + } + + public async readTemplate(stackId: string) { + const getTemplateResponse = await this.cfnClient.send( + new GetTemplateCommand({ + StackName: stackId, + }), + ); + const templateBody = getTemplateResponse.TemplateBody; + assert(templateBody); + return JSON.parse(templateBody) as CFNTemplate; + } + + public async describeStack(stackId: string) { + return ( + await this.cfnClient.send( + new DescribeStacksCommand({ + StackName: stackId, + }), + ) + ).Stacks?.[0]; + } + + private async describeStackResources(stackId: string) { + const { StackResources } = await this.cfnClient.send( + new DescribeStackResourcesCommand({ + StackName: stackId, + }), + ); + + assert(StackResources && StackResources.length > 0); + + return StackResources; + } + + private removeGen1ResourcesFromGen1Stack(gen1Template: CFNTemplate, resourcesToRefactor: string[]) { + const resources = gen1Template.Resources; + assert(resources); + for (const resourceToRefactor of resourcesToRefactor) { + delete resources[resourceToRefactor]; + } + return gen1Template; + } + + private addGen1ResourcesToGen2Stack( + resolvedGen1Template: CFNTemplate, + resourcesToRefactor: string[], + gen1ToGen2ResourceLogicalIdMapping: Map, + gen2Template: CFNTemplate, + ) { + const resources = gen2Template.Resources; + assert(resources); + for (const resourceToRefactor of resourcesToRefactor) { + const gen2ResourceLogicalId = gen1ToGen2ResourceLogicalIdMapping.get(resourceToRefactor); + assert(gen2ResourceLogicalId); + resources[gen2ResourceLogicalId] = resolvedGen1Template.Resources[resourceToRefactor]; + // replace Gen1 dependency with Gen2 counterparts for Gen1 resources being moved over to Gen2 + const dependencies = resources[gen2ResourceLogicalId].DependsOn; + if (!dependencies) continue; + resources[gen2ResourceLogicalId].DependsOn = + dependencies?.map((dependency) => { + if (gen1ToGen2ResourceLogicalIdMapping.has(dependency)) { + const gen2DependencyName = gen1ToGen2ResourceLogicalIdMapping.get(dependency); + assert(gen2DependencyName); + return gen2DependencyName; + } else return dependency; + }) ?? []; + } + return gen2Template; + } + + private buildGen1ToGen2ResourceLogicalIdMapping(gen1ResourceMap: Map, gen2ResourceMap: Map) { + const clonedGen1ResourceMap = new Map(gen1ResourceMap); + const clonedGen2ResourceMap = new Map(gen2ResourceMap); + const gen1ToGen2ResourceLogicalIdMapping = new Map(); + for (const [gen1ResourceLogicalId, gen1Resource] of clonedGen1ResourceMap) { + for (const [gen2ResourceLogicalId, gen2Resource] of clonedGen2ResourceMap) { + if (gen2Resource.Type !== gen1Resource.Type) { + continue; + } + // Since we have 2 app clients, we want to map the corresponding app clients (Web->Web, Native->Native) + // In gen1, we differentiate clients with Web. In gen2, we differentiate with Native. + const isWebClient = gen1ResourceLogicalId === GEN1_WEB_APP_CLIENT && !gen2ResourceLogicalId.includes(GEN2_NATIVE_APP_CLIENT); + const isNativeClient = gen1ResourceLogicalId !== GEN1_WEB_APP_CLIENT && gen2ResourceLogicalId.includes(GEN2_NATIVE_APP_CLIENT); + const foundUserPoolClientPair = gen1Resource.Type === CFN_AUTH_TYPE.UserPoolClient && (isWebClient || isNativeClient); + const foundUserPoolGroupPair = + gen1Resource.Type === CFN_AUTH_TYPE.UserPoolGroup && gen2ResourceLogicalId.includes(gen1ResourceLogicalId); + const foundIamRolePair = gen1Resource.Type === CFN_IAM_TYPE.Role && gen2ResourceLogicalId.includes(gen1ResourceLogicalId); + if ( + !RESOURCE_TYPES_WITH_MULTIPLE_RESOURCES.includes(gen1Resource.Type) || + foundUserPoolClientPair || + foundUserPoolGroupPair || + foundIamRolePair + ) { + gen1ToGen2ResourceLogicalIdMapping.set(gen1ResourceLogicalId, gen2ResourceLogicalId); + clonedGen1ResourceMap.delete(gen1ResourceLogicalId); + clonedGen2ResourceMap.delete(gen2ResourceLogicalId); + break; + } + } + } + return gen1ToGen2ResourceLogicalIdMapping; + } + + private async removeGen2ResourcesFromGen2Stack(gen2Template: CFNTemplate, resourcesToRemove: string[]) { + const clonedGen2Template = JSON.parse(JSON.stringify(gen2Template)); + const stackOutputs = this.gen2DescribeStacksResponse?.Outputs; + assert(stackOutputs); + const stackResources = await this.describeStackResources(this.gen2StackId); + const gen2TemplateWithDepsResolved = new CfnDependencyResolver(clonedGen2Template).resolve(resourcesToRemove); + const resolvedRefsGen2Template = new CfnOutputResolver(gen2TemplateWithDepsResolved, this.region, this.accountId).resolve( + resourcesToRemove, + stackOutputs, + stackResources, + ); + resourcesToRemove.forEach((logicalResourceId) => { + delete resolvedRefsGen2Template.Resources[logicalResourceId]; + }); + return resolvedRefsGen2Template; + } + + public generateRefactorTemplates( + gen1ResourcesToMove: Map, + gen2ResourcesToRemove: Map, + gen1Template: CFNTemplate, + gen2Template: CFNTemplate, + sourceToDestinationResourceLogicalIdMapping?: Map, + ): CFNStackRefactorTemplates { + const gen1LogicalResourceIds = [...gen1ResourcesToMove.keys()]; + const gen1ToGen2ResourceLogicalIdMapping = + sourceToDestinationResourceLogicalIdMapping ?? + this.buildGen1ToGen2ResourceLogicalIdMapping(gen1ResourcesToMove, gen2ResourcesToRemove); + const clonedGen1Template = JSON.parse(JSON.stringify(gen1Template)); + const clonedGen2Template = JSON.parse(JSON.stringify(gen2Template)); + const gen2TemplateForRefactor = this.addGen1ResourcesToGen2Stack( + clonedGen1Template, + gen1LogicalResourceIds, + gen1ToGen2ResourceLogicalIdMapping, + clonedGen2Template, + ); + + const gen1TemplateForRefactor = this.removeGen1ResourcesFromGen1Stack(clonedGen1Template, gen1LogicalResourceIds); + return { + sourceTemplate: gen1TemplateForRefactor, + destinationTemplate: gen2TemplateForRefactor, + logicalIdMapping: gen1ToGen2ResourceLogicalIdMapping, + }; + } +} + +export default CategoryTemplateGenerator; diff --git a/packages/amplify-migration-template-gen/src/cfn-stack-name-extractor.ts b/packages/amplify-migration-template-gen/src/cfn-stack-name-extractor.ts new file mode 100644 index 00000000000..b5ad5f2540d --- /dev/null +++ b/packages/amplify-migration-template-gen/src/cfn-stack-name-extractor.ts @@ -0,0 +1,3 @@ +export default function extractStackNameFromId(stackId: string): string { + return stackId.split('/')[1]; +} diff --git a/packages/amplify-migration-template-gen/src/cfn-stack-refactor-updater.ts b/packages/amplify-migration-template-gen/src/cfn-stack-refactor-updater.ts new file mode 100644 index 00000000000..a242e0350a4 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/cfn-stack-refactor-updater.ts @@ -0,0 +1,122 @@ +import { + CloudFormationClient, + CreateStackRefactorCommand, + CreateStackRefactorCommandInput, + DescribeStackRefactorCommand, + DescribeStackRefactorCommandOutput, + ExecuteStackRefactorCommand, + StackRefactorExecutionStatus, + StackRefactorStatus, +} from '@aws-sdk/client-cloudformation'; +import assert from 'node:assert'; +import { CFNStackStatus, FailedRefactorResponse } from './types'; +import { pollStackForCompletionState } from './cfn-stack-updater'; + +const POLL_ATTEMPTS = 30; +const POLL_INTERVAL_MS = 1500; +const COMPLETION_STATE = '_COMPLETE'; +const FAILED_STATE = '_FAILED'; +export const UPDATE_COMPLETE = 'UPDATE_COMPLETE'; +/** + * Refactors a stack with given source and destination template. + * @param cfnClient + * @param createStackRefactorCommandInput + * @param attempts number of attempts to poll CFN stack for update completion state. The interval between the polls is 1.5 seconds. + * @returns a tuple containing the success/failed state and the reason if any. + */ +export async function tryRefactorStack( + cfnClient: CloudFormationClient, + createStackRefactorCommandInput: CreateStackRefactorCommandInput, + attempts = POLL_ATTEMPTS, +): Promise<[boolean, FailedRefactorResponse | undefined]> { + const { StackRefactorId } = await cfnClient.send(new CreateStackRefactorCommand(createStackRefactorCommandInput)); + assert(StackRefactorId); + let describeStackRefactorResponse = await pollStackRefactorForCompletionState( + cfnClient, + StackRefactorId, + (_describeStackRefactorResponse: DescribeStackRefactorCommandOutput) => { + assert(_describeStackRefactorResponse.Status); + return ( + _describeStackRefactorResponse.Status.endsWith(COMPLETION_STATE) || _describeStackRefactorResponse.Status.endsWith(FAILED_STATE) + ); + }, + attempts, + ); + if (describeStackRefactorResponse.Status !== StackRefactorStatus.CREATE_COMPLETE) { + return [ + false, + { + status: describeStackRefactorResponse.Status, + reason: describeStackRefactorResponse.StatusReason, + stackRefactorId: StackRefactorId, + }, + ]; + } + await cfnClient.send( + new ExecuteStackRefactorCommand({ + StackRefactorId, + }), + ); + describeStackRefactorResponse = await pollStackRefactorForCompletionState( + cfnClient, + StackRefactorId, + (describeStackRefactorResponse: DescribeStackRefactorCommandOutput) => { + assert(describeStackRefactorResponse.ExecutionStatus); + return ( + describeStackRefactorResponse.ExecutionStatus.endsWith(COMPLETION_STATE) || + describeStackRefactorResponse.ExecutionStatus.endsWith(FAILED_STATE) + ); + }, + attempts, + ); + if (describeStackRefactorResponse.ExecutionStatus !== StackRefactorExecutionStatus.EXECUTE_COMPLETE) { + return [ + false, + { + status: describeStackRefactorResponse.ExecutionStatus, + stackRefactorId: StackRefactorId, + reason: describeStackRefactorResponse.ExecutionStatusReason, + }, + ]; + } + + const sourceStackName = createStackRefactorCommandInput.StackDefinitions?.[0].StackName; + const destinationStackName = createStackRefactorCommandInput.StackDefinitions?.[1].StackName; + assert(sourceStackName); + assert(destinationStackName); + const sourceStackStatus = await pollStackForCompletionState(cfnClient, sourceStackName); + assert(sourceStackStatus === CFNStackStatus.UPDATE_COMPLETE, `${sourceStackName} was not updated successfully.`); + const destinationStackStatus = await pollStackForCompletionState(cfnClient, destinationStackName); + assert(destinationStackStatus === CFNStackStatus.UPDATE_COMPLETE, `${destinationStackName} was not updated successfully.`); + + return [true, undefined]; +} + +/** + * Polls a stack refactor operation for completion state + * @param cfnClient + * @param stackRefactorId + * @param exitCondition a function that determines if the stack refactor operation has reached a completion state. + * @param attempts number of attempts to poll for completion. + * @returns the stack status + */ +async function pollStackRefactorForCompletionState( + cfnClient: CloudFormationClient, + stackRefactorId: string, + exitCondition: (describeStackRefactorResponse: DescribeStackRefactorCommandOutput) => boolean, + attempts: number, +): Promise { + do { + const describeStackRefactorResponse = await cfnClient.send( + new DescribeStackRefactorCommand({ + StackRefactorId: stackRefactorId, + }), + ); + if (exitCondition(describeStackRefactorResponse)) { + return describeStackRefactorResponse; + } + await new Promise((res) => setTimeout(() => res(''), POLL_INTERVAL_MS)); + attempts--; + } while (attempts > 0); + throw new Error(`Stack refactor ${stackRefactorId} did not reach a completion state within the given time period.`); +} diff --git a/packages/amplify-migration-template-gen/src/cfn-stack-updater.ts b/packages/amplify-migration-template-gen/src/cfn-stack-updater.ts new file mode 100644 index 00000000000..e373eedc9f3 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/cfn-stack-updater.ts @@ -0,0 +1,74 @@ +import { CloudFormationClient, DescribeStacksCommand, Parameter, UpdateStackCommand } from '@aws-sdk/client-cloudformation'; +import { CFNTemplate } from './types'; +import assert from 'node:assert'; + +const POLL_ATTEMPTS = 60; +const POLL_INTERVAL_MS = 1500; +const NO_UPDATES_MESSAGE = 'No updates are to be performed'; +const CFN_IAM_CAPABILIY = 'CAPABILITY_NAMED_IAM'; +const COMPLETION_STATE = '_COMPLETE'; +export const UPDATE_COMPLETE = 'UPDATE_COMPLETE'; +/** + * Updates a stack with given template. If no updates are present, it no-ops. + * @param cfnClient + * @param stackName + * @param parameters + * @param templateBody + * @param attempts number of attempts to poll CFN stack for update completion state. The interval between the polls is 1.5 seconds. + */ +export async function tryUpdateStack( + cfnClient: CloudFormationClient, + stackName: string, + parameters: Parameter[], + templateBody: CFNTemplate, + attempts = POLL_ATTEMPTS, +): Promise { + try { + await cfnClient.send( + new UpdateStackCommand({ + TemplateBody: JSON.stringify(templateBody), + Parameters: parameters, + StackName: stackName, + Capabilities: [CFN_IAM_CAPABILIY], + Tags: [], + }), + ); + return pollStackForCompletionState(cfnClient, stackName, attempts); + } catch (e) { + if (!e.message.includes(NO_UPDATES_MESSAGE)) { + throw e; + } + return UPDATE_COMPLETE; + } +} + +/** + * Polls a stack for completion state + * @param cfnClient + * @param stackName + * @param attempts number of attempts to poll for completion. + * @returns the stack status + */ +export async function pollStackForCompletionState( + cfnClient: CloudFormationClient, + stackName: string, + attempts: number = POLL_ATTEMPTS, +): Promise { + do { + const { Stacks } = await cfnClient.send( + new DescribeStacksCommand({ + StackName: stackName, + }), + ); + const stack = Stacks?.[0]; + assert(stack); + const stackStatus = stack.StackStatus; + assert(stackStatus); + if (stackStatus?.endsWith(COMPLETION_STATE)) { + return stackStatus; + } + await new Promise((res) => setTimeout(() => res(''), POLL_INTERVAL_MS)); + attempts--; + } while (attempts > 0); + throw new Error(`Stack ${stackName} did not reach a completion state within the given time period.`); +} diff --git a/packages/amplify-migration-template-gen/src/custom-test-matchers.ts b/packages/amplify-migration-template-gen/src/custom-test-matchers.ts new file mode 100644 index 00000000000..71ceafaa429 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/custom-test-matchers.ts @@ -0,0 +1,52 @@ +import { + DescribeStackResourcesCommand, + DescribeStackResourcesCommandInput, + UpdateStackCommand, + DescribeStacksCommand, + DescribeStacksCommandInput, + UpdateStackCommandInput, + CreateStackRefactorCommandInput, + ExecuteStackRefactorCommandInput, + DescribeStackRefactorCommandInput, + CreateStackRefactorCommand, + ExecuteStackRefactorCommand, + DescribeStackRefactorCommand, +} from '@aws-sdk/client-cloudformation'; + +type CFNCommand = DescribeStackResourcesCommand | DescribeStacksCommand | UpdateStackCommand; +type CFNCommandType = + | typeof DescribeStackResourcesCommand + | typeof DescribeStacksCommand + | typeof UpdateStackCommand + | typeof CreateStackRefactorCommand + | typeof ExecuteStackRefactorCommand + | typeof DescribeStackRefactorCommand; +type CFNCommandInput = + | DescribeStackResourcesCommandInput + | DescribeStacksCommandInput + | UpdateStackCommandInput + | CreateStackRefactorCommandInput + | ExecuteStackRefactorCommandInput + | DescribeStackRefactorCommandInput; + +export const toBeACloudFormationCommand = (actual: [CFNCommand], expectedInput: CFNCommandInput, expectedType: CFNCommandType) => { + const actualInstance = actual[0]; + expect(actualInstance.input).toEqual(expectedInput); + const constructorName = actualInstance.constructor.name; + const pass = constructorName === expectedType.prototype.constructor.name; + + return { + pass, + message: () => `expected ${actual} to be instance of ${constructorName}`, + }; +}; + +declare global { + // Needed for custom matchers. + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Matchers { + toBeACloudFormationCommand(expectedInput: CFNCommandInput, expectedType: CFNCommandType): R; + } + } +} diff --git a/packages/amplify-migration-template-gen/src/index.ts b/packages/amplify-migration-template-gen/src/index.ts new file mode 100644 index 00000000000..cd440f6b919 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/index.ts @@ -0,0 +1,2 @@ +export * from './template-generator'; +export { ResourceMapping } from './types'; diff --git a/packages/amplify-migration-template-gen/src/migration-readme-generator.test.ts b/packages/amplify-migration-template-gen/src/migration-readme-generator.test.ts new file mode 100644 index 00000000000..5e6b535f797 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/migration-readme-generator.test.ts @@ -0,0 +1,65 @@ +import MigrationReadMeGenerator from './migration-readme-generator'; +import fs from 'node:fs/promises'; + +jest.mock('node:fs/promises'); + +describe('MigrationReadMeGenerator', () => { + const PATH = 'test'; + const migrationReadMeGenerator = new MigrationReadMeGenerator({ + path: PATH, + categories: ['auth', 'storage'], + hasOAuthEnabled: false, + }); + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should initialize migration readme', async () => { + await migrationReadMeGenerator.initialize(); + expect(fs.writeFile).toHaveBeenCalledWith('test/MIGRATION_README.md', '', { + encoding: 'utf8', + }); + }); + + it('should render step1 without oauth related information', async () => { + await migrationReadMeGenerator.renderStep1(); + expect(fs.appendFile).toHaveBeenCalledWith( + 'test/MIGRATION_README.md', + `## REDEPLOY GEN2 APPLICATION +1.a) Uncomment the following lines in \`amplify/backend.ts\` file: +\`\`\` +s3Bucket.bucketName = YOUR_GEN1_BUCKET_NAME; +\`\`\` + +\`\`\` +Tags.of(backend.stack).add("gen1-migrated-app", "true"); +\`\`\` + +1.b) Trigger a CI/CD build via hosting by committing \`amplify/backend.ts\` file to your Git repository`, + ); + }); + + it('should render step1 without storage', async () => { + const PATH = 'test'; + const migrationReadMeGenerator = new MigrationReadMeGenerator({ + path: PATH, + categories: ['auth'], + hasOAuthEnabled: true, + }); + await migrationReadMeGenerator.renderStep1(); + expect(fs.appendFile).toHaveBeenCalledWith( + 'test/MIGRATION_README.md', + `## REDEPLOY GEN2 APPLICATION +1.a) Uncomment the following lines in \`amplify/backend.ts\` file: + +\`\`\` +backend.auth.resources.userPool.node.tryRemoveChild('UserPoolDomain'); +\`\`\` +\`\`\` +Tags.of(backend.stack).add("gen1-migrated-app", "true"); +\`\`\` + +1.b) Trigger a CI/CD build via hosting by committing \`amplify/backend.ts\` file to your Git repository`, + ); + }); +}); diff --git a/packages/amplify-migration-template-gen/src/migration-readme-generator.ts b/packages/amplify-migration-template-gen/src/migration-readme-generator.ts new file mode 100644 index 00000000000..f2e0d89f971 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/migration-readme-generator.ts @@ -0,0 +1,50 @@ +import fs from 'node:fs/promises'; +import { CATEGORY } from './types'; + +interface MigrationReadMeGeneratorOptions { + path: string; + categories: CATEGORY[]; + hasOAuthEnabled: boolean; +} + +class MigrationReadmeGenerator { + private readonly path: string; + private readonly migrationReadMePath: string; + private readonly categories: CATEGORY[]; + private readonly hasOAuthEnabled: boolean; + + constructor({ path, categories, hasOAuthEnabled }: MigrationReadMeGeneratorOptions) { + this.path = path; + this.migrationReadMePath = `${this.path}/MIGRATION_README.md`; + this.categories = categories; + this.hasOAuthEnabled = hasOAuthEnabled; + } + + async initialize(): Promise { + await fs.writeFile(this.migrationReadMePath, ``, { encoding: 'utf8' }); + } + + async renderStep1() { + const s3BucketChanges = `\`\`\` +s3Bucket.bucketName = YOUR_GEN1_BUCKET_NAME; +\`\`\``; + const userPoolDomainRemoval = `\`\`\` +backend.auth.resources.userPool.node.tryRemoveChild('UserPoolDomain'); +\`\`\``; + const gen2Tag = `\`\`\` +Tags.of(backend.stack).add("gen1-migrated-app", "true"); +\`\`\``; + await fs.appendFile( + this.migrationReadMePath, + `## REDEPLOY GEN2 APPLICATION +1.a) Uncomment the following lines in \`amplify/backend.ts\` file: +${this.categories.includes('storage') ? s3BucketChanges : ''} +${this.hasOAuthEnabled ? userPoolDomainRemoval : ''} +${gen2Tag} + +1.b) Trigger a CI/CD build via hosting by committing \`amplify/backend.ts\` file to your Git repository`, + ); + } +} + +export default MigrationReadmeGenerator; diff --git a/packages/amplify-migration-template-gen/src/oauth-values-retriever.test.ts b/packages/amplify-migration-template-gen/src/oauth-values-retriever.test.ts new file mode 100644 index 00000000000..c6fa94d66d1 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/oauth-values-retriever.test.ts @@ -0,0 +1,42 @@ +import oauthValuesRetriever from './oauth-values-retriever'; +import { SSMClient } from '@aws-sdk/client-ssm'; +import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; + +const INVALID_OAUTH_METADATA_PARAM = 'Invalid Gen1 OAuth provider metadata'; +const APP_ID = 'appId'; +const ENV_NAME = 'envName'; +const USER_POOL_ID = 'userPoolId'; + +// This test suite covers negative cases. Happy path cases are covered in its consumer (category-template-generator.test.ts) +describe('OAuthValuesRetriever', () => { + it('should fail if the oauth param is not an array', async () => { + await expect( + oauthValuesRetriever({ + appId: APP_ID, + environmentName: ENV_NAME, + userPoolId: USER_POOL_ID, + oAuthParameter: { + ParameterKey: 'hostedUIProviderMeta', + ParameterValue: JSON.stringify({}), + }, + ssmClient: new SSMClient(), + cognitoIdpClient: new CognitoIdentityProviderClient(), + }), + ).rejects.toThrowError(INVALID_OAUTH_METADATA_PARAM); + }); + it('should fail if the oauth param does not have provider info', async () => { + await expect( + oauthValuesRetriever({ + appId: APP_ID, + environmentName: ENV_NAME, + userPoolId: USER_POOL_ID, + oAuthParameter: { + ParameterKey: 'hostedUIProviderMeta', + ParameterValue: JSON.stringify([{}]), + }, + ssmClient: new SSMClient(), + cognitoIdpClient: new CognitoIdentityProviderClient(), + }), + ).rejects.toThrowError(INVALID_OAUTH_METADATA_PARAM); + }); +}); diff --git a/packages/amplify-migration-template-gen/src/oauth-values-retriever.ts b/packages/amplify-migration-template-gen/src/oauth-values-retriever.ts new file mode 100644 index 00000000000..fd7ec241e02 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/oauth-values-retriever.ts @@ -0,0 +1,99 @@ +import assert from 'node:assert'; +import { GetParameterCommand, SSMClient } from '@aws-sdk/client-ssm'; +import { CognitoIdentityProviderClient, DescribeIdentityProviderCommand } from '@aws-sdk/client-cognito-identity-provider'; +import { Parameter } from '@aws-sdk/client-cloudformation'; +import { HostedUIProviderMeta, OAuthClient } from './types'; + +const INVALID_OAUTH_GEN1_PROVIDER_METADATA_ERROR = 'Invalid Gen1 OAuth provider metadata'; + +const isHostedProviderMetadata = (parsedValue: unknown): parsedValue is HostedUIProviderMeta => { + return typeof parsedValue === 'object' && parsedValue !== null && 'ProviderName' in parsedValue; +}; + +export const constructSignInWithApplePrivateKeyParamName = (appId: string, environment: string): string => { + return `/amplify/${appId}/${environment}/AMPLIFY_SIWA_PRIVATE_KEY`; +}; + +type RetrieveOAuthValuesParameters = { + ssmClient: SSMClient; + cognitoIdpClient: CognitoIdentityProviderClient; + oAuthParameter: Parameter; + userPoolId: string; + appId: string; + environmentName: string; +}; +/** + * Retrieves OAuth values from Cognito and SSM + * @param ssmClient + * @param cognitoIdpClient + * @param oAuthParameter + * @param userPoolId + * @param appId + * @param environmentName + * @returns RetrieveOAuthValuesParameters + */ +const retrieveOAuthValues = async ({ + ssmClient, + cognitoIdpClient, + oAuthParameter, + userPoolId, + appId, + environmentName, +}: RetrieveOAuthValuesParameters) => { + const value = oAuthParameter.ParameterValue; + assert(value); + const parsedValue = JSON.parse(value); + if (!Array.isArray(parsedValue) || parsedValue.length === 0) { + throw new Error(INVALID_OAUTH_GEN1_PROVIDER_METADATA_ERROR); + } + + const oAuthClientValues: OAuthClient[] = []; + for (const provider of parsedValue) { + if (!isHostedProviderMetadata(provider)) { + throw new Error(INVALID_OAUTH_GEN1_PROVIDER_METADATA_ERROR); + } + + const { ProviderName } = provider; + const { IdentityProvider } = await cognitoIdpClient.send( + new DescribeIdentityProviderCommand({ + UserPoolId: userPoolId, + ProviderName, + }), + ); + const providerDetails = IdentityProvider?.ProviderDetails; + assert(providerDetails); + const { client_id, client_secret, team_id, key_id } = providerDetails; + assert(client_id); + if (ProviderName === 'SignInWithApple') { + assert(team_id); + assert(key_id); + // Retrieve private key + const { Parameter: PrivateKeyParameter } = await ssmClient.send( + new GetParameterCommand({ + Name: constructSignInWithApplePrivateKeyParamName(appId, environmentName), + WithDecryption: true, + }), + ); + assert(PrivateKeyParameter); + const privateKey = PrivateKeyParameter.Value; + assert(privateKey); + oAuthClientValues.push({ + ProviderName, + client_id, + team_id, + key_id, + private_key: privateKey, + }); + } else { + assert(client_secret); + oAuthClientValues.push({ + ProviderName, + client_id, + client_secret, + }); + } + } + return oAuthClientValues; +}; + +export default retrieveOAuthValues; diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-condition-resolver.test.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-condition-resolver.test.ts new file mode 100644 index 00000000000..557b3ecaa8e --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-condition-resolver.test.ts @@ -0,0 +1,127 @@ +import CFNConditionResolver from './cfn-condition-resolver'; +import { CFNTemplate } from '../types'; + +describe('CFNConditionResolver', () => { + const template: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'CFN template with conditions', + Conditions: { + MyCond: { + 'Fn::Equals': [{ Ref: 'EnvType' }, 'prod'], + }, + MyNotCond: { + 'Fn::Not': [{ Condition: 'MyCond' }], + }, + MyOrCondition: { + 'Fn::Or': [{ 'Fn::Equals': ['prod', { Ref: 'EnvType' }] }, { Condition: 'MyCond' }], + }, + MyAndCondition: { + 'Fn::And': [{ 'Fn::Equals': ['prod', { Ref: 'EnvType' }] }, { Condition: 'MyCond' }], + }, + }, + Resources: { + MyIfConditionResource: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: { + 'Fn::If': ['MyCond', 'my-bucket-prod', 'my-bucket-dev'], + }, + }, + }, + MyOrConditionResource: { + Type: 'AWS::S3::Bucket', + Condition: 'MyOrCondition', + Properties: { + BucketName: 'my-bucket-prod1', + }, + }, + MyAndConditionResource: { + Type: 'AWS::S3::Bucket', + Condition: 'MyAndCondition', + Properties: { + BucketName: 'my-bucket-prod2', + }, + }, + MyNotConditionResource: { + Type: 'AWS::S3::Bucket', + Condition: 'MyNotCond', + Properties: { + BucketName: 'my-bucket-prod3', + }, + }, + }, + Parameters: { + EnvType: { + Type: 'String', + Default: 'dev', + }, + }, + Outputs: { + BucketName: { + Value: { Ref: 'MyResource' }, + Description: 'Name of the S3 bucket', + }, + }, + }; + const expectedResolvedTemplate: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'CFN template with conditions', + Conditions: { + MyCond: { + 'Fn::Equals': [{ Ref: 'EnvType' }, 'prod'], + }, + MyNotCond: { + 'Fn::Not': [{ Condition: 'MyCond' }], + }, + MyOrCondition: { + 'Fn::Or': [{ 'Fn::Equals': ['prod', { Ref: 'EnvType' }] }, { Condition: 'MyCond' }], + }, + MyAndCondition: { + 'Fn::And': [{ 'Fn::Equals': ['prod', { Ref: 'EnvType' }] }, { Condition: 'MyCond' }], + }, + }, + Resources: { + MyIfConditionResource: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: 'my-bucket-prod', + }, + }, + MyOrConditionResource: { + Type: 'AWS::S3::Bucket', + Condition: 'MyOrCondition', + Properties: { + BucketName: 'my-bucket-prod1', + }, + }, + MyAndConditionResource: { + Type: 'AWS::S3::Bucket', + Condition: 'MyAndCondition', + Properties: { + BucketName: 'my-bucket-prod2', + }, + }, + }, + Parameters: { + EnvType: { + Type: 'String', + Default: 'dev', + }, + }, + Outputs: { + BucketName: { + Value: { Ref: 'MyResource' }, + Description: 'Name of the S3 bucket', + }, + }, + }; + it('should resolve the conditions in the template', () => { + const resolvedTemplate = new CFNConditionResolver(template).resolve([ + { + ParameterKey: 'EnvType', + ParameterValue: 'prod', + }, + ]); + expect(resolvedTemplate).toEqual(expectedResolvedTemplate); + }); +}); diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-condition-resolver.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-condition-resolver.ts new file mode 100644 index 00000000000..1bec33738fd --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-condition-resolver.ts @@ -0,0 +1,168 @@ +import { CFNConditionFunction, CFNConditionFunctionStatement, CFNFunction, CFNResource, CFNTemplate } from '../types'; +import assert from 'node:assert'; +import { Parameter } from '@aws-sdk/client-cloudformation'; + +/** + * Class to resolve conditions in a CloudFormation template. + * This is needed prior to a stack refactor since same conditions and params are not present in Gen1 and Gen2 stacks + * and the resource being moved needs to have its condition resolved. + */ +class CFNConditionResolver { + private readonly conditions: Record | undefined; + constructor(private readonly template: CFNTemplate) { + this.conditions = template.Conditions; + } + + public resolve(parameters: Parameter[]) { + if (!this.conditions || Object.keys(this.conditions).length === 0) return this.template; + + const clonedTemplate = JSON.parse(JSON.stringify(this.template)) as CFNTemplate; + const conditionValueMap = new Map(); + Object.entries(this.conditions).forEach(([conditionKey, conditionValue]) => { + const fnType = Object.keys(conditionValue)[0]; + if (Object.values(CFNFunction).includes(fnType as CFNFunction)) { + const conditionStatements = conditionValue[fnType as keyof CFNConditionFunction]; + const [leftStatement, rightStatement] = conditionStatements as [CFNConditionFunctionStatement, CFNConditionFunctionStatement]; + const result = this.resolveCondition(leftStatement, rightStatement, parameters, fnType as CFNFunction); + conditionValueMap.set(conditionKey, result); + } + }); + + this.resolveConditionInResources(clonedTemplate.Resources, conditionValueMap); + + return clonedTemplate; + } + + private resolveCondition( + leftStatement: CFNConditionFunctionStatement, + rightStatement: CFNConditionFunctionStatement, + params: Parameter[], + fnType: CFNFunction, + ): boolean { + assert(this.conditions); + let resolvedLeftStatement: boolean | string | undefined; + let resolvedRightStatement: boolean | string | undefined; + + if (typeof leftStatement !== 'object') { + resolvedLeftStatement = leftStatement; + } + if (typeof rightStatement !== 'object') { + resolvedRightStatement = rightStatement; + } + // Resolve nested condition + if (typeof leftStatement === 'object' && 'Condition' in leftStatement) { + const nestedConditionName = leftStatement.Condition; + const nestedCondition = this.conditions[nestedConditionName]; + const nestedFnType = Object.keys(nestedCondition)[0] as CFNFunction; + const [nestedLeftStatement, nestedRightStatement] = nestedCondition[nestedFnType as keyof CFNConditionFunction] as [ + CFNConditionFunctionStatement, + CFNConditionFunctionStatement, + ]; + resolvedLeftStatement = this.resolveCondition(nestedLeftStatement, nestedRightStatement, params, nestedFnType); + } + if (typeof rightStatement === 'object' && 'Condition' in rightStatement) { + const nestedConditionName = rightStatement.Condition; + const nestedCondition = this.conditions[nestedConditionName]; + const nestedFnType = Object.keys(nestedCondition)[0] as CFNFunction; + const [nestedLeftStatement, nestedRightStatement] = nestedCondition[nestedFnType as keyof CFNConditionFunction] as [ + CFNConditionFunctionStatement, + CFNConditionFunctionStatement, + ]; + resolvedRightStatement = this.resolveCondition(nestedLeftStatement, nestedRightStatement, params, nestedFnType); + } + + // Resolve nested function + if (typeof leftStatement === 'object' && Object.values(CFNFunction).includes(Object.keys(leftStatement)[0] as CFNFunction)) { + const nestedCondition = leftStatement; + const nestedFnType = Object.keys(nestedCondition)[0] as CFNFunction; + const [nestedLeftStatement, nestedRightStatement] = nestedCondition[nestedFnType as keyof CFNConditionFunction] as [ + CFNConditionFunctionStatement, + CFNConditionFunctionStatement, + ]; + resolvedLeftStatement = this.resolveCondition(nestedLeftStatement, nestedRightStatement, params, nestedFnType); + } + if (typeof rightStatement === 'object' && Object.values(CFNFunction).includes(Object.keys(rightStatement)[0] as CFNFunction)) { + const nestedCondition = rightStatement; + const nestedFnType = Object.keys(nestedCondition)[0] as CFNFunction; + const [nestedLeftStatement, nestedRightStatement] = nestedCondition[nestedFnType as keyof CFNConditionFunction] as [ + CFNConditionFunctionStatement, + CFNConditionFunctionStatement, + ]; + resolvedRightStatement = this.resolveCondition(nestedLeftStatement, nestedRightStatement, params, nestedFnType); + } + + // Resolve parameter refs + if (typeof leftStatement === 'object' && 'Ref' in leftStatement) { + const parameterKey = leftStatement.Ref; + const value = params.find((p) => p.ParameterKey === parameterKey)?.ParameterValue; + assert(value); + resolvedLeftStatement = value; + } + if (rightStatement && typeof rightStatement === 'object' && 'Ref' in rightStatement) { + const parameterKey = rightStatement.Ref; + const value = params.find((p) => p.ParameterKey === parameterKey)?.ParameterValue; + assert(value); + resolvedRightStatement = value; + } + + let result: boolean | undefined; + switch (fnType) { + case CFNFunction.Equals: + result = resolvedLeftStatement === resolvedRightStatement; + break; + case CFNFunction.Not: + result = !resolvedLeftStatement; + break; + case CFNFunction.Or: + result = !!(resolvedLeftStatement || resolvedRightStatement); + break; + case CFNFunction.And: + result = !!(resolvedLeftStatement && resolvedRightStatement); + break; + default: + throw new Error(`Invalid ${fnType} condition`); + } + return result; + } + + private resolveConditionInResources(resources: Record, conditionValueMap: Map) { + Object.entries(resources).forEach(([logicalId, value]) => { + const condition = value.Condition; + if (condition && conditionValueMap.has(condition)) { + const result = conditionValueMap.get(condition); + // delete resources from template that have unmet condition + if (!result) { + delete resources[logicalId]; + } + } + const props = value.Properties; + Object.entries(props).forEach(([propName, propValue]) => { + if (typeof propValue === 'object') { + props[propName] = this.resolveIfCondition(propValue, conditionValueMap); + } else if (Array.isArray(propValue)) { + propValue.forEach((item, index) => { + if (typeof item === 'object') { + propValue[index] = this.resolveIfCondition(item, conditionValueMap); + } + }); + } + }); + }); + return resources; + } + + private resolveIfCondition(propValue: object, conditionValueMap: Map) { + let result = propValue; + if (CFNFunction.If in propValue) { + const ifCondition = propValue[CFNFunction.If] as [string, object, object]; + const conditionName = ifCondition[0]; + if (conditionValueMap.has(conditionName)) { + const conditionValue = conditionValueMap.get(conditionName); + result = conditionValue ? ifCondition[1] : ifCondition[2]; + } + } + return result; + } +} + +export default CFNConditionResolver; diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-dependency-resolver.test.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-dependency-resolver.test.ts new file mode 100644 index 00000000000..c2299439723 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-dependency-resolver.test.ts @@ -0,0 +1,81 @@ +import CfnDependencyResolver from './cfn-dependency-resolver'; +import { CFNTemplate } from '../types'; + +describe('CFNDependencyResolver', () => { + const template: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + BucketName: { + Type: 'String', + Description: 'Bucket name', + }, + }, + Outputs: { + BucketName: { + Description: 'Bucket name', + Value: { Ref: 'BucketName' }, + }, + }, + Resources: { + MyBucket: { + Type: 'AWS::S3::Bucket', + Properties: {}, + }, + Topic: { + Type: 'AWS::S3::Topic', + Properties: { + DisplayName: 'MyTopic', + }, + DependsOn: ['MyBucket'], + }, + MyUserPool: { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'MyUserPool', + }, + DependsOn: ['MyBucket', 'Topic'], + }, + }, + }; + const expectedTemplate: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + BucketName: { + Type: 'String', + Description: 'Bucket name', + }, + }, + Outputs: { + BucketName: { + Description: 'Bucket name', + Value: { Ref: 'BucketName' }, + }, + }, + Resources: { + MyBucket: { + Type: 'AWS::S3::Bucket', + Properties: {}, + }, + Topic: { + Type: 'AWS::S3::Topic', + Properties: { + DisplayName: 'MyTopic', + }, + DependsOn: [], + }, + MyUserPool: { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'MyUserPool', + }, + DependsOn: ['MyBucket'], + }, + }, + }; + it('should resolve dependencies', () => { + const dependencies = new CfnDependencyResolver(template).resolve(['MyBucket', 'MyUserPool']); + expect(dependencies).toEqual(expectedTemplate); + }); +}); diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-dependency-resolver.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-dependency-resolver.ts new file mode 100644 index 00000000000..be7a6f2093d --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-dependency-resolver.ts @@ -0,0 +1,27 @@ +import { CFNTemplate } from '../types'; + +class CfnDependencyResolver { + constructor(private readonly template: CFNTemplate) {} + + public resolve(resourcesToRefactor: string[]) { + const clonedGen1Template = JSON.parse(JSON.stringify(this.template)) as CFNTemplate; + const resources = clonedGen1Template.Resources; + Object.entries(resources).forEach(([logicalResourceId, resource]) => { + if (resource?.DependsOn) { + const deps = resource.DependsOn; + const depsInRefactor = deps.filter((dep: string) => resourcesToRefactor.includes(dep)); + // If resource is not part of refactor, it should not depend on any resource being moved as part of refactor. + if (depsInRefactor.length > 0 && !resourcesToRefactor.includes(logicalResourceId)) { + resource.DependsOn = deps.filter((dep: string) => !resourcesToRefactor.includes(dep)); + } + // If resource is part of refactor, it should only depend on resources being moved as part of refactor. + else if (resourcesToRefactor.includes(logicalResourceId) && deps.length > depsInRefactor.length) { + resource.DependsOn = depsInRefactor; + } + } + }); + return clonedGen1Template; + } +} + +export default CfnDependencyResolver; diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-output-resolver.test.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-output-resolver.test.ts new file mode 100644 index 00000000000..001eecef4c7 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-output-resolver.test.ts @@ -0,0 +1,488 @@ +import CfnOutputResolver from './cfn-output-resolver'; +import { CFNTemplate } from '../types'; + +describe('CFNOutputResolver', () => { + const template: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + AuthenticatedRole: { + Type: 'String', + }, + }, + Outputs: { + MyS3BucketOutputRef: { + Description: 'S3 bucket', + Value: { Ref: 'MyS3Bucket' }, + }, + AnotherOutput: { + Description: 'Another output', + Value: 'another value', + }, + MyUserPoolOutputRef: { + Description: 'User pool', + Value: { Ref: 'MyUserPool' }, + }, + MyUserPoolClientOutputRef: { + Description: 'User pool', + Value: { Ref: 'MyUserPoolClient' }, + }, + LambdaRole: { + Description: 'Lambda execution role', + Value: { Ref: 'TestLambdaRole' }, + }, + CreatedSNSRole: { + Description: 'role arn', + Value: { + 'Fn::GetAtt': ['SNSRole', 'Arn'], + }, + }, + HostedUIDomain: { + Description: 'HostedUIDomain', + Value: { + 'Fn::If': [ + 'ShouldNotCreateEnvResources', + 'HostedUIDomainLogicalId', + { + 'Fn::Join': [ + '-', + [ + { + Ref: 'hostedUIDomainName', + }, + { + Ref: 'env', + }, + ], + ], + }, + ], + }, + }, + snsTopicArn: { + Description: 'SnsTopicArn', + Value: { + Ref: 'snstopic', + }, + }, + }, + Resources: { + MyS3Bucket: { + Type: 'AWS::S3::Bucket', + Properties: { + UpdateReplacePolicy: 'Delete', + DeletionPolicy: 'Delete', + }, + }, + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: { 'Fn::GetAtt': ['MyS3Bucket', 'Arn'] }, + }, + ], + }, + Roles: [{ Ref: 'AuthenticatedRole' }], + }, + }, + MyUserPool: { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'MyUserPool', + SmsConfiguration: { + ExternalId: 'testsns_role_external_id', + SnsCallerArn: { + 'Fn::GetAtt': ['SNSRole', 'Arn'], + }, + }, + }, + }, + HostedUICustomResourcePolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'HostedUICustomResourcePolicy', + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: 'cognito-idp:DescribeUserPool', + Resource: { 'Fn::GetAtt': ['MyUserPool', 'Arn'] }, + }, + ], + }, + Roles: [{ Ref: 'AuthenticatedRole' }], + }, + }, + MyUserPoolClient: { + Type: 'AWS::Cognito::UserPoolClient', + Properties: { + ClientName: 'MyUserPoolClient', + UserPoolId: { Ref: 'MyUserPool' }, + }, + }, + TestLambda: { + Type: 'AWS::Lambda::Function', + Properties: { + FunctionName: 'TestLambda', + Handler: 'index.handler', + Role: { 'Fn::GetAtt': ['TestLambdaRole', 'Arn'] }, + Code: { + ZipFile: 'exports.handler = function() {}', + }, + Runtime: 'nodejs14.x', + }, + }, + TestLambdaRole: { + Type: 'AWS::IAM::Role', + Properties: { + RoleName: 'TestLambdaRole', + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: {}, + }, + ], + }, + }, + }, + SNSRole: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: 'cognito-idp.amazonaws.com', + }, + Action: ['sts:AssumeRole'], + Condition: { + StringEquals: { + 'sts:ExternalId': 'role_external_id', + }, + }, + }, + ], + }, + }, + }, + sqsqueue: { + Type: 'AWS::SQS::Queue', + Properties: { + QueueName: { + 'Fn::Join': ['', ['sqs-queue-amplifyCodegen-', 'dev']], + }, + }, + }, + snsSubscription: { + Type: 'AWS::SNS::Subscription', + Properties: { + Endpoint: { + 'Fn::GetAtt': ['sqsqueue', 'Arn'], + }, + Protocol: 'sqs', + TopicArn: { Ref: 'snsTopic' }, + }, + }, + snsTopic: { + Type: 'AWS::SNS::Topic', + Properties: { + TopicName: 'snsTopic', + }, + }, + CustomS3AutoDeleteObjectsCustomResourceProviderHandler: { + Type: 'AWS::Lambda::Function', + Properties: { + FunctionName: 'CustomS3AutoDeleteObjectsCustomResourceProviderHandler', + Handler: 'index.handler', + Code: { + ZipFile: 'exports.handler = function() {}', + }, + Runtime: 'nodejs14.x', + }, + }, + CustomS3AutoDeleteObjects: { + Type: 'Custom::S3AutoDeleteObjects', + Properties: { + ServiceToken: { + 'Fn::GetAtt': ['CustomS3AutoDeleteObjectsCustomResourceProviderHandler', 'Arn'], + }, + BucketName: { + Ref: 'MyS3Bucket', + }, + }, + }, + }, + }; + const expectedTemplate: CFNTemplate = { + AWSTemplateFormatVersion: '2010-09-09', + Description: 'Test template', + Parameters: { + AuthenticatedRole: { + Type: 'String', + }, + }, + Outputs: { + MyS3BucketOutputRef: { + Description: 'S3 bucket', + Value: 'test-bucket', + }, + AnotherOutput: { + Description: 'Another output', + Value: 'another value', + }, + MyUserPoolOutputRef: { + Description: 'User pool', + Value: 'test-userpoolid', + }, + MyUserPoolClientOutputRef: { + Description: 'User pool', + Value: 'test-userpoolclientid', + }, + LambdaRole: { + Description: 'Lambda execution role', + Value: 'arn:aws:iam::12345:role/lambda-exec-role', + }, + CreatedSNSRole: { + Description: 'role arn', + Value: 'arn:aws:iam::12345:role/sns12345-dev', + }, + HostedUIDomain: { + Description: 'HostedUIDomain', + Value: 'my-hosted-UI-domain', + }, + snsTopicArn: { + Description: 'SnsTopicArn', + Value: 'arn:aws:sns:us-east-1:12345:snsTopic', + }, + }, + Resources: { + MyS3Bucket: { + Type: 'AWS::S3::Bucket', + Properties: { + UpdateReplacePolicy: 'Delete', + DeletionPolicy: 'Delete', + }, + }, + MyS3BucketPolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'MyS3BucketPolicy', + PolicyDocument: { + Statement: [ + { + Effect: 'Allow', + Action: 's3:GetObject', + Resource: 'arn:aws:s3:::test-bucket', + }, + ], + }, + Roles: [{ Ref: 'AuthenticatedRole' }], + }, + }, + MyUserPool: { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'MyUserPool', + SmsConfiguration: { + ExternalId: 'testsns_role_external_id', + SnsCallerArn: 'arn:aws:iam::12345:role/sns12345-dev', + }, + }, + }, + HostedUICustomResourcePolicy: { + Type: 'AWS::IAM::Policy', + Properties: { + PolicyName: 'HostedUICustomResourcePolicy', + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Action: 'cognito-idp:DescribeUserPool', + Resource: 'arn:aws:cognito-idp:us-east-1:12345:userpool/test-userpoolid', + }, + ], + }, + Roles: [{ Ref: 'AuthenticatedRole' }], + }, + }, + MyUserPoolClient: { + Type: 'AWS::Cognito::UserPoolClient', + Properties: { + ClientName: 'MyUserPoolClient', + UserPoolId: 'test-userpoolid', + }, + }, + TestLambda: { + Type: 'AWS::Lambda::Function', + Properties: { + FunctionName: 'TestLambda', + Handler: 'index.handler', + Role: 'arn:aws:iam::12345:role/lambda-exec-role', + Code: { + ZipFile: 'exports.handler = function() {}', + }, + Runtime: 'nodejs14.x', + }, + }, + TestLambdaRole: { + Type: 'AWS::IAM::Role', + Properties: { + RoleName: 'TestLambdaRole', + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: {}, + }, + ], + }, + }, + }, + SNSRole: { + Type: 'AWS::IAM::Role', + Properties: { + AssumeRolePolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { + Service: 'cognito-idp.amazonaws.com', + }, + Action: ['sts:AssumeRole'], + Condition: { + StringEquals: { + 'sts:ExternalId': 'role_external_id', + }, + }, + }, + ], + }, + }, + }, + sqsqueue: { + Type: 'AWS::SQS::Queue', + Properties: { + QueueName: { + 'Fn::Join': ['', ['sqs-queue-amplifyCodegen-', 'dev']], + }, + }, + }, + snsSubscription: { + Type: 'AWS::SNS::Subscription', + Properties: { + Endpoint: 'arn:aws:sqs:us-east-1:12345:physicalIdSqs', + Protocol: 'sqs', + TopicArn: { Ref: 'snsTopic' }, + }, + }, + snsTopic: { + Type: 'AWS::SNS::Topic', + Properties: { + TopicName: 'snsTopic', + }, + }, + CustomS3AutoDeleteObjectsCustomResourceProviderHandler: { + Type: 'AWS::Lambda::Function', + Properties: { + FunctionName: 'CustomS3AutoDeleteObjectsCustomResourceProviderHandler', + Handler: 'index.handler', + Code: { + ZipFile: 'exports.handler = function() {}', + }, + Runtime: 'nodejs14.x', + }, + }, + CustomS3AutoDeleteObjects: { + Type: 'Custom::S3AutoDeleteObjects', + Properties: { + ServiceToken: 'arn:aws:lambda:us-east-1:12345:function:mycustomS3AutoDeleteObjectsLambdaFunction', + BucketName: 'test-bucket', + }, + }, + }, + }; + + it('should resolve output references', () => { + expect( + new CfnOutputResolver(template, 'us-east-1', '12345').resolve( + ['MyS3Bucket', 'MyUserPool', 'MyUserPoolClient'], + [ + { + OutputKey: 'MyS3BucketOutputRef', + OutputValue: 'test-bucket', + }, + { + OutputKey: 'AnotherOutput', + OutputValue: 'another value', + }, + { + OutputKey: 'MyUserPoolOutputRef', + OutputValue: 'test-userpoolid', + }, + { + OutputKey: 'MyUserPoolClientOutputRef', + OutputValue: 'test-userpoolclientid', + }, + { + OutputKey: 'LambdaRole', + OutputValue: 'arn:aws:iam::12345:role/lambda-exec-role', + }, + { + OutputKey: 'CreatedSNSRole', + OutputValue: 'arn:aws:iam::12345:role/sns12345-dev', + }, + { + OutputKey: 'HostedUIDomain', + OutputValue: 'my-hosted-UI-domain', + }, + { + OutputKey: 'snsTopicArn', + OutputValue: 'arn:aws:sns:us-east-1:12345:snsTopic', + }, + ], + [ + { + StackName: 'amplify-amplifycodegen-dev', + StackId: 'arn:aws:cloudformation:us-west-2:123456789:stack/amplify-amplifycodegen-dev', + LogicalResourceId: 'sqsqueue', + PhysicalResourceId: 'physicalIdSqs', + ResourceType: 'AWS::SQS::Queue', + Timestamp: new Date('2025-04-02T22:27:41.603000+00:00'), + ResourceStatus: 'CREATE_COMPLETE', + }, + { + StackName: 'amplify-amplifycodegen-dev', + StackId: 'arn:aws:cloudformation:us-west-2:123456789:stack/amplify-amplifycodegen-dev', + LogicalResourceId: 'snsSubscription', + PhysicalResourceId: 'physicalIdSns', + ResourceType: 'AWS::SNS::Subscription', + Timestamp: new Date('2025-04-02T22:27:41.603000+00:00'), + ResourceStatus: 'CREATE_COMPLETE', + }, + { + StackName: 'amplify-amplifycodegen-dev', + StackId: 'arn:aws:cloudformation:us-west-2:123456789:stack/amplify-amplifycodegen-dev', + LogicalResourceId: 'CustomS3AutoDeleteObjectsCustomResourceProviderHandler', + PhysicalResourceId: 'mycustomS3AutoDeleteObjectsLambdaFunction', + ResourceType: 'AWS::Lambda::Function', + Timestamp: new Date('2025-04-02T22:27:41.603000+00:00'), + ResourceStatus: 'CREATE_COMPLETE', + }, + ], + ), + ).toEqual(expectedTemplate); + }); +}); diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-output-resolver.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-output-resolver.ts new file mode 100644 index 00000000000..b607806da0b --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-output-resolver.ts @@ -0,0 +1,184 @@ +import { AWS_RESOURCE_ATTRIBUTES, CFN_RESOURCE_TYPES, CFNTemplate } from '../types'; +import assert from 'node:assert'; +import { Output, StackResource } from '@aws-sdk/client-cloudformation'; + +const REF = 'Ref'; +const GET_ATT = 'Fn::GetAtt'; + +/** + * This class is responsible for resolving logical resource ids in a CloudFormation template + * with their corresponding stack outputs. + */ +class CfnOutputResolver { + constructor(private readonly template: CFNTemplate, private readonly region: string, private readonly accountId: string) {} + + public resolve(logicalResourceIds: string[], stackOutputs: Output[], stackResources: StackResource[]): CFNTemplate { + const resources = this.template?.Resources; + assert(resources); + const clonedStackTemplate = JSON.parse(JSON.stringify(this.template)) as CFNTemplate; + const stackTemplateOutputs = this.template?.Outputs; + const stackTemplateResources = this.template?.Resources; + assert(stackTemplateResources); + assert(stackOutputs); + assert(stackTemplateOutputs); + let stackTemplateResourcesString = JSON.stringify(stackTemplateResources); + + Object.entries(stackTemplateOutputs).forEach(([outputKey, outputValue]) => { + const value = outputValue.Value; + const stackOutputValue = stackOutputs?.find((op) => op.OutputKey === outputKey)?.OutputValue; + assert(stackOutputValue); + + if (typeof value !== 'object') { + return; + } + + let logicalResourceId: string | undefined; + // Replace logicalId references using stack output values + if (REF in value && typeof value[REF] === 'string') { + logicalResourceId = value[REF]; + const outputRegexp = new RegExp(`{"${REF}":"${logicalResourceId}"}`, 'g'); + stackTemplateResourcesString = stackTemplateResourcesString.replaceAll(outputRegexp, `"${stackOutputValue}"`); + } else if (GET_ATT in value && Array.isArray(value[GET_ATT])) { + logicalResourceId = value[GET_ATT][0]; + } else { + return; + } + assert(logicalResourceId); + + // Replace Fn:GetAtt references using stack output values + const fnGetAttRegExp = new RegExp(`{"${GET_ATT}":\\["${logicalResourceId}","(?\\w+)"]}`, 'g'); + const fnGetAttRegExpResult = stackTemplateResourcesString.matchAll(fnGetAttRegExp).next(); + if (!fnGetAttRegExpResult.done) { + const resourceType = this.template.Resources[logicalResourceId].Type as CFN_RESOURCE_TYPES; + const attributeName = fnGetAttRegExpResult.value.groups?.AttributeName; + assert(attributeName); + const resource = this.getResourceAttribute(attributeName as AWS_RESOURCE_ATTRIBUTES, resourceType, stackOutputValue); + if (resource) { + stackTemplateResourcesString = stackTemplateResourcesString.replaceAll(fnGetAttRegExp, this.buildFnGetAttReplace(resource)); + } + } + }); + + // If not available in outputs, try to replace with their physical id counterparts. + stackTemplateResourcesString = this.tryReplaceLogicalResourceRefWithPhysicalId(stackTemplateResourcesString, stackResources); + + clonedStackTemplate.Resources = JSON.parse(stackTemplateResourcesString); + Object.entries(clonedStackTemplate.Outputs).forEach(([outputKey]) => { + const stackOutputValue = stackOutputs?.find((op) => op.OutputKey === outputKey)?.OutputValue; + assert(stackOutputValue); + clonedStackTemplate.Outputs[outputKey].Value = stackOutputValue; + }); + + return clonedStackTemplate; + } + + /** + * Currently, we only look for Fn:GetAtt references in the template and try to replace with physical resource ids (if they are not available in outputs) + * before performing the refactor. We can expand to look for other cases if need be. + * If this function expands, we can always move it into its own resolver. + * @param stackTemplateResourcesString + * @param stackResources + * @private + */ + private tryReplaceLogicalResourceRefWithPhysicalId(stackTemplateResourcesString: string, stackResources: StackResource[]) { + const fnGetAttRegExp = new RegExp(`{"${GET_ATT}":\\["(?\\w+)","(?\\w+)"]}`, 'g'); + const fnGetAttRegExpResult = stackTemplateResourcesString.matchAll(fnGetAttRegExp); + + for (const fnGetAttRegExpResultItem of fnGetAttRegExpResult) { + const groups = fnGetAttRegExpResultItem.groups; + if (groups && groups.LogicalResourceId) { + const stackResourceWithMatchingLogicalId = stackResources.find( + (resource) => resource.LogicalResourceId === groups.LogicalResourceId, + ); + if (stackResourceWithMatchingLogicalId) { + const fnGetAttRegExpPerLogicalId = new RegExp(`{"${GET_ATT}":\\["${groups.LogicalResourceId}","(?\\w+)"]}`, 'g'); + const stackResourcePhysicalId = stackResourceWithMatchingLogicalId.PhysicalResourceId; + assert(stackResourcePhysicalId); + if (groups.AttributeName === 'Arn') { + // Few resources like SQS have their physical ids as their HTTP URLs. We need to construct the arn manually in such cases. + const resourceId = stackResourcePhysicalId.startsWith('http') ? stackResourcePhysicalId.split('/')[2] : stackResourcePhysicalId; + const resourceArn = this.getResourceAttribute( + groups.AttributeName, + stackResourceWithMatchingLogicalId.ResourceType as CFN_RESOURCE_TYPES, + resourceId, + ); + if (resourceArn) { + stackTemplateResourcesString = stackTemplateResourcesString.replaceAll(fnGetAttRegExpPerLogicalId, `"${resourceArn.Arn}"`); + } else { + stackTemplateResourcesString = stackTemplateResourcesString.replaceAll( + fnGetAttRegExpPerLogicalId, + `"${stackResourcePhysicalId}"`, + ); + } + } else { + stackTemplateResourcesString = stackTemplateResourcesString.replaceAll( + fnGetAttRegExpPerLogicalId, + `"${stackResourcePhysicalId}"`, + ); + } + } + } + } + return stackTemplateResourcesString; + } + + /** + * Get resource attribute based on attribute name, resource type and resource identifier. + * Only Arn is supported for now since that is what is used in gen1 and gen2 stacks for Auth and Storage categories. + * @param attributeName + * @param resourceType + * @param resourceIdentifier + * @private + */ + private getResourceAttribute( + attributeName: AWS_RESOURCE_ATTRIBUTES, + resourceType: CFN_RESOURCE_TYPES, + resourceIdentifier: string, + ): Record | undefined { + switch (attributeName) { + case 'Arn': { + switch (resourceType) { + case 'AWS::S3::Bucket': + return { + Arn: `arn:aws:s3:::${resourceIdentifier}`, + }; + case 'AWS::Cognito::UserPool': + return { + Arn: `arn:aws:cognito-idp:${this.region}:${this.accountId}:userpool/${resourceIdentifier}`, + }; + case 'AWS::IAM::Role': + return { + // output is already in ARN format + Arn: resourceIdentifier.startsWith('arn:aws:iam') + ? resourceIdentifier + : `arn:aws:iam::${this.accountId}:role/${resourceIdentifier}`, + }; + case 'AWS::SQS::Queue': + return { + Arn: `arn:aws:sqs:${this.region}:${this.accountId}:${resourceIdentifier}`, + }; + case 'AWS::Lambda::Function': + return { + Arn: `arn:aws:lambda:${this.region}:${this.accountId}:function:${resourceIdentifier}`, + }; + default: + return undefined; + } + } + default: + return undefined; + } + } + + /** + * Build a custom replace function to replace Fn::GetAtt references with resource attribute values. + * @param record + * @private + */ + private buildFnGetAttReplace(record: Record) { + return (_match: string, _p1: string, _offset: number, _text: string, groups: Record) => + `"${record[groups.AttributeName]}"`; + } +} + +export default CfnOutputResolver; diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-parameter-resolver.test.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-parameter-resolver.test.ts new file mode 100644 index 00000000000..0b8f44d9244 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-parameter-resolver.test.ts @@ -0,0 +1,115 @@ +import CfnParameterResolver from './cfn-parameter-resolver'; +import { CFNTemplate } from '../types'; + +describe('CFNParameterResolver', () => { + const template: CFNTemplate = { + Description: 'This is a test template', + AWSTemplateFormatVersion: '2010-09-09', + Parameters: { + Env: { + Type: 'String', + Default: 'dev', + }, + // comma delimited parameter + CommaDelimited: { + Type: 'CommaDelimitedList', + Default: 'a,b,c', + }, + // List parameter + NumberList: { + Type: 'List', + Default: '1,2,3', + }, + // NoEcho parameter + NoEcho: { + Type: 'String', + Default: 'no-echo', + NoEcho: true, + }, + }, + Resources: { + MyBucket: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: 'my-bucket', + // use parameters + Tags: [ + { Key: 'env', Value: { Ref: 'Env' } }, + { Key: 'comma', Value: { Ref: 'CommaDelimited' } }, + { Key: 'number', Value: { Ref: 'NumberList' } }, + { Key: 'no-echo', Value: { Ref: 'NoEcho' } }, + ], + }, + }, + }, + Outputs: { + MyBucketName: { + Description: 'My bucket name', + Value: { + Ref: 'MyBucket', + }, + }, + }, + }; + + const expectedTemplate: CFNTemplate = { + Description: 'This is a test template', + AWSTemplateFormatVersion: '2010-09-09', + Parameters: { + Env: { + Type: 'String', + Default: 'dev', + }, + CommaDelimited: { + Type: 'CommaDelimitedList', + Default: 'a,b,c', + }, + NumberList: { + Type: 'List', + Default: '1,2,3', + }, + NoEcho: { + Type: 'String', + Default: 'no-echo', + NoEcho: true, + }, + }, + Resources: { + MyBucket: { + Type: 'AWS::S3::Bucket', + Properties: { + BucketName: 'my-bucket', + Tags: [ + { Key: 'env', Value: 'prod' }, + { Key: 'comma', Value: ['a', 'b', 'c', 'd'] }, + { Key: 'number', Value: ['1', '2', '3', '4'] }, + { Key: 'no-echo', Value: { Ref: 'NoEcho' } }, + ], + }, + }, + }, + Outputs: { + MyBucketName: { + Description: 'My bucket name', + Value: { + Ref: 'MyBucket', + }, + }, + }, + }; + + it('should resolve parameters in template', () => { + const resolvedTemplate = new CfnParameterResolver(template).resolve([ + { ParameterKey: 'Env', ParameterValue: 'prod' }, + { ParameterKey: 'CommaDelimited', ParameterValue: 'a,b,c,d' }, + { ParameterKey: 'NumberList', ParameterValue: '1,2,3,4' }, + { ParameterKey: 'NoEcho', ParameterValue: 'new-no-echo' }, + ]); + expect(resolvedTemplate).toEqual(expectedTemplate); + }); + + it('should not resolve when no parameters are present', () => { + const resolvedTemplate = new CfnParameterResolver(template).resolve([]); + expect(resolvedTemplate).toEqual(template); + }); +}); diff --git a/packages/amplify-migration-template-gen/src/resolvers/cfn-parameter-resolver.ts b/packages/amplify-migration-template-gen/src/resolvers/cfn-parameter-resolver.ts new file mode 100644 index 00000000000..dc0c8f6a344 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/resolvers/cfn-parameter-resolver.ts @@ -0,0 +1,45 @@ +import { CFN_PSEUDO_PARAMETERS_REF, CFNTemplate, CFNParameter } from '../types'; +import { Parameter } from '@aws-sdk/client-cloudformation'; +import assert from 'node:assert'; + +class CfnParameterResolver { + constructor(private readonly template: CFNTemplate, private readonly stackName: string | undefined = undefined) {} + + public resolve(parameters: Parameter[]) { + if (!parameters.length) return this.template; + const clonedParameters = JSON.parse(JSON.stringify(parameters)) as Parameter[]; + const clonedGen1Template = JSON.parse(JSON.stringify(this.template)) as CFNTemplate; + let templateString = JSON.stringify(clonedGen1Template); + const parametersFromTemplate = this.template.Parameters; + const clonedParametersFromTemplate = JSON.parse(JSON.stringify(parametersFromTemplate)) as Record; + // This is required for Gen1 bucket name as it relies on Gen1 stack name, and we need to resolve + // it before moving to Gen2 stack. + if (this.stackName) { + clonedParametersFromTemplate[CFN_PSEUDO_PARAMETERS_REF.StackName] = { + Type: 'String', + }; + clonedParameters.push({ + ParameterKey: CFN_PSEUDO_PARAMETERS_REF.StackName, + ParameterValue: this.stackName, + }); + } + for (const { ParameterKey, ParameterValue } of clonedParameters) { + assert(ParameterKey); + if (!ParameterValue) continue; + const { Type: parameterType, NoEcho } = clonedParametersFromTemplate[ParameterKey]; + if (NoEcho) continue; + // All parameter values referenced by Ref are coerced to strings. List/Comma delimited are converted to arrays before coercing to string. + // Ref: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html + let resolvedParameterValue: string = JSON.stringify(ParameterValue); + const isListValue = parameterType === 'CommaDelimitedList' || parameterType === 'List'; + if (isListValue) { + resolvedParameterValue = JSON.stringify(ParameterValue.includes(',') ? ParameterValue.split(',') : [ParameterValue]); + } + const paramRegexp = new RegExp(`{"Ref":"${ParameterKey}"}`, 'g'); + templateString = templateString.replaceAll(paramRegexp, resolvedParameterValue); + } + return JSON.parse(templateString); + } +} + +export default CfnParameterResolver; diff --git a/packages/amplify-migration-template-gen/src/setup-jest.ts b/packages/amplify-migration-template-gen/src/setup-jest.ts new file mode 100644 index 00000000000..01bf7df61fd --- /dev/null +++ b/packages/amplify-migration-template-gen/src/setup-jest.ts @@ -0,0 +1,6 @@ +import { expect } from '@jest/globals'; +import { toBeACloudFormationCommand } from './custom-test-matchers'; + +expect.extend({ + toBeACloudFormationCommand, +}); diff --git a/packages/amplify-migration-template-gen/src/template-generator.test.ts b/packages/amplify-migration-template-gen/src/template-generator.test.ts new file mode 100644 index 00000000000..242f75724f1 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/template-generator.test.ts @@ -0,0 +1,1203 @@ +import { TemplateGenerator } from './template-generator'; +import CategoryTemplateGenerator from './category-template-generator'; +import { + CloudFormationClient, + CreateStackRefactorCommand, + DescribeStackRefactorCommand, + DescribeStackResourcesCommand, + DescribeStackResourcesOutput, + DescribeStacksCommand, + DescribeStacksCommandOutput, + ExecuteStackRefactorCommand, + StackRefactorExecutionStatus, + StackRefactorStatus, + StackStatus, + UpdateStackCommand, +} from '@aws-sdk/client-cloudformation'; +import fs from 'node:fs/promises'; +import { SSMClient } from '@aws-sdk/client-ssm'; +import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; +import { CATEGORY, CFN_AUTH_TYPE, CFN_S3_TYPE, CFN_IAM_TYPE, CFNTemplate } from './types'; +import assert from 'node:assert'; + +jest.useFakeTimers(); + +const mockCfnClientSendMock = jest.fn(); +const mockGenerateGen1PreProcessTemplate = jest.fn(); +const mockGenerateGen2ResourceRemovalTemplate = jest.fn(); +const mockGenerateStackRefactorTemplates = jest.fn(); +const mockGenerateRefactorTemplates = jest.fn(); +const mockReadTemplate = jest.fn(); +const mockDescribeStack = jest.fn(); +const mockReadMeInitialize = jest.fn(); +const mockReadMeRenderStep1 = jest.fn(); +const REGION = 'us-east-1'; +const getStackId = (stackName: string, category: CATEGORY) => { + // In Gen1, user pool group and auth are their own stacks. In Gen2, they are combined into 1. + const resolvedCategory = stackName === GEN2_ROOT_STACK_NAME && category === 'auth-user-pool-group' ? 'auth' : category; + return `arn:aws:cloudformation:${REGION}:${ACCOUNT_ID}:stack/${stackName}-${resolvedCategory}/12345`; +}; + +const NUM_CATEGORIES_TO_REFACTOR = 3; +const ACCOUNT_ID = 'TEST_ACCOUNT_ID'; +const GEN1_ROOT_STACK_NAME = 'amplify-gen1-dev-12345'; +const GEN2_ROOT_STACK_NAME = 'amplify-gen2-test-sandbox-12345'; +const GEN1_AUTH_STACK_ID = getStackId(GEN1_ROOT_STACK_NAME, 'auth'); +const GEN1_AUTH_USER_POOL_GROUP_STACK_ID = getStackId(GEN1_ROOT_STACK_NAME, 'auth-user-pool-group'); +const GEN2_AUTH_STACK_ID = getStackId(GEN2_ROOT_STACK_NAME, 'auth'); +const GEN1_STORAGE_STACK_ID = getStackId(GEN1_ROOT_STACK_NAME, 'storage'); +const GEN2_STORAGE_STACK_ID = getStackId(GEN2_ROOT_STACK_NAME, 'storage'); +const GEN1_S3_BUCKET_LOGICAL_ID = 'S3Bucket'; +const GEN2_S3_BUCKET_LOGICAL_ID = 'Gen2S3Bucket'; +const STUB_CFN_CLIENT = new CloudFormationClient(); +const STUB_SSM_CLIENT = new SSMClient(); +const STUB_COGNITO_IDP_CLIENT = new CognitoIdentityProviderClient(); +const APP_ID = 'd123456'; +const ENV_NAME = 'test'; +const CDK_IDENTIFIER = '12345678'; +const GEN2_AUTH_LOGICAL_ID_PREFIX = 'amplifyAuth'; +const GEN2_AUTH_USER_POOL_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}Gen2UserPool${CDK_IDENTIFIER}`; +const GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}Gen2IdentityPool${CDK_IDENTIFIER}`; +const GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}UserPoolAppClient${CDK_IDENTIFIER}`; +const GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}UserPoolNativeAppClient${CDK_IDENTIFIER}`; +const GEN2_IDENTITY_POOL_ROLE_ATTACHMENT_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}IdentityPoolRoleAttachment${CDK_IDENTIFIER}`; +const GEN2_USER_POOL_GROUP_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}MyUserPoolGroup${CDK_IDENTIFIER}`; +const GEN2_USER_POOL_GROUP_NAME = 'MyUserPool'; +const GEN2_USER_POOL_GROUP_ROLE_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}myUserPoolGroupRole${CDK_IDENTIFIER}`; +const GEN2_AUTH_ROLE_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}unauthenticatedUserRole${CDK_IDENTIFIER}`; +const GEN2_UNAUTH_ROLE_LOGICAL_ID = `${GEN2_AUTH_LOGICAL_ID_PREFIX}authenticatedUserRole${CDK_IDENTIFIER}`; +const STACK_CATEGORIES_TO_REFACTOR: CATEGORY[] = ['auth', 'auth-user-pool-group', 'storage']; +export const GEN1_USER_POOL_GROUPS_STACK_TYPE_DESCRIPTION = 'auth-Cognito-UserPool-Groups'; +export const GEN1_AUTH_STACK_TYPE_DESCRIPTION = 'auth-Cognito'; +const USER_POOL_PARAM_NAME = 'authUserPoolId'; + +const mockDescribeGen1StackResources: DescribeStackResourcesOutput = { + StackResources: [ + { + ResourceType: 'AWS::CloudFormation::Stack', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: 'auth', + PhysicalResourceId: GEN1_AUTH_STACK_ID, + Timestamp: new Date(), + }, + { + ResourceType: 'AWS::CloudFormation::Stack', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: 'authUserPoolGroup', + PhysicalResourceId: GEN1_AUTH_USER_POOL_GROUP_STACK_ID, + Timestamp: new Date(), + }, + { + ResourceType: 'AWS::CloudFormation::Stack', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: 'storage', + PhysicalResourceId: GEN1_STORAGE_STACK_ID, + Timestamp: new Date(), + }, + { + ResourceType: 'AWS::S3::Bucket', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN1_S3_BUCKET_LOGICAL_ID, + PhysicalResourceId: 'my-s3-bucket-gen1', + Timestamp: new Date(), + }, + { + ResourceType: 'AWS::Cognito::UserPoolClient', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: 'UserPoolClient', + PhysicalResourceId: 'user-pool-client-id', + Timestamp: new Date(), + }, + ], +}; + +const mockDescribeGen2StackResources: DescribeStackResourcesOutput = { + StackResources: [ + { + ResourceType: 'AWS::CloudFormation::Stack', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: 'auth', + PhysicalResourceId: getStackId(GEN2_ROOT_STACK_NAME, 'auth'), + Timestamp: new Date(), + }, + { + ResourceType: 'AWS::CloudFormation::Stack', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: 'storage', + PhysicalResourceId: getStackId(GEN2_ROOT_STACK_NAME, 'storage'), + Timestamp: new Date(), + }, + { + ResourceType: 'AWS::S3::Bucket', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN2_S3_BUCKET_LOGICAL_ID, + PhysicalResourceId: 'my-s3-bucket-gen2', + Timestamp: new Date(), + }, + ], +}; + +const mockDescribeGen2AuthStackResources: DescribeStackResourcesOutput = { + StackResources: [ + { + ResourceType: CFN_IAM_TYPE.Role, + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN2_AUTH_ROLE_LOGICAL_ID, + PhysicalResourceId: `authRole`, + Timestamp: new Date(), + }, + { + ResourceType: CFN_IAM_TYPE.Role, + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN2_UNAUTH_ROLE_LOGICAL_ID, + PhysicalResourceId: 'unAuthRole', + Timestamp: new Date(), + }, + { + ResourceType: CFN_IAM_TYPE.Role, + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN2_USER_POOL_GROUP_ROLE_LOGICAL_ID, + PhysicalResourceId: 'myGroupRole', + Timestamp: new Date(), + }, + ], +}; + +const mockDescribeGen2StorageStackResources: DescribeStackResourcesOutput = { + StackResources: [ + { + ResourceType: CFN_S3_TYPE.Bucket, + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN2_S3_BUCKET_LOGICAL_ID, + PhysicalResourceId: `myGen1BucketAfterRefactor`, + Timestamp: new Date(), + }, + ], +}; + +const mockDescribeGen1AuthStackResources: DescribeStackResourcesOutput = { + StackResources: [ + { + ResourceType: CFN_AUTH_TYPE.UserPool, + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: `UserPool`, + PhysicalResourceId: `userPoolId`, + Timestamp: new Date(), + }, + ], +}; + +const mockDescribeGen1AuthUserPoolGroupStackResources: DescribeStackResourcesOutput = { + StackResources: [ + { + ResourceType: CFN_AUTH_TYPE.UserPoolGroup, + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN2_USER_POOL_GROUP_LOGICAL_ID, + PhysicalResourceId: GEN2_USER_POOL_GROUP_NAME, + Timestamp: new Date(), + }, + ], +}; + +const mockDescribeGen2StackResourcesWithStorageMissing: DescribeStackResourcesOutput = { + StackResources: [ + { + ResourceType: 'AWS::CloudFormation::Stack', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: 'auth', + PhysicalResourceId: `arn:aws:cloudformation:${REGION}:${ACCOUNT_ID}:stack/${GEN2_ROOT_STACK_NAME}-auth/12345`, + Timestamp: new Date(), + }, + { + ResourceType: 'AWS::S3::Bucket', + ResourceStatus: 'CREATE_COMPLETE', + LogicalResourceId: GEN2_S3_BUCKET_LOGICAL_ID, + PhysicalResourceId: 'my-s3-bucket-gen2', + Timestamp: new Date(), + }, + ], +}; + +jest.mock('@aws-sdk/client-cloudformation', () => { + return { + ...jest.requireActual('@aws-sdk/client-cloudformation'), + CloudFormationClient: function () { + return { + config: { + region: () => REGION, + }, + send: mockCfnClientSendMock, + }; + }, + }; +}); + +jest.mock('node:fs/promises'); +jest.mock('./migration-readme-generator', () => { + return function () { + return { + initialize: mockReadMeInitialize, + renderStep1: mockReadMeRenderStep1, + }; + }; +}); +const stubReadTemplate: CFNTemplate = { + AWSTemplateFormatVersion: 'AWSTemplateFormatVersion', + Description: 'Gen2 template', + Parameters: { + [USER_POOL_PARAM_NAME]: { + Type: 'String', + Description: 'Cognito User Pool ID', + }, + }, + Resources: { + [GEN2_AUTH_USER_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPool, + Properties: { + UserPoolName: { 'Fn::Join': ['-', 'my-user-pool', 'dev'] }, + UserPoolId: { Ref: 'authUserPoolId' }, + }, + }, + [GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.IdentityPool, + Properties: { + IdentityPoolName: { 'Fn::Join': ['-', 'my-identity-pool', 'dev'] }, + }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'WebClient', + }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolClient, + Properties: { + ClientName: 'NativeClient', + }, + }, + [GEN2_IDENTITY_POOL_ROLE_ATTACHMENT_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.IdentityPoolRoleAttachment, + Properties: { + IdentityPoolId: { Ref: GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID }, + Roles: { + authenticated: { 'Fn::GetAtt': [GEN2_AUTH_ROLE_LOGICAL_ID, 'Arn'] }, + unauthenticated: { 'Fn::GetAtt': [GEN2_UNAUTH_ROLE_LOGICAL_ID, 'Arn'] }, + }, + }, + }, + [GEN2_USER_POOL_GROUP_LOGICAL_ID]: { + Type: CFN_AUTH_TYPE.UserPoolGroup, + Properties: { + GroupName: GEN2_USER_POOL_GROUP_NAME, + RoleArn: { + 'Fn::GetAtt': [GEN2_USER_POOL_GROUP_ROLE_LOGICAL_ID, 'Arn'], + }, + }, + }, + [GEN2_S3_BUCKET_LOGICAL_ID]: { + Properties: { + BucketName: 'S3BucketName', + }, + Type: CFN_S3_TYPE.Bucket, + }, + }, + Outputs: { + [GEN2_AUTH_USER_POOL_LOGICAL_ID]: { + Value: { Ref: GEN2_AUTH_USER_POOL_LOGICAL_ID }, + }, + [GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID]: { + Value: { Ref: GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID]: { + Value: { Ref: GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID }, + }, + [GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID]: { + Value: { Ref: GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID }, + }, + }, +}; +const stubCategoryTemplateGenerator = { + generateGen1PreProcessTemplate: mockGenerateGen1PreProcessTemplate.mockReturnValue({ + oldTemplate: {}, + newTemplate: {}, + parameters: [], + }), + generateGen2ResourceRemovalTemplate: mockGenerateGen2ResourceRemovalTemplate.mockReturnValue({ + oldTemplate: {}, + newTemplate: {}, + parameters: [], + }), + generateStackRefactorTemplates: mockGenerateStackRefactorTemplates.mockReturnValue({ + sourceTemplate: {}, + destinationTemplate: {}, + logicalIdMapping: new Map([['ResourceA', 'ResourceB']]), + }), + generateRefactorTemplates: mockGenerateRefactorTemplates.mockReturnValue({ + sourceTemplate: {}, + destinationTemplate: {}, + logicalIdMapping: new Map([['ResourceA', 'ResourceB']]), + }), + readTemplate: mockReadTemplate.mockReturnValue(stubReadTemplate), + describeStack: mockDescribeStack.mockReturnValue({ + Outputs: [ + { + OutputKey: GEN2_AUTH_USER_POOL_LOGICAL_ID, + OutputValue: 'user-pool-id', + }, + { + OutputKey: GEN2_AUTH_IDENTITY_POOL_LOGICAL_ID, + OutputValue: 'identity-pool-id', + }, + { + OutputKey: GEN2_AUTH_USER_POOL_CLIENT_WEB_LOGICAL_ID, + OutputValue: 'web-client-id', + }, + { + OutputKey: GEN2_AUTH_USER_POOL_CLIENT_NATIVE_LOGICAL_ID, + OutputValue: 'native-client-id', + }, + ], + Parameters: [ + { + ParameterKey: USER_POOL_PARAM_NAME, + ParameterValue: 'user-pool-id', + }, + ], + }), +}; +jest.mock('./category-template-generator', () => { + return jest.fn().mockImplementation(() => { + return stubCategoryTemplateGenerator; + }); +}); + +const describeStackResourcesResponse = (stackName: string | undefined) => { + assert(stackName); + switch (stackName) { + case GEN1_ROOT_STACK_NAME: + return Promise.resolve(mockDescribeGen1StackResources); + case GEN2_ROOT_STACK_NAME: + return Promise.resolve(mockDescribeGen2StackResources); + case GEN1_AUTH_STACK_ID: + return Promise.resolve(mockDescribeGen1AuthStackResources); + case GEN2_AUTH_STACK_ID: + return Promise.resolve(mockDescribeGen2AuthStackResources); + case GEN1_AUTH_USER_POOL_GROUP_STACK_ID: + return Promise.resolve(mockDescribeGen1AuthUserPoolGroupStackResources); + case GEN2_STORAGE_STACK_ID: + return Promise.resolve(mockDescribeGen2StorageStackResources); + default: + throw new Error(`Unexpected stack: ${stackName}`); + } +}; + +const describeStacksResponse = (stackName: string | undefined, stackStatus: StackStatus = 'UPDATE_COMPLETE') => { + assert(stackName); + const defaultResponse: DescribeStacksCommandOutput = { + Stacks: [ + { + StackStatus: stackStatus, + StackName: stackName, + CreationTime: new Date(), + }, + ], + $metadata: {}, + }; + assert(defaultResponse.Stacks?.[0]); + switch (stackName) { + case GEN1_AUTH_STACK_ID: { + defaultResponse.Stacks[0].Description = JSON.stringify({ + stackType: GEN1_AUTH_STACK_TYPE_DESCRIPTION, + }); + return Promise.resolve(defaultResponse); + } + case GEN1_AUTH_USER_POOL_GROUP_STACK_ID: + defaultResponse.Stacks[0].Description = JSON.stringify({ + stackType: GEN1_USER_POOL_GROUPS_STACK_TYPE_DESCRIPTION, + }); + return Promise.resolve(defaultResponse); + default: + return Promise.resolve(defaultResponse); + } +}; + +describe('TemplateGenerator', () => { + beforeEach(() => { + mockCfnClientSendMock.mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return describeStackResourcesResponse(command.input.StackName); + } + if (command instanceof UpdateStackCommand) { + return Promise.resolve({}); + } + if (command instanceof DescribeStacksCommand) { + return describeStacksResponse(command.input.StackName); + } + if (command instanceof CreateStackRefactorCommand) { + return Promise.resolve({ + StackRefactorId: '12345', + }); + } + if (command instanceof DescribeStackRefactorCommand) { + return Promise.resolve({ + Status: StackRefactorStatus.CREATE_COMPLETE, + ExecutionStatus: StackRefactorExecutionStatus.EXECUTE_COMPLETE, + }); + } + return Promise.resolve({}); + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should refactor resources from Gen1 to Gen2 successfully', async () => { + // Act + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.generate(); + + // Assert + successfulTemplateGenerationAssertions(); + assertCFNCalls(); + }); + + it('should refactor resources from Gen1 to Gen2 successfully, skipping categories that have already been refactored previously', async () => { + mockGenerateGen1PreProcessTemplate.mockImplementationOnce(() => { + throw new Error('No resources to move in Gen1 stack'); + }); + // Act + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.generate(); + + // Assert + successfulTemplateGenerationAssertions(1); + assertCFNCalls(false, ['auth']); + }); + + it('should refactor custom resources from Gen1 to Gen2 successfully', async () => { + // Arrange + const customResourceMap = [ + { + Source: { LogicalResourceId: 'CustomResource1', StackName: GEN1_ROOT_STACK_NAME }, + Destination: { LogicalResourceId: 'CustomResource1', StackName: GEN2_ROOT_STACK_NAME }, + }, + ]; + // Act + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.generate(customResourceMap); + + // Assert + successfulCustomResourcesAssertions(); + assertCFNCalls(); + }); + + it('should fail to generate when no applicable categories are found', async () => { + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + const failureSendMock = (command: any) => { + if (command instanceof DescribeStackResourcesCommand) { + return Promise.resolve( + command.input.StackName === GEN1_ROOT_STACK_NAME + ? mockDescribeGen1StackResources + : mockDescribeGen2StackResourcesWithStorageMissing, + ); + } + return Promise.resolve({}); + }; + mockCfnClientSendMock.mockImplementationOnce(failureSendMock).mockImplementationOnce(failureSendMock); + await expect(() => generator.generate()).rejects.toEqual( + new Error('No corresponding category found in destination stack for storage category'), + ); + }); + + it('should throw exception when update stack fails', async () => { + // Arrange + const errorMessage = 'Malformed template'; + mockCfnClientSendMock.mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return describeStackResourcesResponse(command.input.StackName); + } else if (command instanceof DescribeStacksCommand) { + return describeStacksResponse(command.input.StackName); + } else if (command instanceof UpdateStackCommand) { + throw new Error(errorMessage); + } + return Promise.resolve({}); + }); + + // Act + Assert + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await expect(generator.generate()).rejects.toThrow(errorMessage); + }); + + it('should skip update if already updated', async () => { + // Arrange + mockCfnClientSendMock.mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return describeStackResourcesResponse(command.input.StackName); + } else if (command instanceof UpdateStackCommand) { + throw new Error('No updates are to be performed'); + } else if (command instanceof DescribeStacksCommand) { + return describeStacksResponse(command.input.StackName); + } else if (command instanceof CreateStackRefactorCommand) { + return Promise.resolve({ + StackRefactorId: '12345', + }); + } else if (command instanceof DescribeStackRefactorCommand) { + return Promise.resolve({ + Status: StackRefactorStatus.CREATE_COMPLETE, + ExecutionStatus: StackRefactorExecutionStatus.EXECUTE_COMPLETE, + }); + } + return Promise.resolve({}); + }); + + // Act + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.generate(); + + // Assert + successfulTemplateGenerationAssertions(); + assertCFNCalls(true); + }); + + it('should fail after all poll attempts have exhausted during update stack', async () => { + // Arrange + mockCfnClientSendMock.mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return describeStackResourcesResponse(command.input.StackName); + } else if (command instanceof UpdateStackCommand) { + return Promise.resolve({}); + } else if (command instanceof DescribeStacksCommand) { + return describeStacksResponse(command.input.StackName, 'UPDATE_IN_PROGRESS'); + } + return Promise.resolve({}); + }); + + // Act + Assert + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + expect.assertions(1); + // Intentionally not awaiting the below call to be able to advance timers and micro task queue in waitForPromisesAndFakeTimers + // and catch the error below + generator.generate().catch((e) => { + expect(e.message).toBe( + `Stack ${getStackId(GEN1_ROOT_STACK_NAME, 'auth')} did not reach a completion state within the given time period.`, + ); + }); + await waitForPromisesAndFakeTimers(); + return; + }); + + it('should rollback gen2 stack when create stack refactor fails', async () => { + mockCfnClientSendMock.mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return describeStackResourcesResponse(command.input.StackName); + } else if (command instanceof UpdateStackCommand) { + return Promise.resolve({}); + } else if (command instanceof DescribeStacksCommand) { + return describeStacksResponse(command.input.StackName); + } else if (command instanceof CreateStackRefactorCommand) { + return Promise.resolve({ + StackRefactorId: '12345', + }); + } else if (command instanceof DescribeStackRefactorCommand) { + return Promise.resolve({ + Status: StackRefactorStatus.CREATE_FAILED, + StatusReason: 'Update operations not permitted in refactor', + }); + } + return Promise.resolve({}); + }); + + // Act + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.generate(); + const numCFNOperationsBeforeGen2StackUpdate = 5; + assertRollbackRefactor('auth', numCFNOperationsBeforeGen2StackUpdate + 1, true); + expect(mockReadMeRenderStep1).not.toHaveBeenCalled(); + }); + + it('should rollback gen2 stack when execute stack refactor fails', async () => { + mockCfnClientSendMock.mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return describeStackResourcesResponse(command.input.StackName); + } else if (command instanceof UpdateStackCommand) { + return Promise.resolve({}); + } else if (command instanceof DescribeStacksCommand) { + return describeStacksResponse(command.input.StackName); + } else if (command instanceof CreateStackRefactorCommand) { + return Promise.resolve({ + StackRefactorId: '12345', + }); + } else if (command instanceof DescribeStackRefactorCommand) { + return Promise.resolve({ + Status: StackRefactorStatus.CREATE_COMPLETE, + ExecutionStatus: StackRefactorExecutionStatus.EXECUTE_FAILED, + StatusReason: 'Update operations not permitted in refactor', + }); + } + return Promise.resolve({}); + }); + + // Act + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.generate(); + const numCFNOperationsBeforeGen2StackUpdate = 5; + assertRollbackRefactor('auth', numCFNOperationsBeforeGen2StackUpdate + 1, false, true); + expect(mockReadMeRenderStep1).not.toHaveBeenCalled(); + }); + + it('should fail after all poll attempts have exhausted during create stack refactor', async () => { + // Arrange + mockCfnClientSendMock.mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return describeStackResourcesResponse(command.input.StackName); + } else if (command instanceof UpdateStackCommand) { + return Promise.resolve({}); + } else if (command instanceof DescribeStacksCommand) { + return describeStacksResponse(command.input.StackName); + } else if (command instanceof CreateStackRefactorCommand) { + return Promise.resolve({ + StackRefactorId: '12345', + }); + } else if (command instanceof DescribeStackRefactorCommand) { + return Promise.resolve({ + Status: StackRefactorStatus.CREATE_IN_PROGRESS, + }); + } + return Promise.resolve({}); + }); + + // Act + Assert + const generator = new TemplateGenerator( + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + expect.assertions(2); + // Intentionally not awaiting the below call to be able to advance timers and micro task queue in waitForPromisesAndFakeTimers + // and catch the error below + generator.generate().catch((e) => { + expect(e.message).toBe(`Stack refactor 12345 did not reach a completion state within the given time period.`); + expect(mockReadMeRenderStep1).not.toHaveBeenCalled(); + }); + await waitForPromisesAndFakeTimers(); + return; + }); + + it('should revert resources from Gen2 to Gen1 successfully', async () => { + // Act + const generator = new TemplateGenerator( + GEN2_ROOT_STACK_NAME, + GEN1_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.revert(); + + // Assert + successfulRevertAssertions(); + // 2 describe stack resources call for each root stack (Gen1, Gen2) + // 2 describe stacks call for Gen 1 auth related stacks (auth, user pool groups) + // 1 describe stack resources call for Gen2 auth stack to get physical ids for auth roles + let callIndex = assertStackRefactorCommands('auth', 5, false, false, true); + // 1 describe stack resources call for Gen2 auth stack to get physical ids for user group roles + // 1 describe stack resources call for Gen2 storage stack to get physical ids for user group roles + callIndex = assertStackRefactorCommands('auth-user-pool-group', callIndex + 2, false, false, true); + assertStackRefactorCommands('storage', callIndex + 2, false, false, true); + }); + + it('should revert resources from Gen2 to Gen1 successfully, skipping categories that have already been updated previously', async () => { + const clonedStubGetTemplate = JSON.parse(JSON.stringify(stubReadTemplate)); + delete clonedStubGetTemplate.Resources[GEN2_S3_BUCKET_LOGICAL_ID]; + mockReadTemplate.mockReturnValue(clonedStubGetTemplate); + // Act + const generator = new TemplateGenerator( + GEN2_ROOT_STACK_NAME, + GEN1_ROOT_STACK_NAME, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + ); + await generator.revert(); + + // Assert + successfulRevertAssertions(1); + // 2 describe stack resources call for each root stack (Gen1, Gen2) + // 2 describe stacks call for Gen 1 auth related stacks (auth, user pool groups) + // 1 describe stack resources call for Gen2 auth stack to get physical ids for auth roles + const callIndex = assertStackRefactorCommands('auth', 5, false, false, true); + assertStackRefactorCommands('auth-user-pool-group', callIndex + 2, false, false, true); + }); + + function successfulTemplateGenerationAssertions(numCategoriesToSkipUpdate = 0) { + expect(fs.mkdir).toBeCalledTimes(1); + expect(mockGenerateGen1PreProcessTemplate).toBeCalledTimes(NUM_CATEGORIES_TO_REFACTOR); + expect(mockGenerateGen2ResourceRemovalTemplate).toBeCalledTimes(NUM_CATEGORIES_TO_REFACTOR - numCategoriesToSkipUpdate); + expect(mockGenerateStackRefactorTemplates).toBeCalledTimes(NUM_CATEGORIES_TO_REFACTOR - numCategoriesToSkipUpdate); + expect(mockReadMeInitialize).toBeCalledTimes(1); + expect(mockReadMeRenderStep1).toBeCalledTimes(1); + expect(CategoryTemplateGenerator).toBeCalledTimes(3); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 1, + GEN1_AUTH_STACK_ID, + GEN2_AUTH_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [ + CFN_AUTH_TYPE.UserPool, + CFN_AUTH_TYPE.UserPoolClient, + CFN_AUTH_TYPE.IdentityPool, + CFN_AUTH_TYPE.IdentityPoolRoleAttachment, + CFN_AUTH_TYPE.UserPoolDomain, + ], + undefined, + ); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 2, + GEN1_AUTH_USER_POOL_GROUP_STACK_ID, + GEN2_AUTH_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [CFN_AUTH_TYPE.UserPoolGroup], + undefined, + ); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 3, + GEN1_STORAGE_STACK_ID, + GEN2_STORAGE_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [CFN_S3_TYPE.Bucket], + undefined, + ); + } + + function successfulRevertAssertions(numCategoriesToSkipUpdate = 0) { + expect(fs.mkdir).not.toBeCalled(); + expect(mockGenerateGen1PreProcessTemplate).not.toBeCalled(); + expect(mockGenerateGen2ResourceRemovalTemplate).not.toBeCalled(); + expect(mockGenerateRefactorTemplates).toBeCalledTimes(NUM_CATEGORIES_TO_REFACTOR - numCategoriesToSkipUpdate); + expect(mockReadMeInitialize).not.toBeCalled(); + expect(mockReadMeRenderStep1).not.toBeCalled(); + expect(CategoryTemplateGenerator).toBeCalledTimes(NUM_CATEGORIES_TO_REFACTOR); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 1, + GEN2_AUTH_STACK_ID, + GEN1_AUTH_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [ + CFN_AUTH_TYPE.UserPool, + CFN_AUTH_TYPE.UserPoolClient, + CFN_AUTH_TYPE.IdentityPool, + CFN_AUTH_TYPE.IdentityPoolRoleAttachment, + CFN_AUTH_TYPE.UserPoolDomain, + ], + undefined, + ); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 2, + GEN2_AUTH_STACK_ID, + GEN1_AUTH_USER_POOL_GROUP_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [CFN_AUTH_TYPE.UserPoolGroup], + undefined, + ); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 3, + GEN2_STORAGE_STACK_ID, + GEN1_STORAGE_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [CFN_S3_TYPE.Bucket], + undefined, + ); + } + + function successfulCustomResourcesAssertions() { + expect(fs.mkdir).toBeCalledTimes(1); + expect(mockGenerateGen1PreProcessTemplate).toBeCalledTimes(4); + expect(mockGenerateGen2ResourceRemovalTemplate).toBeCalledTimes(4); + expect(mockGenerateStackRefactorTemplates).toBeCalledTimes(3); + expect(mockReadMeInitialize).toBeCalledTimes(1); + expect(mockReadMeRenderStep1).toBeCalledTimes(1); + expect(CategoryTemplateGenerator).toBeCalledTimes(4); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 1, + GEN1_AUTH_STACK_ID, + GEN2_AUTH_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [ + CFN_AUTH_TYPE.UserPool, + CFN_AUTH_TYPE.UserPoolClient, + CFN_AUTH_TYPE.IdentityPool, + CFN_AUTH_TYPE.IdentityPoolRoleAttachment, + CFN_AUTH_TYPE.UserPoolDomain, + ], + undefined, + ); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 2, + GEN1_AUTH_USER_POOL_GROUP_STACK_ID, + GEN2_AUTH_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [CFN_AUTH_TYPE.UserPoolGroup], + undefined, + ); + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 3, + GEN1_STORAGE_STACK_ID, + GEN2_STORAGE_STACK_ID, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [CFN_S3_TYPE.Bucket], + undefined, + ); + // custom resource category + expect(CategoryTemplateGenerator).toHaveBeenNthCalledWith( + 4, + GEN1_ROOT_STACK_NAME, + GEN2_ROOT_STACK_NAME, + REGION, + ACCOUNT_ID, + STUB_CFN_CLIENT, + STUB_SSM_CLIENT, + STUB_COGNITO_IDP_CLIENT, + APP_ID, + ENV_NAME, + [], + expect.any(Function), + ); + } + + function assertCFNCalls(skipUpdate = false, categoriesToSkipUpdate: CATEGORY[] = []) { + let callIndex = 0; + expect(mockCfnClientSendMock.mock.calls[callIndex++]).toBeACloudFormationCommand( + { + StackName: GEN1_ROOT_STACK_NAME, + }, + DescribeStackResourcesCommand, + ); + expect(mockCfnClientSendMock.mock.calls[callIndex++]).toBeACloudFormationCommand( + { + StackName: GEN2_ROOT_STACK_NAME, + }, + DescribeStackResourcesCommand, + ); + expect(mockCfnClientSendMock.mock.calls[callIndex++]).toBeACloudFormationCommand( + { + StackName: GEN1_AUTH_STACK_ID, + }, + DescribeStacksCommand, + ); + expect(mockCfnClientSendMock.mock.calls[callIndex++]).toBeACloudFormationCommand( + { + StackName: GEN1_AUTH_USER_POOL_GROUP_STACK_ID, + }, + DescribeStacksCommand, + ); + + let updateStackCallIndex = callIndex; + for (const category of STACK_CATEGORIES_TO_REFACTOR) { + if (categoriesToSkipUpdate.includes(category)) { + continue; + } + updateStackCallIndex = assertUpdateCFNCallsWithCategory(category, updateStackCallIndex, skipUpdate); + updateStackCallIndex++; + } + } + + function assertUpdateCFNCallsWithCategory(category: CATEGORY, updateStackCallIndex: number, skipUpdate: boolean) { + const updateStackCallIndexInterval = skipUpdate ? 1 : 2; + expect(mockCfnClientSendMock.mock.calls[updateStackCallIndex]).toBeACloudFormationCommand( + { + StackName: getStackId(GEN1_ROOT_STACK_NAME, category), + Capabilities: ['CAPABILITY_NAMED_IAM'], + Parameters: [], + TemplateBody: JSON.stringify({}), + Tags: [], + }, + UpdateStackCommand, + ); + if (!skipUpdate) { + expect(mockCfnClientSendMock.mock.calls[updateStackCallIndex + 1]).toBeACloudFormationCommand( + { + StackName: getStackId(GEN1_ROOT_STACK_NAME, category), + }, + DescribeStacksCommand, + ); + } + updateStackCallIndex += updateStackCallIndexInterval; + expect(mockCfnClientSendMock.mock.calls[updateStackCallIndex]).toBeACloudFormationCommand( + { + StackName: getStackId(GEN2_ROOT_STACK_NAME, category), + Capabilities: ['CAPABILITY_NAMED_IAM'], + Parameters: [], + TemplateBody: JSON.stringify({}), + Tags: [], + }, + UpdateStackCommand, + ); + if (!skipUpdate) { + expect(mockCfnClientSendMock.mock.calls[updateStackCallIndex + 1]).toBeACloudFormationCommand( + { + StackName: getStackId(GEN2_ROOT_STACK_NAME, category), + }, + DescribeStacksCommand, + ); + } + return assertStackRefactorCommands(category, updateStackCallIndex + (skipUpdate ? 1 : 2)); + } + + function assertStackRefactorCommands( + category: CATEGORY, + callIndex: number, + onCreateRefactorFailed = false, + onExecuteRefactorFailed = false, + isRevert = false, + ) { + const sourceStackName = isRevert ? getStackId(GEN2_ROOT_STACK_NAME, category) : getStackId(GEN1_ROOT_STACK_NAME, category); + const destinationStackName = isRevert ? getStackId(GEN1_ROOT_STACK_NAME, category) : getStackId(GEN2_ROOT_STACK_NAME, category); + expect(mockCfnClientSendMock.mock.calls[callIndex]).toBeACloudFormationCommand( + { + ResourceMappings: [ + { + Source: { + LogicalResourceId: 'ResourceA', + StackName: sourceStackName, + }, + Destination: { + LogicalResourceId: 'ResourceB', + StackName: destinationStackName, + }, + }, + ], + StackDefinitions: [ + { + TemplateBody: `{}`, + StackName: sourceStackName, + }, + { + TemplateBody: `{}`, + StackName: destinationStackName, + }, + ], + }, + CreateStackRefactorCommand, + ); + expect(mockCfnClientSendMock.mock.calls[++callIndex]).toBeACloudFormationCommand( + { + StackRefactorId: '12345', + }, + DescribeStackRefactorCommand, + ); + if (!onCreateRefactorFailed) { + expect(mockCfnClientSendMock.mock.calls[++callIndex]).toBeACloudFormationCommand( + { + StackRefactorId: '12345', + }, + ExecuteStackRefactorCommand, + ); + expect(mockCfnClientSendMock.mock.calls[++callIndex]).toBeACloudFormationCommand( + { + StackRefactorId: '12345', + }, + DescribeStackRefactorCommand, + ); + if (!onExecuteRefactorFailed) { + expect(mockCfnClientSendMock.mock.calls[++callIndex]).toBeACloudFormationCommand( + { + StackName: sourceStackName, + }, + DescribeStacksCommand, + ); + expect(mockCfnClientSendMock.mock.calls[++callIndex]).toBeACloudFormationCommand( + { + StackName: destinationStackName, + }, + DescribeStacksCommand, + ); + } + } + return callIndex; + } + + function assertRollbackRefactor(category: CATEGORY, callIndex: number, onCreateRefactorFailed = false, onExecuteRefactorFailed = false) { + expect(mockCfnClientSendMock.mock.calls[callIndex]).toBeACloudFormationCommand( + { + StackName: getStackId(GEN2_ROOT_STACK_NAME, category), + Capabilities: ['CAPABILITY_NAMED_IAM'], + Parameters: [], + TemplateBody: JSON.stringify({}), + Tags: [], + }, + UpdateStackCommand, + ); + callIndex = assertStackRefactorCommands(category, callIndex + 2, onCreateRefactorFailed, onExecuteRefactorFailed); + expect(mockCfnClientSendMock.mock.calls[++callIndex]).toBeACloudFormationCommand( + { + StackName: getStackId(GEN2_ROOT_STACK_NAME, category), + }, + DescribeStacksCommand, + ); + expect(mockCfnClientSendMock.mock.calls[++callIndex]).toBeACloudFormationCommand( + { + StackName: getStackId(GEN2_ROOT_STACK_NAME, category), + Capabilities: ['CAPABILITY_NAMED_IAM'], + Parameters: [], + TemplateBody: JSON.stringify({}), + Tags: [], + }, + UpdateStackCommand, + ); + } + + const waitForPromisesAndFakeTimers = async () => { + do { + jest.runAllTimers(); + await new Promise(jest.requireActual('timers').setImmediate); + } while (jest.getTimerCount() > 0); + }; +}); diff --git a/packages/amplify-migration-template-gen/src/template-generator.ts b/packages/amplify-migration-template-gen/src/template-generator.ts new file mode 100644 index 00000000000..5bc28a2726d --- /dev/null +++ b/packages/amplify-migration-template-gen/src/template-generator.ts @@ -0,0 +1,626 @@ +import { CloudFormationClient, DescribeStackResourcesCommand, DescribeStacksCommand, Parameter } from '@aws-sdk/client-cloudformation'; +import assert from 'node:assert'; +import CategoryTemplateGenerator, { HOSTED_PROVIDER_META_PARAMETER_NAME } from './category-template-generator'; +import fs from 'node:fs/promises'; +import { + CATEGORY, + NON_CUSTOM_RESOURCE_CATEGORY, + CFN_AUTH_TYPE, + CFN_CATEGORY_TYPE, + CFN_RESOURCE_TYPES, + CFN_S3_TYPE, + CFNResource, + CFNStackStatus, + CFNTemplate, + ResourceMapping, +} from './types'; +import MigrationReadmeGenerator from './migration-readme-generator'; +import { pollStackForCompletionState, tryUpdateStack } from './cfn-stack-updater'; +import { SSMClient } from '@aws-sdk/client-ssm'; +import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; +import { tryRefactorStack } from './cfn-stack-refactor-updater'; +import CfnOutputResolver from './resolvers/cfn-output-resolver'; +import CfnDependencyResolver from './resolvers/cfn-dependency-resolver'; +import CfnParameterResolver from './resolvers/cfn-parameter-resolver'; +import ora from 'ora'; + +const CFN_RESOURCE_STACK_TYPE = 'AWS::CloudFormation::Stack'; +const GEN2_AMPLIFY_AUTH_LOGICAL_ID_PREFIX = 'amplifyAuth'; + +const CATEGORIES: CATEGORY[] = ['auth', 'storage']; +const TEMPLATES_DIR = '.amplify/migration/templates'; +const SEPARATOR = ' to '; + +const GEN1 = 'Gen 1'; +const GEN2 = 'Gen 2'; +const AUTH_RESOURCES_TO_REFACTOR = [ + CFN_AUTH_TYPE.UserPool, + CFN_AUTH_TYPE.UserPoolClient, + CFN_AUTH_TYPE.IdentityPool, + CFN_AUTH_TYPE.IdentityPoolRoleAttachment, + CFN_AUTH_TYPE.UserPoolDomain, +]; +const AUTH_USER_POOL_GROUP_RESOURCES_TO_REFACTOR = [CFN_AUTH_TYPE.UserPoolGroup]; +const STORAGE_RESOURCES_TO_REFACTOR = [CFN_S3_TYPE.Bucket]; +const GEN1_RESOURCE_TYPE_TO_LOGICAL_RESOURCE_IDS_MAP = new Map([ + [CFN_AUTH_TYPE.UserPool.valueOf(), 'UserPool'], + [CFN_AUTH_TYPE.UserPoolClient.valueOf(), 'UserPoolClientWeb'], + [CFN_AUTH_TYPE.IdentityPool.valueOf(), 'IdentityPool'], + [CFN_AUTH_TYPE.IdentityPoolRoleAttachment.valueOf(), 'IdentityPoolRoleMap'], + [CFN_AUTH_TYPE.UserPoolDomain.valueOf(), 'UserPoolDomain'], + [CFN_S3_TYPE.Bucket.valueOf(), 'S3Bucket'], +]); +const LOGICAL_IDS_TO_REMOVE_FOR_REVERT_MAP = new Map([ + ['auth', AUTH_RESOURCES_TO_REFACTOR], + ['auth-user-pool-group', AUTH_USER_POOL_GROUP_RESOURCES_TO_REFACTOR], + ['storage', [CFN_S3_TYPE.Bucket]], +]); +const GEN2_NATIVE_APP_CLIENT = 'UserPoolNativeAppClient'; +const GEN1_USER_POOL_GROUPS_STACK_TYPE_DESCRIPTION = 'auth-Cognito-UserPool-Groups'; +const GEN1_AUTH_STACK_TYPE_DESCRIPTION = 'auth-Cognito'; +const NO_RESOURCES_TO_MOVE_ERROR = 'No resources to move'; +const NO_RESOURCES_TO_REMOVE_ERROR = 'No resources to remove'; + +class TemplateGenerator { + private readonly categoryStackMap: Map; + private readonly categoryTemplateGenerators: [CATEGORY, string, string, CategoryTemplateGenerator][]; + private region: string | undefined; + private readonly categoryGeneratorConfig = { + auth: { + resourcesToRefactor: AUTH_RESOURCES_TO_REFACTOR, + }, + 'auth-user-pool-group': { + resourcesToRefactor: AUTH_USER_POOL_GROUP_RESOURCES_TO_REFACTOR, + }, + storage: { + resourcesToRefactor: STORAGE_RESOURCES_TO_REFACTOR, + }, + } as const; + + constructor( + private readonly fromStack: string, + private readonly toStack: string, + private readonly accountId: string, + private readonly cfnClient: CloudFormationClient, + private readonly ssmClient: SSMClient, + private readonly cognitoIdpClient: CognitoIdentityProviderClient, + private readonly appId: string, + private readonly environmentName: string, + ) { + this.categoryStackMap = new Map(); + this.categoryTemplateGenerators = []; + } + + private async setRegion() { + this.region = await this.cfnClient.config.region(); + } + + public async generate(customResourceMap?: ResourceMapping[]) { + await fs.mkdir(TEMPLATES_DIR, { recursive: true }); + await this.setRegion(); + await this.parseCategoryStacks(); + if (customResourceMap) { + for (const { Source, Destination } of customResourceMap) { + this.updateCategoryStackMap(Source.LogicalResourceId, Source.StackName, Destination.StackName, false, false); + } + } + return await this.generateCategoryTemplates(false, customResourceMap); + } + + public async revert() { + await this.setRegion(); + await this.parseCategoryStacks(true); + return await this.generateCategoryTemplates(true); + } + + private async parseCategoryStacks(isRevert = false): Promise { + const sourceStackResourcesResponse = await this.cfnClient.send( + new DescribeStackResourcesCommand({ + StackName: this.fromStack, + }), + ); + const destStackResourcesResponse = await this.cfnClient.send( + new DescribeStackResourcesCommand({ + StackName: this.toStack, + }), + ); + const sourceStackResources = sourceStackResourcesResponse.StackResources; + const destStackResources = destStackResourcesResponse.StackResources; + assert(sourceStackResources, 'No source stack resources found'); + assert(destStackResources, 'No destination stack resources found'); + const sourceCategoryStacks = sourceStackResources?.filter((stackResource) => stackResource.ResourceType === CFN_RESOURCE_STACK_TYPE); + const destinationCategoryStacks = destStackResources?.filter((stackResource) => stackResource.ResourceType === CFN_RESOURCE_STACK_TYPE); + assert(sourceCategoryStacks && sourceCategoryStacks?.length > 0, 'No source category stack found'); + assert(destinationCategoryStacks && destinationCategoryStacks?.length > 0, 'No destination category stack found'); + for (const { LogicalResourceId: sourceLogicalResourceId, PhysicalResourceId: sourcePhysicalResourceId } of sourceCategoryStacks) { + const category = CATEGORIES.find((category) => sourceLogicalResourceId?.startsWith(category)); + if (!category) continue; + assert(sourcePhysicalResourceId); + let destinationPhysicalResourceId: string | undefined; + let userPoolGroupDestinationPhysicalResourceId: string | undefined; + + const correspondingCategoryStackInDestination = destinationCategoryStacks.find( + ({ LogicalResourceId: destinationLogicalResourceId }) => destinationLogicalResourceId?.startsWith(category), + ); + if (!correspondingCategoryStackInDestination) { + throw new Error(`No corresponding category found in destination stack for ${category} category`); + } + destinationPhysicalResourceId = correspondingCategoryStackInDestination.PhysicalResourceId; + + let isUserPoolGroupStack = false; + if (!isRevert && category === 'auth') { + const gen1AuthTypeStack = await this.getGen1AuthTypeStack(sourcePhysicalResourceId); + isUserPoolGroupStack = gen1AuthTypeStack === 'auth-user-pool-group'; + } else if (isRevert && category === 'auth') { + for (const { + LogicalResourceId: destinationLogicalResourceId, + PhysicalResourceId: _destinationPhysicalResourceId, + } of destinationCategoryStacks) { + assert(_destinationPhysicalResourceId); + const destinationIsAuthCategory = destinationLogicalResourceId?.startsWith('auth'); + if (!destinationIsAuthCategory) continue; + const gen1AuthTypeStack = await this.getGen1AuthTypeStack(_destinationPhysicalResourceId); + isUserPoolGroupStack = gen1AuthTypeStack === 'auth-user-pool-group'; + if (isUserPoolGroupStack) { + userPoolGroupDestinationPhysicalResourceId = _destinationPhysicalResourceId; + } else if (gen1AuthTypeStack === 'auth') { + destinationPhysicalResourceId = _destinationPhysicalResourceId; + } + } + } + + assert(destinationPhysicalResourceId); + + this.updateCategoryStackMap( + category, + sourcePhysicalResourceId, + destinationPhysicalResourceId, + isUserPoolGroupStack, + isRevert, + userPoolGroupDestinationPhysicalResourceId, + ); + } + } + + private updateCategoryStackMap( + category: CATEGORY | string, + sourcePhysicalResourceId: string, + destinationPhysicalResourceId: string, + isUserPoolGroupStack: boolean, + isRevert: boolean, + userPoolGroupDestinationPhysicalResourceId?: string, + ): void { + // User pool groups and the auth resources are part of the same stack in Gen2, but different in Gen1 + // Hence, we need to also add auth category stack mapping in case of revert (moving back to Gen1). + if (!isUserPoolGroupStack || isRevert) { + this.categoryStackMap.set(category, [sourcePhysicalResourceId, destinationPhysicalResourceId]); + } + if (isUserPoolGroupStack) { + const destinationId = + isRevert && userPoolGroupDestinationPhysicalResourceId ? userPoolGroupDestinationPhysicalResourceId : destinationPhysicalResourceId; + + this.categoryStackMap.set('auth-user-pool-group', [sourcePhysicalResourceId, destinationId]); + } + } + + private getGen1AuthTypeStack = async (stackName: string): Promise => { + const describeStacksResponse = await this.cfnClient.send( + new DescribeStacksCommand({ + StackName: stackName, + }), + ); + const stackDescription = describeStacksResponse?.Stacks?.[0]?.Description; + assert(stackDescription); + try { + const parsedStackDescription = JSON.parse(stackDescription); + if (typeof parsedStackDescription === 'object' && 'stackType' in parsedStackDescription) { + switch (parsedStackDescription.stackType) { + case GEN1_USER_POOL_GROUPS_STACK_TYPE_DESCRIPTION: + return 'auth-user-pool-group'; + case GEN1_AUTH_STACK_TYPE_DESCRIPTION: + return 'auth'; + } + } + } catch (e) { + // unable to parse description. Fail silently. + } + return null; + }; + + private isNoResourcesError(error: unknown): boolean { + return ( + typeof error === 'object' && + error !== null && + 'message' in error && + typeof error.message === 'string' && + (error.message.includes(NO_RESOURCES_TO_MOVE_ERROR) || error.message.includes(NO_RESOURCES_TO_REMOVE_ERROR)) + ); + } + + private getStackCategoryName(category: string) { + return !this.isCustomResource(category) ? category : 'custom'; + } + + private async processGen1Stack( + category: string, + categoryTemplateGenerator: CategoryTemplateGenerator, + sourceCategoryStackId: string, + ): Promise<[CFNTemplate, Parameter[]] | undefined> { + let updatingGen1CategoryStack; + try { + const { newTemplate, parameters: gen1StackParameters } = await categoryTemplateGenerator.generateGen1PreProcessTemplate(); + assert(gen1StackParameters); + updatingGen1CategoryStack = ora(`Updating Gen 1 ${this.getStackCategoryName(category)} stack...`).start(); + + const gen1StackUpdateStatus = await tryUpdateStack(this.cfnClient, sourceCategoryStackId, gen1StackParameters, newTemplate); + + assert(gen1StackUpdateStatus === CFNStackStatus.UPDATE_COMPLETE, `Gen 1 stack is in an invalid state: ${gen1StackUpdateStatus}`); + updatingGen1CategoryStack.succeed(`Updated Gen 1 ${this.getStackCategoryName(category)} stack successfully`); + + return [newTemplate, gen1StackParameters]; + } catch (e) { + if (this.isNoResourcesError(e)) { + updatingGen1CategoryStack?.succeed( + `No resources found to move in Gen 1 ${this.getStackCategoryName(category)} stack. Skipping update.`, + ); + return undefined; + } + throw e; + } + } + + private async processGen2Stack( + category: string, + categoryTemplateGenerator: CategoryTemplateGenerator, + destinationCategoryStackId: string, + ): Promise<{ + newTemplate: CFNTemplate; + oldTemplate: CFNTemplate; + parameters?: Parameter[]; + }> { + try { + const { newTemplate, oldTemplate, parameters } = await categoryTemplateGenerator.generateGen2ResourceRemovalTemplate(); + + const updatingGen2CategoryStack = ora(`Updating Gen 2 ${this.getStackCategoryName(category)} stack...`).start(); + + const gen2StackUpdateStatus = await tryUpdateStack(this.cfnClient, destinationCategoryStackId, parameters ?? [], newTemplate); + + assert(gen2StackUpdateStatus === CFNStackStatus.UPDATE_COMPLETE, `Gen 2 stack is in an invalid state: ${gen2StackUpdateStatus}`); + updatingGen2CategoryStack.succeed(`Updated Gen 2 ${this.getStackCategoryName(category)} stack successfully`); + + return { newTemplate, oldTemplate, parameters }; + } catch (e) { + if (this.isNoResourcesError(e)) { + const currentTemplate = categoryTemplateGenerator.gen2Template; + assert(currentTemplate); + const parameters = categoryTemplateGenerator.gen2StackParameters; + return { newTemplate: currentTemplate, oldTemplate: currentTemplate, parameters }; + } + throw e; + } + } + + private initializeCategoryGenerators(customResourceMap?: ResourceMapping[]) { + assert(this.region); + + for (const [category, [sourceStackId, destinationStackId]] of this.categoryStackMap.entries()) { + const config = this.categoryGeneratorConfig[category as keyof typeof this.categoryGeneratorConfig]; + + if (config) { + this.categoryTemplateGenerators.push([ + category, + sourceStackId, + destinationStackId, + this.createCategoryTemplateGenerator(sourceStackId, destinationStackId, config.resourcesToRefactor), + ]); + } else if (customResourceMap && this.isCustomResource(category)) { + this.categoryTemplateGenerators.push([ + category, + sourceStackId, + destinationStackId, + this.createCategoryTemplateGenerator(sourceStackId, destinationStackId, [], customResourceMap), + ]); + } + } + } + + private createCategoryTemplateGenerator( + sourceStackId: string, + destinationStackId: string, + resourcesToRefactor: CFN_CATEGORY_TYPE[], + customResourceMap?: ResourceMapping[], + ): CategoryTemplateGenerator { + assert(this.region); + return new CategoryTemplateGenerator( + sourceStackId, + destinationStackId, + this.region, + this.accountId, + this.cfnClient, + this.ssmClient, + this.cognitoIdpClient, + this.appId, + this.environmentName, + resourcesToRefactor, + customResourceMap + ? (_resourcesToMove: CFN_CATEGORY_TYPE[], cfnResource: [string, CFNResource]) => { + const [logicalId] = cfnResource; + + // Check if customResourceMap contains the logical ID + return ( + customResourceMap?.some( + (resourceMapping) => + resourceMapping.Source.LogicalResourceId === logicalId || resourceMapping.Destination.LogicalResourceId === logicalId, + ) ?? false + ); + } + : undefined, + ); + } + + private isCustomResource(category: string) { + return !Object.values(NON_CUSTOM_RESOURCE_CATEGORY) + .map((nonCustomCategory) => nonCustomCategory.valueOf()) + .includes(category); + } + + private async generateCategoryTemplates(isRevert = false, customResourceMap?: ResourceMapping[]) { + this.initializeCategoryGenerators(customResourceMap); + let hasOAuthEnabled = false; + for (const [category, sourceCategoryStackId, destinationCategoryStackId, categoryTemplateGenerator] of this + .categoryTemplateGenerators) { + let newSourceTemplate: CFNTemplate | undefined; + let newDestinationTemplate: CFNTemplate | undefined; + let oldDestinationTemplate: CFNTemplate | undefined; + let sourceStackParameters: Parameter[] | undefined; + let destinationStackParameters: Parameter[] | undefined; + let sourceTemplateForRefactor: CFNTemplate | undefined; + let destinationTemplateForRefactor: CFNTemplate | undefined; + let logicalIdMappingForRefactor: Map | undefined; + + if (customResourceMap && this.isCustomResource(category)) { + const processGen1StackResponse = await this.processGen1Stack(category, categoryTemplateGenerator, sourceCategoryStackId); + if (!processGen1StackResponse) continue; + const [newGen1Template] = processGen1StackResponse; + newSourceTemplate = newGen1Template; + + const { newTemplate } = await this.processGen2Stack(category, categoryTemplateGenerator, destinationCategoryStackId); + newDestinationTemplate = newTemplate; + + const sourceToDestinationMap = new Map(); + + for (const resourceMapping of customResourceMap) { + const sourceLogicalId = resourceMapping.Source.LogicalResourceId; + const destinationLogicalId = resourceMapping.Destination.LogicalResourceId; + + if (sourceLogicalId && destinationLogicalId) { + sourceToDestinationMap.set(sourceLogicalId, destinationLogicalId); + } + } + + const { sourceTemplate, destinationTemplate, logicalIdMapping } = categoryTemplateGenerator.generateRefactorTemplates( + categoryTemplateGenerator.gen1ResourcesToMove, + categoryTemplateGenerator.gen2ResourcesToRemove, + newSourceTemplate, + newDestinationTemplate, + sourceToDestinationMap, + ); + + sourceTemplateForRefactor = sourceTemplate; + destinationTemplateForRefactor = destinationTemplate; + logicalIdMappingForRefactor = logicalIdMapping; + } else if (!isRevert) { + const processGen1StackResponse = await this.processGen1Stack(category, categoryTemplateGenerator, sourceCategoryStackId); + if (!processGen1StackResponse) continue; + const [newGen1Template, gen1StackParameters] = processGen1StackResponse; + sourceStackParameters = gen1StackParameters; + newSourceTemplate = newGen1Template; + if (category === 'auth' && sourceStackParameters?.find((param) => param.ParameterKey === HOSTED_PROVIDER_META_PARAMETER_NAME)) { + hasOAuthEnabled = true; + } + const { newTemplate, oldTemplate, parameters } = await this.processGen2Stack( + category, + categoryTemplateGenerator, + destinationCategoryStackId, + ); + newDestinationTemplate = newTemplate; + oldDestinationTemplate = oldTemplate; + destinationStackParameters = parameters; + const { sourceTemplate, destinationTemplate, logicalIdMapping } = categoryTemplateGenerator.generateStackRefactorTemplates( + newSourceTemplate, + newDestinationTemplate, + ); + sourceTemplateForRefactor = sourceTemplate; + destinationTemplateForRefactor = destinationTemplate; + logicalIdMappingForRefactor = logicalIdMapping; + } + // revert scenario + else { + const sourceCategoryTemplate = await categoryTemplateGenerator.readTemplate(sourceCategoryStackId); + const destinationCategoryTemplate = await categoryTemplateGenerator.readTemplate(destinationCategoryStackId); + newSourceTemplate = sourceCategoryTemplate; + newDestinationTemplate = destinationCategoryTemplate; + try { + const { sourceTemplate, destinationTemplate, logicalIdMapping } = await this.generateRefactorTemplatesForRevert( + newSourceTemplate, + newDestinationTemplate, + categoryTemplateGenerator, + sourceCategoryStackId, + category, + ); + sourceTemplateForRefactor = sourceTemplate; + destinationTemplateForRefactor = destinationTemplate; + logicalIdMappingForRefactor = logicalIdMapping; + } catch (e) { + if (typeof e === 'object' && 'message' in e && e.message.includes(NO_RESOURCES_TO_MOVE_ERROR)) { + continue; + } + throw e; + } + } + + const refactorResources = ora( + `Moving ${this.getStackCategoryName(category)} resources from ${this.getSourceToDestinationMessage(isRevert)} stack...`, + ).start(); + const { success, failedRefactorMetadata } = await this.refactorResources( + logicalIdMappingForRefactor, + sourceCategoryStackId, + destinationCategoryStackId, + category, + isRevert, + sourceTemplateForRefactor, + destinationTemplateForRefactor, + ); + if (!success) { + refactorResources.fail( + `Moving ${this.getStackCategoryName(category)} resources from ${this.getSourceToDestinationMessage( + isRevert, + )} stack failed. Reason: ${failedRefactorMetadata?.reason}. Status: ${failedRefactorMetadata?.status}. RefactorId: ${ + failedRefactorMetadata?.stackRefactorId + }.`, + ); + await pollStackForCompletionState(this.cfnClient, destinationCategoryStackId, 30); + if (!isRevert && oldDestinationTemplate) { + await this.rollbackGen2Stack(category, destinationCategoryStackId, destinationStackParameters, oldDestinationTemplate); + } + return false; + } else { + refactorResources.succeed( + `Moved ${this.getStackCategoryName(category)} resources from ${this.getSourceToDestinationMessage(isRevert)} stack successfully`, + ); + } + } + if (!isRevert) { + const migrationReadMeGenerator = new MigrationReadmeGenerator({ + path: `${TEMPLATES_DIR}`, + categories: [...this.categoryStackMap.keys()], + hasOAuthEnabled, + }); + await migrationReadMeGenerator.initialize(); + await migrationReadMeGenerator.renderStep1(); + } + return true; + } + + private async refactorResources( + logicalIdMappingForRefactor: Map, + sourceCategoryStackId: string, + destinationCategoryStackId: string, + category: 'auth' | 'storage' | 'auth-user-pool-group' | string, + isRevert: boolean, + sourceTemplateForRefactor: CFNTemplate, + destinationTemplateForRefactor: CFNTemplate, + ) { + const resourceMappings = []; + for (const [sourceLogicalId, destinationLogicalId] of logicalIdMappingForRefactor) { + resourceMappings.push({ + Source: { + StackName: sourceCategoryStackId, + LogicalResourceId: sourceLogicalId, + }, + Destination: { + StackName: destinationCategoryStackId, + LogicalResourceId: destinationLogicalId, + }, + }); + } + const [success, failedRefactorMetadata] = await tryRefactorStack(this.cfnClient, { + StackDefinitions: [ + { + TemplateBody: JSON.stringify(sourceTemplateForRefactor), + StackName: sourceCategoryStackId, + }, + { + TemplateBody: JSON.stringify(destinationTemplateForRefactor), + StackName: destinationCategoryStackId, + }, + ], + ResourceMappings: resourceMappings, + }); + return { success, failedRefactorMetadata }; + } + + private async rollbackGen2Stack( + category: CATEGORY, + gen2CategoryStackId: string, + gen2StackParameters: Parameter[] | undefined, + oldGen2Template: CFNTemplate, + ) { + const rollingBackGen2Stack = ora(`Rolling back Gen 2 ${this.getStackCategoryName(category)} stack...`).start(); + const gen2StackUpdateStatus = await tryUpdateStack(this.cfnClient, gen2CategoryStackId, gen2StackParameters ?? [], oldGen2Template); + assert(gen2StackUpdateStatus === CFNStackStatus.UPDATE_COMPLETE, `Gen 2 Stack is in a failed state: ${gen2StackUpdateStatus}.`); + rollingBackGen2Stack.succeed(`Rolled back Gen 2 ${this.getStackCategoryName(category)} stack successfully`); + } + + private async generateRefactorTemplatesForRevert( + newSourceTemplate: CFNTemplate, + newDestinationTemplate: CFNTemplate, + categoryTemplateGenerator: CategoryTemplateGenerator, + sourceCategoryStackId: string, + category: CATEGORY, + ) { + assert(newSourceTemplate.Resources); + const sourceResourcesToRemove: Map = new Map( + Object.entries(newSourceTemplate.Resources).filter(([, value]) => + LOGICAL_IDS_TO_REMOVE_FOR_REVERT_MAP.get(category)?.some((resourceToMove) => resourceToMove.valueOf() === value.Type), + ), + ); + if (sourceResourcesToRemove.size === 0) { + throw new Error(`${NO_RESOURCES_TO_MOVE_ERROR} in ${category} stack.`); + } + const describeStackResponseForSourceTemplate = await categoryTemplateGenerator.describeStack(sourceCategoryStackId); + assert(describeStackResponseForSourceTemplate); + const sourceLogicalIds = [...sourceResourcesToRemove.keys()]; + const { Outputs, Parameters } = describeStackResponseForSourceTemplate; + assert(Outputs); + assert(this.region); + const { StackResources } = await this.cfnClient.send( + new DescribeStackResourcesCommand({ + StackName: sourceCategoryStackId, + }), + ); + assert(StackResources); + const newSourceTemplateWithParametersResolved = new CfnParameterResolver(newSourceTemplate).resolve(Parameters ?? []); + const newSourceTemplateWithOutputsResolved = new CfnOutputResolver( + newSourceTemplateWithParametersResolved, + this.region, + this.accountId, + ).resolve(sourceLogicalIds, Outputs, StackResources); + const newSourceTemplateWithDepsResolved = new CfnDependencyResolver(newSourceTemplateWithOutputsResolved).resolve(sourceLogicalIds); + return categoryTemplateGenerator.generateRefactorTemplates( + sourceResourcesToRemove, + new Map(), + newSourceTemplateWithDepsResolved, + newDestinationTemplate, + this.buildSourceToDestinationMapForRevert(sourceResourcesToRemove), + ); + } + + private getSourceToDestinationMessage(revert: boolean) { + const SOURCE_TO_DESTINATION_STACKS = [GEN1, GEN2]; + return revert ? SOURCE_TO_DESTINATION_STACKS.reverse().join(SEPARATOR) : SOURCE_TO_DESTINATION_STACKS.join(SEPARATOR); + } + + private buildSourceToDestinationMapForRevert(sourceResourcesToRemove: Map): Map { + const sourceToDestinationLogicalIdsMap = new Map(); + for (const [sourceLogicalId, resource] of sourceResourcesToRemove) { + if (sourceLogicalId.includes(GEN2_NATIVE_APP_CLIENT)) { + sourceToDestinationLogicalIdsMap.set(sourceLogicalId, 'UserPoolClient'); + } else if (resource.Type === CFN_AUTH_TYPE.UserPoolGroup) { + const [, sourceLogicalIdSuffix] = sourceLogicalId.split(GEN2_AMPLIFY_AUTH_LOGICAL_ID_PREFIX); + // last 8 digits are always a CDK HASH + // amplifyAuth8digitCDKHASH + const destinationLogicalId = sourceLogicalIdSuffix.slice(0, sourceLogicalIdSuffix.length - 8); + sourceToDestinationLogicalIdsMap.set(sourceLogicalId, destinationLogicalId); + } else { + const destinationLogicalId = GEN1_RESOURCE_TYPE_TO_LOGICAL_RESOURCE_IDS_MAP.get(resource.Type); + assert(destinationLogicalId); + sourceToDestinationLogicalIdsMap.set(sourceLogicalId, destinationLogicalId); + } + } + + return sourceToDestinationLogicalIdsMap; + } +} + +export { TemplateGenerator }; diff --git a/packages/amplify-migration-template-gen/src/types.ts b/packages/amplify-migration-template-gen/src/types.ts new file mode 100644 index 00000000000..2de6f6a7866 --- /dev/null +++ b/packages/amplify-migration-template-gen/src/types.ts @@ -0,0 +1,146 @@ +import { Parameter, StackRefactorExecutionStatus, StackRefactorStatus } from '@aws-sdk/client-cloudformation'; + +// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html +export interface CFNOutput { + Description?: string; + Value: string | object; +} + +export enum CFNFunction { + Equals = 'Fn::Equals', + Not = 'Fn::Not', + Or = 'Fn::Or', + And = 'Fn::And', + If = 'Fn::If', +} + +export type CFNIntrinsicFunctionCondition = { + Condition: string; +}; + +export type CFNConditionFunctionStatement = string | object | CFNConditionFunction | CFNIntrinsicFunctionCondition; + +export type CFNConditionFunction = + | { [CFNFunction.Equals]: [CFNConditionFunctionStatement, CFNConditionFunctionStatement] } + | { [CFNFunction.Not]: [CFNConditionFunctionStatement] } + | { + [CFNFunction.Or]: [CFNConditionFunctionStatement, CFNConditionFunctionStatement]; + } + | { [CFNFunction.And]: [CFNConditionFunctionStatement, CFNConditionFunctionStatement] }; + +export interface CFNResource { + Type: string; + Properties: Record; + DependsOn?: string[]; + Condition?: string; +} + +export interface CFNParameter { + Type: string; + Default?: string; + Description?: string; + NoEcho?: boolean; +} + +export interface CFNTemplate { + Description: string; + AWSTemplateFormatVersion: string; + Conditions?: Record; + Resources: Record; + Parameters?: Record; + Outputs: Record; +} + +export interface CFNChangeTemplate { + oldTemplate: CFNTemplate; + newTemplate: CFNTemplate; +} + +export interface CFNChangeTemplateWithParams extends CFNChangeTemplate { + parameters: Parameter[] | undefined; +} + +export interface CFNStackRefactorTemplates { + sourceTemplate: CFNTemplate; + destinationTemplate: CFNTemplate; + logicalIdMapping: Map; +} + +export enum NON_CUSTOM_RESOURCE_CATEGORY { + AUTH = 'auth', + STORAGE = 'storage', + AUTH_USER_POOL_GROUP = 'auth-user-pool-group', +} + +export type CATEGORY = + | NON_CUSTOM_RESOURCE_CATEGORY.AUTH + | NON_CUSTOM_RESOURCE_CATEGORY.STORAGE + | NON_CUSTOM_RESOURCE_CATEGORY.AUTH_USER_POOL_GROUP + | string; + +export interface ResourceMappingLocation { + StackName: string; + LogicalResourceId: string; +} + +export interface ResourceMapping { + Source: ResourceMappingLocation; + Destination: ResourceMappingLocation; +} + +export enum CFN_AUTH_TYPE { + UserPool = 'AWS::Cognito::UserPool', + UserPoolClient = 'AWS::Cognito::UserPoolClient', + IdentityPool = 'AWS::Cognito::IdentityPool', + IdentityPoolRoleAttachment = 'AWS::Cognito::IdentityPoolRoleAttachment', + UserPoolDomain = 'AWS::Cognito::UserPoolDomain', + UserPoolGroup = 'AWS::Cognito::UserPoolGroup', +} + +export enum CFN_S3_TYPE { + Bucket = 'AWS::S3::Bucket', +} + +export enum CFN_IAM_TYPE { + Role = 'AWS::IAM::Role', +} + +export enum CFN_SQS_TYPE { + Queue = 'AWS::SQS::Queue', +} + +export enum CFN_LAMBDA_TYPE { + Function = 'AWS::Lambda::Function', +} + +export type CFN_RESOURCE_TYPES = CFN_AUTH_TYPE | CFN_S3_TYPE | CFN_IAM_TYPE | CFN_SQS_TYPE | CFN_LAMBDA_TYPE; + +export type AWS_RESOURCE_ATTRIBUTES = 'Arn'; + +export type CFN_CATEGORY_TYPE = CFN_AUTH_TYPE | CFN_S3_TYPE | CFN_IAM_TYPE | string; + +export enum CFN_PSEUDO_PARAMETERS_REF { + StackName = 'AWS::StackName', +} + +export enum CFNStackStatus { + UPDATE_COMPLETE = 'UPDATE_COMPLETE', +} + +export type BaseOAuthClient = { ProviderName: string; client_id: string }; +export type OAuthClientWithSecret = BaseOAuthClient & { client_secret: string }; +export type SignInWithAppleOAuthClient = BaseOAuthClient & { team_id: string; key_id: string; private_key: string }; +export type OAuthClient = OAuthClientWithSecret | SignInWithAppleOAuthClient; +export type HostedUIProviderMeta = { + ProviderName: 'Amazon' | 'Facebook' | 'Google' | 'SignInWithApple'; +}; + +export type FailedRefactorResponse = { + reason: string | undefined; + stackRefactorId: string; + status: StackRefactorStatus | StackRefactorExecutionStatus | undefined; +}; + +export enum GEN2_AUTH_LOGICAL_RESOURCE_ID { + IDENTITY_POOL_ROLE_ATTACHMENT = 'IdentityPoolRoleAttachment', +} diff --git a/packages/amplify-migration-template-gen/tsconfig.json b/packages/amplify-migration-template-gen/tsconfig.json new file mode 100644 index 00000000000..0d3b1836861 --- /dev/null +++ b/packages/amplify-migration-template-gen/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": { "rootDir": "src", "outDir": "lib" }, + "extends": "../../tsconfig.base.json" +} diff --git a/packages/amplify-migration-tests/CHANGELOG.md b/packages/amplify-migration-tests/CHANGELOG.md index c82192dd186..e2396ab829f 100644 --- a/packages/amplify-migration-tests/CHANGELOG.md +++ b/packages/amplify-migration-tests/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.5.6-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-tests@6.5.5-next-7.0...@aws-amplify/amplify-migration-tests@6.5.6-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [6.5.5](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-migration-tests@6.5.4...@aws-amplify/amplify-migration-tests@6.5.5) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-migration-tests diff --git a/packages/amplify-migration-tests/package.json b/packages/amplify-migration-tests/package.json index 5f4770e63fa..78403a35ffc 100644 --- a/packages/amplify-migration-tests/package.json +++ b/packages/amplify-migration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-migration-tests", - "version": "6.5.5", + "version": "6.5.6-next-11.0", "description": "", "repository": { "type": "git", @@ -26,8 +26,8 @@ "setup-profile": "ts-node ./src/configure_tests.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-e2e-core": "5.7.4", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-e2e-core": "5.7.5-next-11.0", "@aws-cdk/cloudformation-diff": "~2.68.0", "@aws-sdk/client-s3": "3.624.0", "amplify-headless-interface": "1.17.7", diff --git a/packages/amplify-migration/.gitignore b/packages/amplify-migration/.gitignore new file mode 100644 index 00000000000..c3af857904e --- /dev/null +++ b/packages/amplify-migration/.gitignore @@ -0,0 +1 @@ +lib/ diff --git a/packages/amplify-migration/CHANGELOG.md b/packages/amplify-migration/CHANGELOG.md new file mode 100644 index 00000000000..1c9a018a9d6 --- /dev/null +++ b/packages/amplify-migration/CHANGELOG.md @@ -0,0 +1,328 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [0.1.0-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-10.0...@aws-amplify/migrate@0.1.0-next-11.0) (2025-05-01) + + +### Bug Fixes + +* migration test file extension ([74b58eb](https://github.com/aws-amplify/amplify-cli/commit/74b58ebf250b9a0ffbf7469fae76ca758a7b077a)) +* remove junit.xml ([04a08d6](https://github.com/aws-amplify/amplify-cli/commit/04a08d6f626cc3e81726a0b32031b0b09470494f)) +* update lock file ([8bb071b](https://github.com/aws-amplify/amplify-cli/commit/8bb071be0b29498bbfed7d72bb9ce172326a150e)) +* update lock file for cli-core ([4b75048](https://github.com/aws-amplify/amplify-cli/commit/4b7504849588229c03dd4f572973b94001b10f64)) + + + + + +# [0.1.0-next-10.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-9.0...@aws-amplify/migrate@0.1.0-next-10.0) (2025-04-24) + +**Note:** Version bump only for package @aws-amplify/migrate + + + + + +# [0.1.0-next-9.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-8.0...@aws-amplify/migrate@0.1.0-next-9.0) (2025-04-22) + + +### Bug Fixes + +* **migrate:** codegen triggers created outside of standard triggers flow ([bc7010a](https://github.com/aws-amplify/amplify-cli/commit/bc7010ab5928681d2a208d6576296b80f09d6e78)) +* remove extraneous console log ([8741187](https://github.com/aws-amplify/amplify-cli/commit/874118705ea86672389c33626044e0181fc3aa24)) + + + + + +# [0.1.0-next-8.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-7.0...@aws-amplify/migrate@0.1.0-next-8.0) (2025-04-21) + +**Note:** Version bump only for package @aws-amplify/migrate + + + + + +# [0.1.0-next-7.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-6.0...@aws-amplify/migrate@0.1.0-next-7.0) (2025-04-19) + + +### Bug Fixes + +* add amplify data as a dependency to amplify-gen2-codegen ([3f0713b](https://github.com/aws-amplify/amplify-cli/commit/3f0713b9777e37a240e9c16c0840c533c880e753)) +* added error handling ([a4647df](https://github.com/aws-amplify/amplify-cli/commit/a4647df76b7e6c8a470690dd43320a96a0a45e97)) +* codegen custom resources and added function scheduling ([1e25182](https://github.com/aws-amplify/amplify-cli/commit/1e251820905291bc8c6ca058ce5397b7ddec5e5b)) +* custom resource error handling, formatting messages ([199b138](https://github.com/aws-amplify/amplify-cli/commit/199b13815df81846abbbcd41793d1cccabae9533)) +* lint errors ([00b25b7](https://github.com/aws-amplify/amplify-cli/commit/00b25b756915e40001d07eceb795aa1b35289cb3)) +* lint errors, add type gauard for auth output ([4b74382](https://github.com/aws-amplify/amplify-cli/commit/4b7438270634b04cd73c302610e814790fb4328a)) +* **migrate:** add validation for path after file:// ([0173f7f](https://github.com/aws-amplify/amplify-cli/commit/0173f7fd66a36a9cb25f0baf9a2306dd6948d877)) +* refactor customResourceMappings in execute command ([000d660](https://github.com/aws-amplify/amplify-cli/commit/000d6608ade3ee4c65724df59200ffc00584e84c)) +* update lock file ([699f9e7](https://github.com/aws-amplify/amplify-cli/commit/699f9e7c48039f77dd6620bf8346fe2840a6166c)) + + +### Features + +* **migrate:** remove gen1 config files in prepare command ([2d72d51](https://github.com/aws-amplify/amplify-cli/commit/2d72d5109aaccff31025813b14ac14e529d85b21)) + + + + + +# [0.1.0-next-6.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-5.0...@aws-amplify/migrate@0.1.0-next-6.0) (2025-03-21) + + +### Bug Fixes + +* add idp codegen and fix a couple of minor bugs ([2b1d203](https://github.com/aws-amplify/amplify-cli/commit/2b1d203f2c065a701076cf4cdbd50b4ef51bc7ee)) +* **amplify-gen2-codegen:** remove extraneous newline ([aaf6dba](https://github.com/aws-amplify/amplify-cli/commit/aaf6dba696091933bc99583a2262f23dc96ea0ec)) +* migration qa feedback ([0f50d1c](https://github.com/aws-amplify/amplify-cli/commit/0f50d1c0ff2607c63cd3bcdd1589d38940a2b6d4)) + + + + + +# [0.1.0-next-5.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-4.0...@aws-amplify/migrate@0.1.0-next-5.0) (2025-03-19) + + +### Bug Fixes + +* **migrate-template-gen:** return early if output is not Fn:GetAtt or a Ref ([5a985f0](https://github.com/aws-amplify/amplify-cli/commit/5a985f0ea48a8b32ed51d04af83fbbf6baa29528)) +* update amplify.yml file with the gen2 command ([4d4434f](https://github.com/aws-amplify/amplify-cli/commit/4d4434f29b8c598cabe1740df8f03e0c4970fda7)) + + + + + +# [0.1.0-next-4.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-3.0...@aws-amplify/migrate@0.1.0-next-4.0) (2025-03-12) + +**Note:** Version bump only for package @aws-amplify/migrate + + + + + +# [0.1.0-next-3.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-2.0...@aws-amplify/migrate@0.1.0-next-3.0) (2025-03-05) + +**Note:** Version bump only for package @aws-amplify/migrate + + + + + +# [0.1.0-next-2.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next-1.0...@aws-amplify/migrate@0.1.0-next-2.0) (2025-02-26) + + +### Bug Fixes + +* **migrate:** lint errors ([a3c75b3](https://github.com/aws-amplify/amplify-cli/commit/a3c75b3a98719970e11733043a405c7764350e53)) +* **migrate:** lint errors ([98dced2](https://github.com/aws-amplify/amplify-cli/commit/98dced209aeea4c26aec86d3d5aba19830091b4a)) +* **migrate:** remove unused import in test ([7b10b3b](https://github.com/aws-amplify/amplify-cli/commit/7b10b3ba880212b8abbbbbe2b67b92c883d9696b)) +* **migrate:** run prettier ([74d87c9](https://github.com/aws-amplify/amplify-cli/commit/74d87c9c5d2ae515ed9c7b92d10cf70e7ebf373c)) +* **migrate:** use latest cli-internal version ([b5b83b3](https://github.com/aws-amplify/amplify-cli/commit/b5b83b3b3d4e8024dc7d0b08c175db6f49610347)) + + +### Features + +* **migrate:** add revert, userpool group refactor ([38eed7e](https://github.com/aws-amplify/amplify-cli/commit/38eed7e57e785cece232ce967ddc9171390af312)) + + + + + +# [0.1.0-next-1.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-next.0...@aws-amplify/migrate@0.1.0-next-1.0) (2025-02-17) + + +### Bug Fixes + +* **migrate:** remove migration dir if exists prior to rename ([b62b487](https://github.com/aws-amplify/amplify-cli/commit/b62b4874ffba3c3fce64cb3e723d02930104b81d)) +* **migrate:** set CLI_ENV to production for usage metrics, emit error metrics when execute fails ([466e6c7](https://github.com/aws-amplify/amplify-cli/commit/466e6c7bebd2928ea76b513feb97db7b563a5037)) + + + + + +# [0.1.0-next.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-beta-latest.0...@aws-amplify/migrate@0.1.0-next.0) (2025-02-14) + + +### Bug Fixes + +* added dynamic reference to the env name in backend.ts and fixed a few bugs ([59e6a01](https://github.com/aws-amplify/amplify-cli/commit/59e6a014a6aadc17c170e57e6278242bed054697)) +* conditional data codegen, phone attribute ([0d5c59f](https://github.com/aws-amplify/amplify-cli/commit/0d5c59fae2849277bced4e4f0c0529d916c0e165)) + + + + + +# [0.1.0-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-alpha.1...@aws-amplify/migrate@0.1.0-beta-latest.0) (2025-02-12) + + +### Bug Fixes + +* add progress bar for gen 2 codegen ([6b7bf79](https://github.com/aws-amplify/amplify-cli/commit/6b7bf79ec9fa7c65245956b179e261fa0604bb9b)) +* add uncomment instructions in readme ([b1ca1b1](https://github.com/aws-amplify/amplify-cli/commit/b1ca1b1efe70425b97c9083f5ac47d71c32aaeb7)) +* auth definition fetcher test ([dd75bfd](https://github.com/aws-amplify/amplify-cli/commit/dd75bfdd81a50104534f18f94ab10fc8c0641d72)) +* check for absence of auth in gen2 codegen ([518a0f4](https://github.com/aws-amplify/amplify-cli/commit/518a0f41f0cc817a05932e4c5bb06d5c805c5cc7)) +* choose current env, update gitignore for gen2, derive fn name from output key ([40b1730](https://github.com/aws-amplify/amplify-cli/commit/40b17308470d1946878c1e74afde61c67c211625)) +* delete extraneous map file ([2699ee2](https://github.com/aws-amplify/amplify-cli/commit/2699ee2ee2ab7fa806675b66710a588ceb5e438b)) +* function migration adapter category ([afb1136](https://github.com/aws-amplify/amplify-cli/commit/afb1136d5c1eb82e0aa7baf6c12784b06a72de17)) +* lint & api md ([351d7b2](https://github.com/aws-amplify/amplify-cli/commit/351d7b22fb7308c974ceea965566431e6d296183)) +* lint errros and warnings in amplify-migration ([8464c01](https://github.com/aws-amplify/amplify-cli/commit/8464c019b70cadbb786b281b9f0b02ca057c402e)) +* lint in migrations package ([8380bb0](https://github.com/aws-amplify/amplify-cli/commit/8380bb0d2829884c02ab8e450c575d5f07f2ac4f)) +* orphaned functions, import auth ([26fd22b](https://github.com/aws-amplify/amplify-cli/commit/26fd22be0232ba11e37d165135c0912deeb0c520)) +* remove extraneous deps ([74c6647](https://github.com/aws-amplify/amplify-cli/commit/74c6647296512c50f9ace9021ea4e2c332e605ac)) +* remove ora mock ([783f33f](https://github.com/aws-amplify/amplify-cli/commit/783f33f9a083679fed266bcb860962c1ed0d629f)) +* use older version of ora to keep it consistent with other packages ([0b390a3](https://github.com/aws-amplify/amplify-cli/commit/0b390a3d19d07efac86699c0628954602ebdf862)) + + +### Features + +* add refactor operation to gen2 migration ([9f2752b](https://github.com/aws-amplify/amplify-cli/commit/9f2752b9b116b81267cb6ac5f7fd0877781c9e7f)) +* include all envs in gen 2 data codegen ([#14087](https://github.com/aws-amplify/amplify-cli/issues/14087)) ([6a437e3](https://github.com/aws-amplify/amplify-cli/commit/6a437e3345489ce22d78621de18acc46f969d883)) + + + + + +# [0.1.0-alpha.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-alpha.0...@aws-amplify/migrate@0.1.0-alpha.1) (2024-12-05) + + +### Bug Fixes + +* added user pool client codegen ([29a7b5e](https://github.com/aws-amplify/amplify-cli/commit/29a7b5eed227b1fa3e5df670cd527477fe5df321)) +* **migrate:** rename generate-templates to execute command ([9e383cd](https://github.com/aws-amplify/amplify-cli/commit/9e383cd8bd9e14ea41322cb0ec5c4206d78d5a95)) + + +### Features + +* **migrate-template-gen:** retrieve oauth values for gen1 stack update ([3604b3e](https://github.com/aws-amplify/amplify-cli/commit/3604b3e86c01b300dd4d3480e900646875bba0f7)) + + + + + +# [0.1.0-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-gen2-migrations-alpha.0...@aws-amplify/migrate@0.1.0-alpha.0) (2024-11-21) + + +### Bug Fixes + +* import auth validation condition ([a76b71b](https://github.com/aws-amplify/amplify-cli/commit/a76b71bdf8eca2bc10a42bd4d90cbea1971141ed)) +* include only required userAttributes and generate identityPoolName in backend file ([76f1bf8](https://github.com/aws-amplify/amplify-cli/commit/76f1bf8bdbc9135bf0f9c983fd2f5448a169af42)) +* invert isImported condition ([2739aec](https://github.com/aws-amplify/amplify-cli/commit/2739aec0dd923537d8bf704bb63944f4756cc2c9)) +* remove duplicate code and .amplify dir ([822bc58](https://github.com/aws-amplify/amplify-cli/commit/822bc5844aa59f22068b4dcb6b09766a5de3ad52)) +* updated storage codegen to include encryption and removal policy ([94299ce](https://github.com/aws-amplify/amplify-cli/commit/94299ced6bd550675ecd87d9087fbca190cce740)) + + +### Features + +* ref auth codegen ([d6b1f28](https://github.com/aws-amplify/amplify-cli/commit/d6b1f288299c03d8809ccb3bcf8b74129c850e56)) + + + + + +# [0.1.0-gen2-migrations-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-gen2-migration-test-alpha.0...@aws-amplify/migrate@0.1.0-gen2-migrations-alpha.0) (2024-10-10) + + +### Bug Fixes + +* add usage data metrics for codegen ([ffc8041](https://github.com/aws-amplify/amplify-cli/commit/ffc8041041c6d1b66589c537e93f05a7453e5bc9)) +* **migrate:** package and lock files ([efec79b](https://github.com/aws-amplify/amplify-cli/commit/efec79b285bbf5291d1223a1ff0efa448594dafc)) +* **migrate:** rename index to command handler ([a479625](https://github.com/aws-amplify/amplify-cli/commit/a479625b705a9b26e30cb58aeca9cdc9c285642d)) +* **migrate:** update api md for generate templates ([4b4ccaa](https://github.com/aws-amplify/amplify-cli/commit/4b4ccaa560b4e4af1c35115acfb21f1a5cab4b9a)) + + +### Features + +* **migrate-template-gen:** add readme generator ([ebd02ef](https://github.com/aws-amplify/amplify-cli/commit/ebd02efb22f187c163db694f4eabd584a43d9873)) +* **migrate:** add generate templates command ([54b918b](https://github.com/aws-amplify/amplify-cli/commit/54b918b0c97da846baf9f1d715253299fe598930)) + + + + + +# [0.1.0-gen2-migration-test-alpha.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-gen2-migration-test.0...@aws-amplify/migrate@0.1.0-gen2-migration-test-alpha.0) (2024-09-26) + + +### Bug Fixes + +* check for empty objects ([c3f3a58](https://github.com/aws-amplify/amplify-cli/commit/c3f3a58ec1095b7051e701aa4f9e94ce0e45513a)) + + +### Features + +* add error for unsupported categories ([a22772d](https://github.com/aws-amplify/amplify-cli/commit/a22772d54c65ff59dffd5721e17ec4501c16d759)) +* unsupported categories codegen ([94552fd](https://github.com/aws-amplify/amplify-cli/commit/94552fdeaca3ffdede0182adbef9a37885bff621)) +* unsupported categories codegen ([1e8d175](https://github.com/aws-amplify/amplify-cli/commit/1e8d17585157a460ae8cf1f53546b270893e2b99)) + + + + + +# [0.1.0-gen2-migration-test.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/migrate@0.1.0-gen2-migrations-test.0...@aws-amplify/migrate@0.1.0-gen2-migration-test.0) (2024-09-23) + + +### Bug Fixes + +* **migrate:** add README ([51b0d66](https://github.com/aws-amplify/amplify-cli/commit/51b0d666292ae766b7b36ec3f171cb0f281674fc)) + + + + + +# 0.1.0-gen2-migrations-test.0 (2024-09-23) + + +### Bug Fixes + +* add attribute mapping for external providers ([4f4d9fd](https://github.com/aws-amplify/amplify-cli/commit/4f4d9fd261eefbaca6bd3a563b03e59573869e91)) +* add relevant removed code due to incorrect merge ([fe1ab64](https://github.com/aws-amplify/amplify-cli/commit/fe1ab6430a668fb55e280552cb358ae97503d002)) +* bugfixes for data codegen ([#13880](https://github.com/aws-amplify/amplify-cli/issues/13880)) ([263cd85](https://github.com/aws-amplify/amplify-cli/commit/263cd85da1acb689e647db42fe0bf176da036cb5)) +* correct package versions; remove unused import ([32b3382](https://github.com/aws-amplify/amplify-cli/commit/32b338286bef118f139ba0d0d98a9d45f23920fb)) +* correct package versions; remove unused import ([2855e28](https://github.com/aws-amplify/amplify-cli/commit/2855e28744bc0d319ff85d7a7a1a36d5fbdad253)) +* data definition fetcher handle undefined data stack ([#13886](https://github.com/aws-amplify/amplify-cli/issues/13886)) ([3a2549c](https://github.com/aws-amplify/amplify-cli/commit/3a2549cae564fa1291f44d64145c46d9df733fc3)) +* extract api ([6f4c58b](https://github.com/aws-amplify/amplify-cli/commit/6f4c58b947fa3be4c2c7c200484fa46b6823bb30)) +* linting error fix ([4244c77](https://github.com/aws-amplify/amplify-cli/commit/4244c77eb2141a9837de26287a6739d53701b79d)) +* make gen2 migration packages public ([a7832cb](https://github.com/aws-amplify/amplify-cli/commit/a7832cb622cabf3eec3f770393477256117ea47d)) +* **migrate:** add test-ci cmd ([c765b5e](https://github.com/aws-amplify/amplify-cli/commit/c765b5e0cfa8d0f8ac9bdd77c4eb3a261e0a933d)) +* **migrate:** convert to gen2 app ([abeb9c9](https://github.com/aws-amplify/amplify-cli/commit/abeb9c9863c6aa78dde0f5b10228537f1038c9b1)) +* **migrate:** prettier changes ([0fc0324](https://github.com/aws-amplify/amplify-cli/commit/0fc03241cc0095a45b1dc59f9102cdf3989daca7)) +* **migrate:** remove ci flag ([875f298](https://github.com/aws-amplify/amplify-cli/commit/875f298014e02d1d4feb544bb097a7ae2aa991d8)) +* **migrate:** remove commented code ([adfa586](https://github.com/aws-amplify/amplify-cli/commit/adfa586a431b14253b2515ce404bb388879a814b)) +* **migrate:** remove explicit js extension ([de079b9](https://github.com/aws-amplify/amplify-cli/commit/de079b9295484a9c944ade6ee9845b2c46fc1b5c)) +* **migrate:** remove unused code ([154dc60](https://github.com/aws-amplify/amplify-cli/commit/154dc6081b79109fccd9b365dbff7d563f3a427d)) +* **migrate:** update api doc ([9616366](https://github.com/aws-amplify/amplify-cli/commit/9616366930d2ffb9d7f8c08f491e9fdb5ec321fa)) +* **migrate:** update command names ([ef72979](https://github.com/aws-amplify/amplify-cli/commit/ef7297949d697c9c53f330745558246538f1343c)) +* **migrate:** use jest ([ee3063a](https://github.com/aws-amplify/amplify-cli/commit/ee3063ac3a9d4947851675e3c88bd7239031ed0e)) +* remove unused vars ([ca3de21](https://github.com/aws-amplify/amplify-cli/commit/ca3de21413a7860939c9c07b022d361bf0f99de7)) +* resolve failing test error ([c28e4f9](https://github.com/aws-amplify/amplify-cli/commit/c28e4f9418d6f6b9139b5c0907c2b76f723d7311)) +* resolve incorrect mfaconifg option ([5f1dd79](https://github.com/aws-amplify/amplify-cli/commit/5f1dd79bbebab1616a5752524d2ecb0ec255fd1a)) +* resolve test errors ([6e72ab4](https://github.com/aws-amplify/amplify-cli/commit/6e72ab4b3db6cfb52dc72fbea2651874402c81ba)) +* resolve workflow errors ([b2e96ea](https://github.com/aws-amplify/amplify-cli/commit/b2e96ea522810edcd4acc69a0b1fe2dc203edba7)) +* resolve workflow errors ([1d5be0a](https://github.com/aws-amplify/amplify-cli/commit/1d5be0a175f1053a6302dd2c1c7032fa75356f83)) + + +### Features + +* add comments to gen1 triggers ([#13866](https://github.com/aws-amplify/amplify-cli/issues/13866)) ([2ec9470](https://github.com/aws-amplify/amplify-cli/commit/2ec947084a89bb000f2b34cc2662121e8cf04fb6)) +* added an assert statement for meta file ([1945b71](https://github.com/aws-amplify/amplify-cli/commit/1945b71cb9c8ddf2cb652b2a87260ed1f643067d)) +* added functions auth ([acf5249](https://github.com/aws-amplify/amplify-cli/commit/acf52491cb3454d29b63d80e2038489ab2a82592)) +* added functions codegen ([8d6afa4](https://github.com/aws-amplify/amplify-cli/commit/8d6afa487e560db04692b8b815680d00e26924f9)) +* added functions codegen ([1bdabc7](https://github.com/aws-amplify/amplify-cli/commit/1bdabc76ad20206dd2711997c8059248c5877a9f)) +* bucket versioning override codegen ([c14156d](https://github.com/aws-amplify/amplify-cli/commit/c14156d4fed0514b0bf7ed6f885bac0419f3dcb2)) +* **cli:** initial migration merge ([f803827](https://github.com/aws-amplify/amplify-cli/commit/f8038278b95d321aef4ff75b1bd5a604815fc821)) +* **cli:** initial migration merge ([#13856](https://github.com/aws-amplify/amplify-cli/issues/13856)) ([ebe5cd0](https://github.com/aws-amplify/amplify-cli/commit/ebe5cd046cfb18c38ffdce17610ed3a133cc9d44)) +* functions codegeb ([ba3babf](https://github.com/aws-amplify/amplify-cli/commit/ba3babfb1403e8f740e1cfbf795707cdd085612f)) +* functions codegen ([50e91e2](https://github.com/aws-amplify/amplify-cli/commit/50e91e22fc97d4c8cee80dae17ab4b6976cccd40)) +* **migrate:** make as an independent executable ([0aeffb9](https://github.com/aws-amplify/amplify-cli/commit/0aeffb96b9fad75549d76d19778725eb522ad64e)) +* oidc/saml external providers codegen ([f248955](https://github.com/aws-amplify/amplify-cli/commit/f2489550925e2f90a53a7d0f833d53571a546ae1)) +* signup user attributes/groups auth codegen ([bacb17b](https://github.com/aws-amplify/amplify-cli/commit/bacb17b29f3bd55ac9d28b55903d4091a5786b15)) +* social auth codegen ([96cc8d5](https://github.com/aws-amplify/amplify-cli/commit/96cc8d580b39ba80745fd235bd00f2b724962adc)) +* storage codegen ([6ccb0ef](https://github.com/aws-amplify/amplify-cli/commit/6ccb0ef8db64b079f15ed7f943a8ac4b27a42211)) +* storage codegen ([9e45af9](https://github.com/aws-amplify/amplify-cli/commit/9e45af9c881572ce67d5bad7e05e057609c80b00)) +* storage triggers ([#13869](https://github.com/aws-amplify/amplify-cli/issues/13869)) ([3847399](https://github.com/aws-amplify/amplify-cli/commit/38473994e563cd90452ecc50639ea056bb8dd039)) +* unauthenticated logins codegen ([2d0b700](https://github.com/aws-amplify/amplify-cli/commit/2d0b700f099ceb36b70ab0745a562bcdd5f5ce4b)) +* update functions codegen ([411511d](https://github.com/aws-amplify/amplify-cli/commit/411511d463ba1cccabcf179319eddff06f535c51)) +* update functions codegen ([1ef8938](https://github.com/aws-amplify/amplify-cli/commit/1ef89380028856e39cfcb2b55e8fd1bd7f6e41ed)) +* updated functions codegen ([4ac9324](https://github.com/aws-amplify/amplify-cli/commit/4ac932478633274e87524aea9eb9f48d3640d36c)) +* updated secret code ([f54457b](https://github.com/aws-amplify/amplify-cli/commit/f54457b8280e4736ea84786f5879206d7eeed571)) diff --git a/packages/amplify-migration/README.md b/packages/amplify-migration/README.md new file mode 100644 index 00000000000..ef65bef1905 --- /dev/null +++ b/packages/amplify-migration/README.md @@ -0,0 +1,16 @@ +## Usage + +### Gen2 Codegen + +In Gen1 root directory run the following to generate Gen2 code based on Gen1 configuration: + +`npx @aws-amplify/migrate to-gen-2 prepare` + +Once this command runs successfully, the Gen1 project is converted to Gen2 with `amplify` directory containing Gen2 code and `.amplify/migration/amplify` containing Gen1 configuration as a backup. + +For executing the migration of resources from Gen1 to Gen2, run the following command: + +`npx @aws-amplify/migrate to-gen-2 execute --from --to ` + +For moving the resources back from Gen2 to Gen1, run the following command: +`npx @aws-amplify/migrate to-gen-2 revert --from --to ` diff --git a/packages/amplify-migration/amplify-plugin.json b/packages/amplify-migration/amplify-plugin.json new file mode 100644 index 00000000000..523e0e5f862 --- /dev/null +++ b/packages/amplify-migration/amplify-plugin.json @@ -0,0 +1,5 @@ +{ + "name": "gen2-migrate", + "type": "util", + "commands": ["migrate", "help"] +} diff --git a/packages/amplify-migration/package.json b/packages/amplify-migration/package.json new file mode 100644 index 00000000000..2db7f412e2b --- /dev/null +++ b/packages/amplify-migration/package.json @@ -0,0 +1,74 @@ +{ + "name": "@aws-amplify/migrate", + "version": "0.1.0-next-11.0", + "type": "commonjs", + "bin": "lib/migrate.js", + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsc", + "test": "jest --logHeapUsage", + "test:watch": "jest --watch", + "extract-api": "ts-node ../../scripts/extract-api.ts" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.{ts,tsx,js,jsx}", + "!src/__tests__/" + ], + "transform": { + "^.+\\.tsx?$": [ + "ts-jest" + ] + }, + "testRegex": "(/src/__tests__/.*|(\\.|/)test)\\.tsx?$", + "coveragePathIgnorePatterns": [ + "/node_modules/", + "/templates/" + ], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "collectCoverage": true + }, + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "jest": "^29.7.0" + }, + "dependencies": { + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-gen1-codegen-auth-adapter": "0.1.0-next-9.0", + "@aws-amplify/amplify-gen1-codegen-function-adapter": "0.1.0-next-9.0", + "@aws-amplify/amplify-gen1-codegen-storage-adapter": "0.1.0-next-9.0", + "@aws-amplify/amplify-gen2-codegen": "0.1.0-next-9.0", + "@aws-amplify/cli-internal": "13.0.2-next-11.0", + "@aws-amplify/migrate-template-gen": "0.1.0-next-11.0", + "@aws-sdk/client-amplify": "^3.592.0", + "@aws-sdk/client-amplifybackend": "^3.592.0", + "@aws-sdk/client-cloudformation": "^3.592.0", + "@aws-sdk/client-cloudwatch-events": "^3.592.0", + "@aws-sdk/client-cognito-identity": "^3.592.0", + "@aws-sdk/client-cognito-identity-provider": "^3.592.0", + "@aws-sdk/client-lambda": "^3.637.0", + "@aws-sdk/client-s3": "^3.592.0", + "@aws-sdk/client-ssm": "^3.592.0", + "@aws-sdk/client-sts": "^3.658.1", + "@types/node": "^20.14.2", + "@types/unzipper": "^0.10.9", + "glob": "^7.2.0", + "kleur": "^4.1.5", + "ora": "^4.0.3", + "typescript": "^5.4.5", + "unzipper": "^0.12.1", + "uuid": "^8.3.2", + "yargs": "^17.7.2" + } +} diff --git a/packages/amplify-migration/src/amplify_stack_parser.ts b/packages/amplify-migration/src/amplify_stack_parser.ts new file mode 100644 index 00000000000..d9ad3e65f0a --- /dev/null +++ b/packages/amplify-migration/src/amplify_stack_parser.ts @@ -0,0 +1,87 @@ +import assert from 'node:assert'; +import { + CloudFormationClient, + DescribeStackResourcesCommand, + DescribeStacksCommand, + Stack, + StackResource, +} from '@aws-sdk/client-cloudformation'; + +export type AmplifyStackTypes = 'authStack' | 'dataStack' | 'storageStack' | 'rootStack'; + +export type AmplifyStacks = Partial>; +export class AmplifyStackParser { + constructor(private cfnClient: CloudFormationClient) {} + private static CFN_STACK_RESOURCE_TYPE = 'AWS::CloudFormation::Stack'; + private getStackResources = async (stackName: string): Promise => { + const { StackResources: stackResources } = await this.cfnClient.send( + new DescribeStackResourcesCommand({ + StackName: stackName, + }), + ); + assert(stackResources); + return stackResources; + }; + getAllStackResources = async (stackName: string): Promise => { + const resources: StackResource[] = []; + const stackQueue = [stackName]; + while (stackQueue.length) { + const currentStackName = stackQueue.shift(); + assert(currentStackName); + const stackResources = await this.getStackResources(currentStackName); + stackQueue.push( + ...stackResources + .filter((r) => r.ResourceType === AmplifyStackParser.CFN_STACK_RESOURCE_TYPE) + .map((r) => { + assert(r.PhysicalResourceId, 'Resource does not have a physical resource id'); + return r.PhysicalResourceId; + }), + ); + resources.push(...stackResources.filter((r) => r.ResourceType !== AmplifyStackParser.CFN_STACK_RESOURCE_TYPE)); + } + return resources; + }; + getResourcesByLogicalId = (resources: StackResource[]) => { + return resources.reduce((acc, curr) => { + if (curr.LogicalResourceId) { + acc[curr.LogicalResourceId] = curr; + } + return acc; + }, {} as Record); + }; + + private describeStack = (stackId: string) => this.cfnClient.send(new DescribeStacksCommand({ StackName: stackId })); + getAmplifyStacks = async (rootStackName: string): Promise => { + const rootStackResponse = await this.describeStack(rootStackName); + const stackResources = await this.getStackResources(rootStackName); + const stackIds = stackResources + .filter((stack) => stack.ResourceType === 'AWS::CloudFormation::Stack') + .reduce((prev, curr) => { + if (curr.PhysicalResourceId) { + if (curr.LogicalResourceId?.startsWith('api')) { + prev.dataStack = curr.PhysicalResourceId; + } + if (curr.LogicalResourceId?.startsWith('auth')) { + prev.authStack = curr.PhysicalResourceId; + } + if (curr.LogicalResourceId?.startsWith('storage')) { + prev.storageStack = curr.PhysicalResourceId; + } + } + return prev; + }, {} as Record); + + const [dataStackResponse, authStackResponse, storageStackResponse] = await Promise.all([ + this.describeStack(stackIds.dataStack), + this.describeStack(stackIds.authStack), + this.describeStack(stackIds.storageStack), + ]); + + return { + rootStack: rootStackResponse?.Stacks?.find(({ StackId }) => StackId === stackIds.rootStack), + dataStack: dataStackResponse?.Stacks?.find(({ StackId }) => StackId === stackIds.dataStack), + authStack: authStackResponse?.Stacks?.find(({ StackId }) => StackId === stackIds.authStack), + storageStack: storageStackResponse?.Stacks?.find(({ StackId }) => StackId === stackIds.storageStack), + }; + }; +} diff --git a/packages/amplify-migration/src/analytics.ts b/packages/amplify-migration/src/analytics.ts new file mode 100644 index 00000000000..9d611bc6398 --- /dev/null +++ b/packages/amplify-migration/src/analytics.ts @@ -0,0 +1,9 @@ +export type AnalyticsDimensions = Record; + +export interface Analytics { + logEvent(eventName: string, dimensions?: AnalyticsDimensions): Promise; +} +export class AppAnalytics implements Analytics { + constructor(private appId: string) {} + logEvent = async () => Promise.resolve(); +} diff --git a/packages/amplify-migration/src/app_auth_definition_fetcher.test.ts b/packages/amplify-migration/src/app_auth_definition_fetcher.test.ts new file mode 100644 index 00000000000..13b04ca5097 --- /dev/null +++ b/packages/amplify-migration/src/app_auth_definition_fetcher.test.ts @@ -0,0 +1,267 @@ +import { CognitoIdentityProviderClient, DescribeUserPoolCommand, ListGroupsCommand } from '@aws-sdk/client-cognito-identity-provider'; +import { CognitoIdentityClient, GetIdentityPoolRolesCommand, ListIdentityPoolsCommand } from '@aws-sdk/client-cognito-identity'; +import { CloudFormationClient, DescribeStackResourcesCommand } from '@aws-sdk/client-cloudformation'; +import { AmplifyClient, GetBackendEnvironmentCommand } from '@aws-sdk/client-amplify'; +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; + +import { AmplifyStackParser } from './amplify_stack_parser'; +import { BackendEnvironmentResolver } from './backend_environment_selector'; +import { AppAuthDefinitionFetcher } from './app_auth_definition_fetcher'; +import { BackendDownloader } from './backend_downloader'; + +const mockUserPoolName = 'mockUserPoolName'; +const mockUserPoolID = 'UserPoolId'; +const mockIdentityPoolName = 'mockIdentityPoolName'; +const mockIdentityPoolId = 'IdentityPoolId'; +const mockAppClientId = 'AppClientID'; +const mockAppClientIdWeb = 'AppClientIDWeb'; +const mockAuthenticatedRoleARN = 'authenticated'; +const mockUnauthenticatedRoleARN = 'unauthenticated'; +const mockCognitoIdentityProviderClientSendFn = jest.fn(); + +const mockImportedAuthMeta = JSON.stringify({ + auth: { + importedAuth: { + service: 'Cognito', + serviceType: 'imported', + output: { + UserPoolId: mockUserPoolID, + UserPoolName: mockUserPoolName, + AppClientID: mockAppClientId, + AppClientIDWeb: mockAppClientIdWeb, + IdentityPoolId: mockIdentityPoolId, + IdentityPoolName: mockIdentityPoolName, + }, + }, + }, +}); + +const mockReadFile = jest.fn().mockResolvedValue(mockImportedAuthMeta); + +jest.mock('unzipper', () => ({ + Open: { + file: jest.fn().mockResolvedValue({ + extract: jest.fn().mockResolvedValue(undefined), + }), + }, +})); + +jest.mock('node:fs/promises', () => { + return { + mkdtemp: jest.fn().mockResolvedValue('tmp'), + writeFile: jest.fn().mockResolvedValue(undefined), + access: jest.fn().mockResolvedValue(true), + readFile: () => mockReadFile(), + }; +}); +jest.mock('@aws-sdk/client-cognito-identity-provider', () => { + return { + ...jest.requireActual('@aws-sdk/client-cognito-identity-provider'), + CognitoIdentityProviderClient: function () { + return { + send: mockCognitoIdentityProviderClientSendFn.mockImplementation((command) => { + if (command instanceof DescribeUserPoolCommand) { + return Promise.resolve({ + UserPoolClient: { + ClientId: 'ClientId', + UserPoolId: 'UserPoolId', + ClientName: 'ClientName', + }, + }); + } else if (command instanceof ListGroupsCommand) { + return Promise.resolve({ + Groups: [ + { + GroupName: 'Admin', + RoleArn: 'RoleArn', + Description: 'Description', + Precedence: 1, + LastModifiedDate: 'LastModifiedDate', + CreationDate: 'CreationDate', + }, + ], + }); + } + return undefined; + }), + }; + }, + }; +}); + +jest.mock('@aws-sdk/client-cognito-identity', () => { + return { + ...jest.requireActual('@aws-sdk/client-cognito-identity'), + CognitoIdentityClient: function () { + return { + send: jest.fn().mockImplementation((command) => { + if (command instanceof ListIdentityPoolsCommand) { + return Promise.resolve({ + IdentityPools: [ + { + IdentityPoolId: 'IdentityPoolId', + IdentityPoolName: 'IdentityPoolName', + }, + ], + }); + } else if (command instanceof GetIdentityPoolRolesCommand) { + return Promise.resolve({ + Roles: { + authenticated: mockAuthenticatedRoleARN, + unauthenticated: mockUnauthenticatedRoleARN, + }, + }); + } + return Promise.resolve(); + }), + }; + }, + }; +}); + +jest.mock('@aws-sdk/client-amplify', () => { + return { + ...jest.requireActual('@aws-sdk/client-amplify'), + AmplifyClient: function () { + return { + send: jest.fn().mockImplementation((command) => { + if (command instanceof GetBackendEnvironmentCommand) { + return Promise.resolve({ + backendEnvironment: { + environmentName: 'dev', + deploymentArtifacts: 's3://deploymentArtifacts', + stackName: 'stackName', + }, + }); + } + return Promise.resolve(); + }), + }; + }, + }; +}); + +jest.mock('@aws-sdk/client-s3', () => { + return { + ...jest.requireActual('@aws-sdk/client-s3'), + S3Client: function () { + return { + send: jest.fn().mockImplementation((command) => { + if (command instanceof GetObjectCommand) { + return Promise.resolve({ + Body: mockImportedAuthMeta, + }); + } + return Promise.resolve(); + }), + }; + }, + }; +}); + +jest.mock('@aws-sdk/client-cloudformation', () => { + return { + ...jest.requireActual('@aws-sdk/client-cloudformation'), + CloudFormationClient: function () { + return { + send: jest.fn().mockImplementation((command) => { + if (command instanceof DescribeStackResourcesCommand) { + return Promise.resolve({ + StackResources: [], + }); + } + return Promise.resolve(); + }), + }; + }, + }; +}); + +jest.mock('@aws-amplify/cli-internal/lib/extensions/amplify-helpers/get-env-info', () => { + return { + getEnvInfo: jest.fn().mockReturnValue({ + envName: 'dev', + }), + }; +}); + +const cognitoIdentityProviderClient = new CognitoIdentityProviderClient(); +const cognitoIdentityClient = new CognitoIdentityClient(); +const cloudFormationClient = new CloudFormationClient(); +const amplifyStackParser = new AmplifyStackParser(cloudFormationClient); +const amplifyClient = new AmplifyClient(); +const s3Client = new S3Client(); +const appId = 'appId'; +const backendEnvironmentResolver = new BackendEnvironmentResolver(appId, amplifyClient); +const ccbFetcher = new BackendDownloader(s3Client); + +describe('Auth definition Fetcher tests', () => { + const appAuthDefinitionFetcher = new AppAuthDefinitionFetcher( + cognitoIdentityClient, + cognitoIdentityProviderClient, + amplifyStackParser, + backendEnvironmentResolver, + () => Promise.resolve({}), + ccbFetcher, + ); + it('should not fetch imported auth definitions when not present', async () => { + // arrange + mockReadFile.mockResolvedValueOnce( + JSON.stringify({ + api: {}, + auth: { + nonImportedResource: { + service: 'Cognito', + output: {}, + }, + }, + }), + ); + // act + assert + await expect(appAuthDefinitionFetcher.getDefinition()).resolves.toEqual(undefined); + }); + it('should fetch imported auth definitions', async () => { + await expect(appAuthDefinitionFetcher.getDefinition()).resolves.toEqual({ + referenceAuth: { + groups: { + Admin: 'RoleArn', + }, + identityPoolId: mockIdentityPoolId, + authRoleArn: mockAuthenticatedRoleARN, + unauthRoleArn: mockUnauthenticatedRoleARN, + userPoolClientId: mockAppClientIdWeb, + userPoolId: mockUserPoolID, + }, + }); + }); + + it('should not fetch imported auth definitions if there is no related cognito resource information', async () => { + mockReadFile.mockResolvedValueOnce( + JSON.stringify({ + auth: { + importedAuth: { + service: 'Cognito', + serviceType: 'imported', + output: {}, + }, + }, + }), + ); + await expect(appAuthDefinitionFetcher.getDefinition()).rejects.toEqual(new Error('No user pool or identity pool found for import.')); + }); + + it('should throw error with invalid auth configuration structure for imported auth', async () => { + mockReadFile.mockResolvedValueOnce( + JSON.stringify({ + auth: { + importedAuth: { + service: 'Cognito', + serviceType: 'imported', + output: 'invalid output', + }, + }, + }), + ); + await expect(appAuthDefinitionFetcher.getDefinition()).rejects.toEqual(new Error('Invalid auth configuration structure')); + }); +}); diff --git a/packages/amplify-migration/src/app_auth_definition_fetcher.ts b/packages/amplify-migration/src/app_auth_definition_fetcher.ts new file mode 100644 index 00000000000..a61a51190a3 --- /dev/null +++ b/packages/amplify-migration/src/app_auth_definition_fetcher.ts @@ -0,0 +1,234 @@ +import assert from 'node:assert'; +import { AuthDefinition } from '@aws-amplify/amplify-gen2-codegen'; +export type AuthTriggerConnectionsFetcher = () => Promise> | undefined>; +import { AmplifyStackParser } from './amplify_stack_parser'; +import { BackendEnvironmentResolver } from './backend_environment_selector'; +import { + CognitoIdentityProviderClient, + DescribeUserPoolCommand, + DescribeUserPoolClientCommand, + ListIdentityProvidersCommand, + LambdaConfigType, + ListGroupsCommand, + IdentityProviderType, + DescribeIdentityProviderCommand, + GetUserPoolMfaConfigCommand, +} from '@aws-sdk/client-cognito-identity-provider'; +import { CognitoIdentityClient, DescribeIdentityPoolCommand, GetIdentityPoolRolesCommand } from '@aws-sdk/client-cognito-identity'; +import { getAuthDefinition } from '@aws-amplify/amplify-gen1-codegen-auth-adapter'; +import { fileOrDirectoryExists } from './directory_exists'; +import { BackendDownloader } from './backend_downloader.js'; +import path from 'node:path'; +import fs from 'node:fs/promises'; + +export interface AppAuthDefinitionFetcher { + getDefinition(): Promise; +} + +interface AuthOutput { + output: { + UserPoolId?: string; + AppClientIDWeb?: string; + IdentityPoolId?: string; + }; +} + +const isAuthOutput = (value: unknown): value is AuthOutput => { + return typeof value === 'object' && value !== null && 'output' in value && typeof value.output === 'object'; +}; + +export class AppAuthDefinitionFetcher { + constructor( + private cognitoIdentityPoolClient: CognitoIdentityClient, + private cognitoIdentityProviderClient: CognitoIdentityProviderClient, + private stackParser: AmplifyStackParser, + private backendEnvironmentResolver: BackendEnvironmentResolver, + private getAuthTriggerConnections: AuthTriggerConnectionsFetcher, + private ccbFetcher: BackendDownloader, + ) {} + + private readJsonFile = async (filePath: string) => { + const contents = await fs.readFile(filePath, { encoding: 'utf8' }); + return JSON.parse(contents); + }; + + private getAuthCategory = async (): Promise | undefined> => { + const backendEnvironment = await this.backendEnvironmentResolver.selectBackendEnvironment(); + if (!backendEnvironment?.deploymentArtifacts) return undefined; + const currentCloudBackendDirectory = await this.ccbFetcher.getCurrentCloudBackend(backendEnvironment.deploymentArtifacts); + const amplifyMetaPath = path.join(currentCloudBackendDirectory, 'amplify-meta.json'); + + if (!(await fileOrDirectoryExists(amplifyMetaPath))) { + throw new Error('Could not find amplify-meta.json'); + } + + const amplifyMeta = (await this.readJsonFile(amplifyMetaPath)) ?? {}; + const authCategory = 'auth' in amplifyMeta && Object.keys(amplifyMeta.auth).length > 0; + + if (authCategory) { + return amplifyMeta.auth; + } else { + return undefined; + } + }; + + private getReferenceAuth = async (authCategory: Record) => { + const isImported = Object.entries(authCategory).some( + ([, value]) => typeof value === 'object' && value !== null && 'serviceType' in value && value.serviceType === 'imported', + ); + if (!isImported) { + return undefined; + } + + const firstAuth = Object.values(authCategory)[0]; + if (!isAuthOutput(firstAuth)) { + throw new Error('Invalid auth configuration structure'); + } + + const { UserPoolId: userPoolId, AppClientIDWeb: userPoolClientId, IdentityPoolId: identityPoolId } = firstAuth.output; + + if (!userPoolId && !userPoolClientId && !identityPoolId) { + throw new Error('No user pool or identity pool found for import.'); + } + + let authRoleArn: string | undefined; + let unauthRoleArn: string | undefined; + let groups: Record | undefined; + + if (identityPoolId) { + const { Roles } = await this.cognitoIdentityPoolClient.send( + new GetIdentityPoolRolesCommand({ + IdentityPoolId: identityPoolId, + }), + ); + if (Roles) { + authRoleArn = Roles.authenticated; + unauthRoleArn = Roles.unauthenticated; + } + } + + if (userPoolId) { + const { Groups } = await this.cognitoIdentityProviderClient.send( + new ListGroupsCommand({ + UserPoolId: userPoolId, + }), + ); + + if (Groups && Groups.length > 0) { + groups = Groups.reduce((acc: Record, { GroupName, RoleArn }) => { + assert(GroupName); + assert(RoleArn); + return { + ...acc, + [GroupName]: RoleArn, + }; + }, {}); + } + } + + return { + userPoolId, + userPoolClientId, + identityPoolId, + unauthRoleArn, + authRoleArn, + groups, + }; + }; + + getDefinition = async (): Promise => { + const authCategory = await this.getAuthCategory(); + if (!authCategory) { + return undefined; + } + const referenceAuth = await this.getReferenceAuth(authCategory); + if (referenceAuth) { + return { + referenceAuth, + }; + } + + const backendEnvironment = await this.backendEnvironmentResolver.selectBackendEnvironment(); + assert(backendEnvironment?.stackName); + const stackResources = await this.stackParser.getAllStackResources(backendEnvironment.stackName); + const resourcesByLogicalId = this.stackParser.getResourcesByLogicalId(stackResources); + + if (!resourcesByLogicalId['UserPool']) { + return undefined; + } + + const { UserPool: userPool } = await this.cognitoIdentityProviderClient.send( + new DescribeUserPoolCommand({ + UserPoolId: resourcesByLogicalId['UserPool'].PhysicalResourceId, + }), + ); + + const { MfaConfiguration: mfaConfig, SoftwareTokenMfaConfiguration: totpConfig } = await this.cognitoIdentityProviderClient.send( + new GetUserPoolMfaConfigCommand({ + UserPoolId: resourcesByLogicalId['UserPool'].PhysicalResourceId, + }), + ); + + const { UserPoolClient: webClient } = await this.cognitoIdentityProviderClient.send( + new DescribeUserPoolClientCommand({ + UserPoolId: resourcesByLogicalId['UserPool'].PhysicalResourceId, + ClientId: resourcesByLogicalId['UserPoolClientWeb'].PhysicalResourceId, + }), + ); + + const { UserPoolClient: userPoolClient } = await this.cognitoIdentityProviderClient.send( + new DescribeUserPoolClientCommand({ + UserPoolId: resourcesByLogicalId['UserPool'].PhysicalResourceId, + ClientId: resourcesByLogicalId['UserPoolClient'].PhysicalResourceId, + }), + ); + + const { Providers: identityProviders } = await this.cognitoIdentityProviderClient.send( + new ListIdentityProvidersCommand({ + UserPoolId: resourcesByLogicalId['UserPool'].PhysicalResourceId, + }), + ); + + const identityProvidersDetails: IdentityProviderType[] = []; + for (const provider of identityProviders || []) { + const { IdentityProvider: providerDetails } = await this.cognitoIdentityProviderClient.send( + new DescribeIdentityProviderCommand({ + UserPoolId: resourcesByLogicalId['UserPool'].PhysicalResourceId, + ProviderName: provider.ProviderName, + }), + ); + if (providerDetails) { + identityProvidersDetails.push(providerDetails); + } + } + + const { Groups: identityGroups } = await this.cognitoIdentityProviderClient.send( + new ListGroupsCommand({ + UserPoolId: resourcesByLogicalId['UserPool'].PhysicalResourceId, + }), + ); + + const { AllowUnauthenticatedIdentities: guestLogin, IdentityPoolName: identityPoolName } = await this.cognitoIdentityPoolClient.send( + new DescribeIdentityPoolCommand({ + IdentityPoolId: resourcesByLogicalId['IdentityPool'].PhysicalResourceId, + }), + ); + + const authTriggerConnections = await this.getAuthTriggerConnections(); + + assert(userPool, 'User pool not found'); + return getAuthDefinition({ + userPool, + identityPoolName, + identityProviders, + identityProvidersDetails, + identityGroups, + webClient, + authTriggerConnections, + guestLogin, + mfaConfig, + totpConfig, + userPoolClient, + }); + }; +} diff --git a/packages/amplify-migration/src/app_functions_definition_fetcher.ts b/packages/amplify-migration/src/app_functions_definition_fetcher.ts new file mode 100644 index 00000000000..2461f8cfa55 --- /dev/null +++ b/packages/amplify-migration/src/app_functions_definition_fetcher.ts @@ -0,0 +1,125 @@ +import assert from 'node:assert'; +import { FunctionDefinition } from '@aws-amplify/amplify-gen2-codegen'; +import { getFunctionDefinition } from '@aws-amplify/amplify-gen1-codegen-function-adapter'; +import { BackendEnvironmentResolver } from './backend_environment_selector'; +import { GetFunctionCommand, GetPolicyCommand, LambdaClient } from '@aws-sdk/client-lambda'; +import { DescribeRuleCommand, CloudWatchEventsClient } from '@aws-sdk/client-cloudwatch-events'; +import { StateManager } from '@aws-amplify/amplify-cli-core'; + +interface AuthConfig { + dependsOn?: Array<{ + category: string; + resourceName: string; + }>; + service: string; + [key: string]: unknown; +} + +export interface AppFunctionsDefinitionFetcher { + getDefinition(): Promise; +} + +export class AppFunctionsDefinitionFetcher { + constructor( + private lambdaClient: LambdaClient, + private cloudWatchEventsClient: CloudWatchEventsClient, + private backendEnvironmentResolver: BackendEnvironmentResolver, + private stateManager: StateManager, + ) {} + + getDefinition = async (): Promise => { + const backendEnvironment = await this.backendEnvironmentResolver.selectBackendEnvironment(); + assert(backendEnvironment?.stackName); + + const meta = this.stateManager.getMeta(); + const functions = meta?.function ?? {}; + + const auth = meta?.auth ?? {}; + const storageList = meta?.storage ?? {}; + + const functionCategoryMap = new Map(); + + const authValues: AuthConfig | undefined = Object.values(auth).find( + (resourceConfig: unknown) => + resourceConfig && typeof resourceConfig === 'object' && 'service' in resourceConfig && resourceConfig?.service === 'Cognito', + ) as AuthConfig; + + // auth triggers + if (auth && authValues && authValues.dependsOn) { + for (const env of authValues.dependsOn) { + if (env.category == 'function') { + functionCategoryMap.set(env.resourceName, 'auth'); + } + } + } + + // s3 storage trigger + Object.keys(storageList).forEach((storage) => { + const storageObj = storageList[storage]; + if (storageObj.dependsOn) { + for (const env of storageObj.dependsOn) { + if (env.category == 'function') { + functionCategoryMap.set(env.resourceName, 'storage'); + } + } + } + }); + + // dynamodb storage trigger + Object.keys(functions).forEach((func) => { + const funcObj = functions[func]; + if (funcObj.dependsOn) { + for (const env of funcObj.dependsOn) { + if (env.category == 'storage') { + functionCategoryMap.set(func, 'storage'); + } + } + } + }); + + const getFunctionPromises = Object.keys(functions).map((key) => { + return this.lambdaClient.send( + new GetFunctionCommand({ + FunctionName: meta.function[key].output.Name, + }), + ); + }); + + const functionConfigurations = (await Promise.all(getFunctionPromises)) + .map((functionResponse) => functionResponse.Configuration ?? null) + .filter((config): config is NonNullable => config !== null); + + // Fetch schedules for functions + const getFunctionSchedulePromises = Object.keys(functions).map(async (key) => { + const functionName = meta.function[key].output.Name; + // Fetch the Lambda policy to get the CloudWatch rule name + let ruleName: string | undefined; + try { + const policyResponse = await this.lambdaClient.send(new GetPolicyCommand({ FunctionName: functionName })); + const policy = JSON.parse(policyResponse.Policy ?? '{}'); + ruleName = policy.Statement?.find((statement: any) => statement.Condition?.ArnLike?.['AWS:SourceArn']?.includes('rule/')) + ?.Condition.ArnLike['AWS:SourceArn'].split('/') + .pop(); + } catch (error) { + return { functionName, scheduleExpression: undefined }; + } + + let scheduleExpression: string | undefined; + + if (ruleName) { + // Use DescribeRuleCommand to get the schedule expression + const ruleResponse = await this.cloudWatchEventsClient.send(new DescribeRuleCommand({ Name: ruleName })); + scheduleExpression = ruleResponse.ScheduleExpression; + } + + return { + functionName, + scheduleExpression, + }; + }); + + const functionSchedules = await Promise.all(getFunctionSchedulePromises); + + return getFunctionDefinition(functionConfigurations, functionSchedules, functionCategoryMap, meta); + }; +} diff --git a/packages/amplify-migration/src/app_storage_definition_fetcher.ts b/packages/amplify-migration/src/app_storage_definition_fetcher.ts new file mode 100644 index 00000000000..edcf1885cd5 --- /dev/null +++ b/packages/amplify-migration/src/app_storage_definition_fetcher.ts @@ -0,0 +1,135 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { getStorageDefinition } from '@aws-amplify/amplify-gen1-codegen-storage-adapter'; +import { BackendDownloader } from './backend_downloader.js'; +import { StorageRenderParameters, StorageTriggerEvent, Lambda, ServerSideEncryptionConfiguration } from '@aws-amplify/amplify-gen2-codegen'; +import { + GetBucketNotificationConfigurationCommand, + S3Client, + GetBucketNotificationConfigurationCommandOutput, + GetBucketAccelerateConfigurationCommand, + GetBucketVersioningCommand, + GetBucketEncryptionCommand, +} from '@aws-sdk/client-s3'; +import { BackendEnvironmentResolver } from './backend_environment_selector'; +import { fileOrDirectoryExists } from './directory_exists'; + +export interface AppStorageDefinitionFetcher { + getDefinition(): Promise | undefined>; +} + +interface StorageOutput { + service: string; + output: { + Name?: string; + BucketName?: string; + }; +} + +export class AppStorageDefinitionFetcher { + constructor( + private backendEnvironmentResolver: BackendEnvironmentResolver, + private ccbFetcher: BackendDownloader, + private s3Client: S3Client, + ) {} + private readJsonFile = async (filePath: string) => { + const contents = await fs.readFile(filePath, { encoding: 'utf8' }); + return JSON.parse(contents); + }; + private getFunctionPath = (functionName: string) => { + return path.join('amplify', 'backend', 'function', functionName, 'src'); + }; + private getStorageTriggers = ( + connections: GetBucketNotificationConfigurationCommandOutput, + ): Partial> => { + const triggers: Partial> = {}; + const lambdaFunctionConfigurations = connections.LambdaFunctionConfigurations || []; + for (const connection of lambdaFunctionConfigurations) { + const functionName = connection.LambdaFunctionArn ? connection.LambdaFunctionArn.split(':').pop()?.split('-')[0] : ''; + const event = connection.Events ? connection.Events[0] : ''; + + if (event.includes('ObjectCreated') && functionName) { + triggers['onUpload' as StorageTriggerEvent] = { source: this.getFunctionPath(functionName) } as Lambda; + } else if (event.includes('ObjectRemoved') && functionName) { + triggers['onDelete' as StorageTriggerEvent] = { source: this.getFunctionPath(functionName) } as Lambda; + } + } + + return triggers; + }; + + getDefinition = async (): Promise => { + const backendEnvironment = await this.backendEnvironmentResolver.selectBackendEnvironment(); + if (!backendEnvironment?.deploymentArtifacts) return undefined; + + const currentCloudBackendDirectory = await this.ccbFetcher.getCurrentCloudBackend(backendEnvironment.deploymentArtifacts); + + const amplifyMetaPath = path.join(currentCloudBackendDirectory, 'amplify-meta.json'); + + if (!(await fileOrDirectoryExists(amplifyMetaPath))) { + throw new Error('Could not find amplify-meta.json'); + } + + const amplifyMeta = (await this.readJsonFile(amplifyMetaPath)) ?? {}; + let storageOptions: StorageRenderParameters | undefined = undefined; + + if ('storage' in amplifyMeta && Object.keys(amplifyMeta.storage).length) { + for (const [storageName, storage] of Object.entries(amplifyMeta.storage)) { + const cliInputsPath = path.join(currentCloudBackendDirectory, 'storage', storageName, 'cli-inputs.json'); + + if (!(await fileOrDirectoryExists(cliInputsPath))) { + throw new Error(`Could not find cli-inputs.json for ${storageName}`); + } + + const cliInputs = await this.readJsonFile(cliInputsPath); + const storageOutput = storage as StorageOutput; + if (storageOutput.service === 'S3') { + const bucketName = storageOutput.output.BucketName; + if (!bucketName) throw new Error('Could not find bucket name'); + + const connections = await this.s3Client.send(new GetBucketNotificationConfigurationCommand({ Bucket: bucketName })); + const { Status: accelerateConfiguration } = await this.s3Client.send( + new GetBucketAccelerateConfigurationCommand({ Bucket: bucketName }), + ); + const { Status: versioningConfiguration } = await this.s3Client.send(new GetBucketVersioningCommand({ Bucket: bucketName })); + + const { ServerSideEncryptionConfiguration: serverSideEncryptionByDefault } = await this.s3Client.send( + new GetBucketEncryptionCommand({ Bucket: bucketName }), + ); + + const triggers = this.getStorageTriggers(connections); + + const storageDefinition = getStorageDefinition({ + cliInputs, + bucketName, + triggers, + }); + + if (!storageOptions) storageOptions = {}; + storageOptions.accessPatterns = storageDefinition.accessPatterns; + storageOptions.storageIdentifier = storageDefinition.storageIdentifier; + storageOptions.triggers = storageDefinition.triggers; + storageOptions.accelerateConfiguration = accelerateConfiguration; + storageOptions.versioningConfiguration = versioningConfiguration; + storageOptions.bucketName = bucketName; + + if (serverSideEncryptionByDefault && serverSideEncryptionByDefault.Rules && serverSideEncryptionByDefault.Rules[0]) { + const serverSideEncryptionConf: ServerSideEncryptionConfiguration = { + serverSideEncryptionByDefault: serverSideEncryptionByDefault.Rules[0].ApplyServerSideEncryptionByDefault!, + bucketKeyEnabled: serverSideEncryptionByDefault.Rules[0].BucketKeyEnabled!, + }; + + storageOptions.bucketEncryptionAlgorithm = serverSideEncryptionConf; + } + } else if (storageOutput.service === 'DynamoDB') { + const tableName = storageOutput.output.Name?.split('-')[0]; + if (!tableName) throw new Error('Could not find table name'); + + if (!storageOptions) storageOptions = {}; + storageOptions.dynamoDB = tableName; + } + } + } + return storageOptions; + }; +} diff --git a/packages/amplify-migration/src/backend_downloader.ts b/packages/amplify-migration/src/backend_downloader.ts new file mode 100644 index 00000000000..326b0c028bd --- /dev/null +++ b/packages/amplify-migration/src/backend_downloader.ts @@ -0,0 +1,46 @@ +import os from 'node:os'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import assert from 'node:assert'; +import { Stream } from 'node:stream'; + +import unzipper from 'unzipper'; +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; + +import { fileOrDirectoryExists } from './directory_exists'; + +export class BackendDownloader { + constructor(private s3Client: S3Client) {} + private static ccbDir: string | undefined; + + private static CURRENT_CLOUD_BACKEND = 'current-cloud-backend'; + private makeTempDirectory = async (): Promise => { + const tmpDir = os.tmpdir(); + const { sep } = path; + return await fs.mkdtemp(`${tmpDir}${sep}`); + }; + getCurrentCloudBackend = async (bucket: string): Promise => { + if (BackendDownloader.ccbDir && (await fileOrDirectoryExists(BackendDownloader.ccbDir))) { + return BackendDownloader.ccbDir; + } + const tmpDir = await this.makeTempDirectory(); + const ccbZippedFilename = `#${BackendDownloader.CURRENT_CLOUD_BACKEND}.zip`; + const ccbZipPath = path.join(tmpDir, ccbZippedFilename); + const response = await this.s3Client.send( + new GetObjectCommand({ + Key: ccbZippedFilename, + Bucket: bucket, + }), + ); + assert(response.Body, 'Body is empty'); + await fs.writeFile(ccbZipPath, response.Body as Stream); + assert(await fileOrDirectoryExists(ccbZipPath), `${ccbZipPath} does not exist.`); + const directory = await unzipper.Open.file(ccbZipPath); + await directory.extract({ + path: path.join(tmpDir, BackendDownloader.CURRENT_CLOUD_BACKEND), + }); + const ccbDir = path.join(tmpDir, BackendDownloader.CURRENT_CLOUD_BACKEND); + BackendDownloader.ccbDir = ccbDir; + return ccbDir; + }; +} diff --git a/packages/amplify-migration/src/backend_environment_selector.ts b/packages/amplify-migration/src/backend_environment_selector.ts new file mode 100644 index 00000000000..ae00e336dae --- /dev/null +++ b/packages/amplify-migration/src/backend_environment_selector.ts @@ -0,0 +1,34 @@ +import assert from 'node:assert'; +import { AmplifyClient, BackendEnvironment, GetBackendEnvironmentCommand, ListBackendEnvironmentsCommand } from '@aws-sdk/client-amplify'; +import { getEnvInfo } from '@aws-amplify/cli-internal/lib/extensions/amplify-helpers/get-env-info'; + +export class BackendEnvironmentResolver { + constructor(private appId: string, private amplifyClient: AmplifyClient) {} + private selectedEnvironment: BackendEnvironment | undefined; + selectBackendEnvironment = async (): Promise => { + if (this.selectedEnvironment) return this.selectedEnvironment; + const envInfo = getEnvInfo(); + assert(envInfo); + const { backendEnvironment } = await this.amplifyClient.send( + new GetBackendEnvironmentCommand({ + appId: this.appId, + environmentName: envInfo.envName, + }), + ); + assert(backendEnvironment, 'No backend environment found'); + this.selectedEnvironment = backendEnvironment; + return this.selectedEnvironment; + }; + + getAllBackendEnvironments = async (): Promise => { + const envInfo = getEnvInfo(); + assert(envInfo); + const { backendEnvironments } = await this.amplifyClient.send( + new ListBackendEnvironmentsCommand({ + appId: this.appId, + }), + ); + assert(backendEnvironments, 'No backend environments found'); + return backendEnvironments; + }; +} diff --git a/packages/amplify-migration/src/command-handler.test.ts b/packages/amplify-migration/src/command-handler.test.ts new file mode 100644 index 00000000000..e2f501a6dba --- /dev/null +++ b/packages/amplify-migration/src/command-handler.test.ts @@ -0,0 +1,786 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import { AmplifyClient, GetAppCommand } from '@aws-sdk/client-amplify'; +import { + updateAmplifyYmlFile, + updateCustomResources, + updateCdkStackFile, + getProjectInfo, + removeGen1ConfigurationFiles, + GEN1_CONFIGURATION_FILES, + getAuthTriggersConnections, + executeStackRefactor, + revertGen2Migration, + updateGitIgnoreForGen2, +} from './command-handlers'; +import { pathManager, stateManager } from '@aws-amplify/amplify-cli-core'; +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { SSMClient } from '@aws-sdk/client-ssm'; +import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; +import { ResourceMapping, TemplateGenerator } from '@aws-amplify/migrate-template-gen'; +import { printer } from './printer'; + +jest.mock('node:fs/promises', () => ({ + access: jest.fn(), + readFile: jest.fn(), + writeFile: jest.fn(), + rm: jest.fn(), + mkdir: jest.fn(), + cp: jest.fn(), + rename: jest.fn(), +})); + +jest.mock('@aws-amplify/amplify-cli-core', () => ({ + pathManager: { + findProjectRoot: jest.fn(), + }, + stateManager: { + getMeta: jest.fn(), + getResourceInputsJson: jest.fn(), + getCurrentRegion: jest.fn(), + }, + AmplifyCategories: { + AUTH: 'auth', + }, +})); + +jest.mock('@aws-sdk/client-cloudformation'); +jest.mock('@aws-sdk/client-ssm'); +jest.mock('@aws-sdk/client-cognito-identity-provider'); +jest.mock('@aws-sdk/client-sts'); +jest.mock('@aws-amplify/migrate-template-gen'); +jest.mock('./printer'); + +const actualUpdateCdkStackFile = jest.requireActual('./command-handlers').updateCdkStackFile; +const actualGetProjectInfoFile = jest.requireActual('./command-handlers').getProjectInfo; + +// Mock the internal methods +jest.mock('./command-handlers', () => ({ + ...jest.requireActual('./command-handlers'), + updateCdkStackFile: jest.fn(), + getProjectInfo: jest.fn(), +})); + +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('mock-uuid'), +})); + +jest.mock('@aws-amplify/cli-internal', () => ({ + UsageData: { + Instance: { + init: jest.fn(), + emitSuccess: jest.fn(), + emitError: jest.fn(), + }, + }, +})); + +jest.mock('@aws-sdk/client-amplify'); + +const generateMock = jest.fn().mockResolvedValue(true); +const revertMock = jest.fn().mockResolvedValue(true); + +jest.mocked(TemplateGenerator).mockImplementation( + () => + ({ + generate: generateMock, + revert: revertMock, + } as unknown as TemplateGenerator), +); + +const mockAccountId = '123456789012'; +jest.requireMock('@aws-sdk/client-sts').GetCallerIdentityCommand = jest.fn(); +jest.requireMock('@aws-sdk/client-sts').STSClient.prototype.send = jest.fn().mockResolvedValue({ + Account: mockAccountId, +}); + +const mockFromStack = 'mockFromStack'; +const mockToStack = 'mockToStack'; +const mockEnvName = 'mockEnvName'; +const mockAppId = 'mockAppId'; +const mockGen1MetaContent = JSON.stringify({ + providers: { + awscloudformation: { + AmplifyAppId: mockAppId, + StackName: `amplify-stack-${mockEnvName}`, + }, + }, +}); +const mockFsReadFileForRefactorOperations = (filePath: unknown) => { + if (typeof filePath === 'string' && filePath.includes('amplify-meta.json')) { + return Promise.resolve(mockGen1MetaContent); + } + return Promise.resolve('{}'); +}; + +describe('updateAmplifyYmlFile', () => { + const amplifyClient = new AmplifyClient(); + const mockAppId = 'testAppId'; + const amplifyYmlPath = '/mockRootDir/amplify.yml'; + const mockBuildSpec = `version: 1 +backend: + phases: + build: + commands: + - '# Execute Amplify CLI with the helper script' + - amplifyPush --simple +frontend: + phases: + preBuild: + commands: + - npm ci --cache .npm --prefer-offline + build: + commands: + - npm run build + artifacts: + baseDirectory: build + files: + - '**/*' + cache: + paths: + - .npm/**/*`; + + const expectedTransformedGen2BuildSpec = `version: 1 +backend: + phases: + build: + commands: + - '# Execute Amplify CLI with the helper script' + - npm ci --cache .npm --prefer-offline + - npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID +frontend: + phases: + preBuild: + commands: + - npm ci --cache .npm --prefer-offline + build: + commands: + - npm run build + artifacts: + baseDirectory: build + files: + - '**/*' + cache: + paths: + - .npm/**/*`; + + beforeEach(() => { + jest.clearAllMocks(); + jest.mocked(pathManager.findProjectRoot).mockReturnValue('/mockRootDir'); + }); + + it('should update amplify.yml file if it exists', async () => { + jest.mocked(fs.readFile).mockResolvedValue(mockBuildSpec); + + await updateAmplifyYmlFile(amplifyClient, mockAppId); + + expect(fs.readFile).toHaveBeenCalledWith(amplifyYmlPath, 'utf-8'); + expect(fs.writeFile).toHaveBeenCalledWith(amplifyYmlPath, expectedTransformedGen2BuildSpec, { + encoding: 'utf-8', + }); + }); + + it('should create amplify.yml file with updated buildSpec if it does not exist', async () => { + jest.mocked(fs.readFile).mockRejectedValue({ code: 'ENOENT' }); + (AmplifyClient.prototype.send as jest.Mock).mockResolvedValue({ + app: { buildSpec: mockBuildSpec }, + }); + + await updateAmplifyYmlFile(amplifyClient, mockAppId); + + expect(AmplifyClient.prototype.send).toHaveBeenCalledWith(expect.any(GetAppCommand)); + expect(fs.writeFile).toHaveBeenCalledWith(amplifyYmlPath, expectedTransformedGen2BuildSpec, { + encoding: 'utf-8', + }); + }); + + it('should not throw an error if buildSpec is not found in the app', async () => { + jest.mocked(fs.readFile).mockRejectedValue({ code: 'ENOENT' }); + (AmplifyClient.prototype.send as jest.Mock).mockResolvedValue({ + app: {}, + }); + + await expect(updateAmplifyYmlFile(amplifyClient, mockAppId)).resolves.not.toThrow(); + }); + + it('should throw an error if app is not found', async () => { + jest.mocked(fs.readFile).mockRejectedValue({ code: 'ENOENT' }); + (AmplifyClient.prototype.send as jest.Mock).mockResolvedValue({}); + + await expect(updateAmplifyYmlFile(amplifyClient, mockAppId)).rejects.toThrow('App not found'); + }); + + it('should throw the original error if it is not related to file not found', async () => { + const error = new Error('Some other error'); + jest.mocked(fs.readFile).mockRejectedValue(error); + + await expect(updateAmplifyYmlFile(amplifyClient, mockAppId)).rejects.toThrow(error); + }); +}); + +describe('removeGen1ConfigurationFiles', () => { + const sourceDir = 'src'; + const mockProjectConfigString = JSON.stringify({ + whyContinueWithGen1: 'Prefer not to answer', + projectName: 'testgen1', + version: '3.1', + frontend: 'javascript', + javascript: { + framework: 'none', + config: { + SourceDir: sourceDir, + DistributionDir: 'dist', + }, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should remove all gen1 configuration files', async () => { + jest.mocked(fs.readFile).mockResolvedValue(mockProjectConfigString); + + await removeGen1ConfigurationFiles(); + + GEN1_CONFIGURATION_FILES.forEach((file) => { + expect(fs.rm).toHaveBeenCalledWith(`${sourceDir}/${file}`); + }); + }); + + it('should not throw an error when project-config json is not found', async () => { + jest.mocked(fs.readFile).mockRejectedValue({ code: 'ENOENT' }); + + await expect(removeGen1ConfigurationFiles()).resolves.not.toThrow(); + GEN1_CONFIGURATION_FILES.forEach((file) => { + expect(fs.rm).not.toHaveBeenCalledWith(`${sourceDir}/${file}`); + }); + }); + + it('should not throw an error when a configuration file is not found', async () => { + jest.mocked(fs.readFile).mockResolvedValue(mockProjectConfigString); + jest.mocked(fs.rm).mockRejectedValueOnce({ code: 'ENOENT' }); + + await expect(removeGen1ConfigurationFiles()).resolves.not.toThrow(); + GEN1_CONFIGURATION_FILES.forEach((file) => { + // making some other files are removed even if one errors out + expect(fs.rm).toHaveBeenCalledWith(`${sourceDir}/${file}`); + }); + }); +}); + +describe('updateCustomResources', () => { + const mockRootDir = '/mock/root/dir'; + + const mockProjectConfig = JSON.stringify({ + projectName: 'testProject', + version: '3.1', + frontend: 'javascript', + javascript: { + framework: 'react', + config: { + SourceDir: 'src', + DistributionDir: 'build', + BuildCommand: 'npm run-script build', + StartCommand: 'npm run-script start', + }, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + + jest.mocked(pathManager.findProjectRoot).mockReturnValue(mockRootDir); + jest.mocked(stateManager.getMeta).mockReturnValue({ + custom: { + resource1: {}, + resource2: {}, + }, + }); + + jest.mocked(fs.mkdir).mockResolvedValue(undefined); + jest.mocked(fs.cp).mockResolvedValue(undefined); + jest.mocked(fs.readFile).mockImplementation((filePath) => { + if (typeof filePath === 'string' && filePath.includes('project-config.json')) { + return Promise.resolve(mockProjectConfig); + } + return Promise.resolve('{}'); + }); + + jest.mocked(getProjectInfo).mockResolvedValue("{envName: `${AMPLIFY_GEN_1_ENV_NAME}`, projectName: 'testProject'}"); + + jest.mocked(updateCdkStackFile).mockResolvedValue(undefined); + }); + + it('should copy custom resources and types folder and call updateCdkStackFile', async () => { + await updateCustomResources(); + + expect(fs.mkdir).toHaveBeenCalledWith('amplify-gen2/amplify/custom', { recursive: true }); + expect(fs.mkdir).toHaveBeenCalledWith('amplify-gen2/amplify/types', { recursive: true }); + + expect(fs.cp).toHaveBeenCalledWith(path.join(mockRootDir, 'amplify', 'backend', 'custom'), 'amplify-gen2/amplify/custom', { + recursive: true, + filter: expect.any(Function), + }); + + expect(fs.cp).toHaveBeenCalledWith(path.join(mockRootDir, 'amplify', 'backend', 'types'), 'amplify-gen2/amplify/types', { + recursive: true, + }); + }); + + it('should filter out package.json and yarn.lock files', async () => { + await updateCustomResources(); + + const cpCalls = (fs.cp as jest.Mock).mock.calls; + const filterFn = cpCalls.find((call) => call[2]?.filter)?.[2]?.filter; + + expect(filterFn).toBeDefined(); + expect(filterFn('some/path/package.json')).toBe(false); + expect(filterFn('some/path/yarn.lock')).toBe(false); + expect(filterFn('some/path/other-file.js')).toBe(true); + }); + + it('should handle file system errors gracefully', async () => { + const fsError = new Error('File system error'); + jest.mocked(fs.mkdir).mockRejectedValue(fsError); + + await expect(updateCustomResources()).rejects.toThrow('File system error'); + }); + + it('should handle empty custom resources object', async () => { + jest.mocked(stateManager.getMeta).mockReturnValue({ custom: {} }); + + await updateCustomResources(); + + expect(fs.cp).not.toHaveBeenCalled(); + expect(updateCdkStackFile).not.toHaveBeenCalled(); + }); +}); + +describe('getAuthTriggersConnections', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return empty object when no auth resources exist', async () => { + jest.mocked(stateManager.getMeta).mockReturnValue({}); + + const result = await getAuthTriggersConnections(); + + expect(result).toEqual({}); + }); + + it('should return empty object when auth resource exists but no trigger connections', async () => { + jest.mocked(stateManager.getMeta).mockReturnValue({ + auth: { + myAuthResource: { + service: 'Cognito', + providerPlugin: 'awscloudformation', + }, + }, + }); + + jest.mocked(stateManager.getResourceInputsJson).mockReturnValue({ + cognitoConfig: {}, + }); + + const result = await getAuthTriggersConnections(); + + expect(result).toEqual({}); + }); + + it('should parse authTriggerConnections when provided as JSON string', async () => { + jest.mocked(stateManager.getMeta).mockReturnValue({ + auth: { + myAuthResource: { + service: 'Cognito', + providerPlugin: 'awscloudformation', + }, + }, + }); + + const mockTriggerConnections = JSON.stringify([ + { + triggerType: 'PreSignUp', + lambdaFunctionName: 'myPreSignUpFunction', + }, + { + triggerType: 'PostConfirmation', + lambdaFunctionName: 'myPostConfirmationFunction', + }, + ]); + + jest.mocked(stateManager.getResourceInputsJson).mockReturnValue({ + cognitoConfig: { + authTriggerConnections: mockTriggerConnections, + }, + }); + + const result = await getAuthTriggersConnections(); + + expect(result).toEqual({ + PreSignUp: 'amplify/backend/function/myPreSignUpFunction/src', + PostConfirmation: 'amplify/backend/function/myPostConfirmationFunction/src', + }); + }); + + it('should parse authTriggerConnections when provided as array of JSON strings', async () => { + jest.mocked(stateManager.getMeta).mockReturnValue({ + auth: { + myAuthResource: { + service: 'Cognito', + providerPlugin: 'awscloudformation', + }, + }, + }); + + const mockTriggerConnections = [ + JSON.stringify({ + triggerType: 'PreSignUp', + lambdaFunctionName: 'myPreSignUpFunction', + }), + JSON.stringify({ + triggerType: 'PostConfirmation', + lambdaFunctionName: 'myPostConfirmationFunction', + }), + ]; + + jest.mocked(stateManager.getResourceInputsJson).mockReturnValue({ + cognitoConfig: { + authTriggerConnections: mockTriggerConnections, + }, + }); + + const result = await getAuthTriggersConnections(); + + expect(result).toEqual({ + PreSignUp: 'amplify/backend/function/myPreSignUpFunction/src', + PostConfirmation: 'amplify/backend/function/myPostConfirmationFunction/src', + }); + }); + + it('should handle triggers defined directly in cognitoConfig', async () => { + jest.mocked(stateManager.getMeta).mockReturnValue({ + auth: { + myAuthResource: { + service: 'Cognito', + providerPlugin: 'awscloudformation', + }, + }, + }); + + jest.mocked(stateManager.getResourceInputsJson).mockReturnValue({ + cognitoConfig: { + triggers: { + PreSignUp: true, + PostConfirmation: true, + }, + }, + }); + + const result = await getAuthTriggersConnections(); + + expect(result).toEqual({ + PreSignUp: 'amplify/backend/function/myAuthResourcePreSignUp/src', + PostConfirmation: 'amplify/backend/function/myAuthResourcePostConfirmation/src', + }); + }); + + it('should throw error when authTriggerConnections is invalid', async () => { + jest.mocked(stateManager.getMeta).mockReturnValue({ + auth: { + myAuthResource: { + service: 'Cognito', + providerPlugin: 'awscloudformation', + }, + }, + }); + + jest.mocked(stateManager.getResourceInputsJson).mockReturnValue({ + cognitoConfig: { + authTriggerConnections: 'invalid-json', + }, + }); + + await expect(getAuthTriggersConnections()).rejects.toThrow('Error parsing auth trigger connections'); + }); +}); + +describe('updateCdkStackFile', () => { + const mockCustomResources = ['resource1']; + const mockCustomResourcesPath = 'amplify-gen2/amplify/custom'; + const mockProjectRoot = '/mockRootDir'; + + const originalCdkContentWithError = ` + import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; + import * as cdk from 'aws-cdk-lib'; + + export class cdkStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const projectInfo = AmplifyHelpers.getProjectInfo(); + AmplifyHelpers.addResourceDependency(this, + { + category: "custom", + resourceName: "customResource1", + }, + { + category: "auth", + resourceName: "authResource1", + } + ); + } + } + `; + + const originalCdkContentWithoutError = ` + import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper'; + import * as cdk from 'aws-cdk-lib'; + + export class cdkStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const projectInfo = AmplifyHelpers.getProjectInfo(); + /* AmplifyHelpers.addResourceDependency(this, + { + category: "custom", + resourceName: "customResource1", + }, + { + category: "auth", + resourceName: "authResource1", + } + ); */ + } + } + `; + + beforeEach(() => { + jest.clearAllMocks(); + jest.mocked(updateCdkStackFile).mockImplementation(actualUpdateCdkStackFile); + jest.mocked(getProjectInfo).mockImplementation(actualGetProjectInfoFile); + jest.mocked(pathManager.findProjectRoot).mockReturnValue('/mockRootDir'); + }); + + afterEach(() => { + // Reset to the mock after each test if needed + jest.mocked(updateCdkStackFile).mockReset(); + jest.mocked(getProjectInfo).mockReset(); + }); + + test.each([ + [originalCdkContentWithError, true], + [originalCdkContentWithoutError, false], + ])('should correctly transform CDK stack file content', async (originalCdkContent, shouldThrowError) => { + const mockProjectConfig = JSON.stringify({ + projectName: 'testProject', + version: '3.1', + frontend: 'javascript', + javascript: { + framework: 'react', + config: { + SourceDir: 'src', + DistributionDir: 'build', + BuildCommand: 'npm run-script build', + StartCommand: 'npm run-script start', + }, + }, + }); + + // Mock readFile to return content for the correct stack file path + jest.mocked(fs.readFile).mockImplementation((filePath) => { + if (typeof filePath === 'string' && filePath.includes('project-config.json')) { + return Promise.resolve(mockProjectConfig); + } + if (filePath === path.join(mockCustomResourcesPath, 'resource1', 'cdk-stack.ts')) { + return Promise.resolve(originalCdkContent); + } + return Promise.resolve('{}'); + }); + + jest.mocked(getProjectInfo).mockResolvedValue("{envName: `${AMPLIFY_GEN_1_ENV_NAME}`, projectName: 'testProject'}"); + + await updateCdkStackFile(mockCustomResources, mockCustomResourcesPath, mockProjectRoot); + + // Verify writeFile was called with the correct path and transformed content + expect(fs.writeFile).toHaveBeenCalled(); + const writeFileCall = (fs.writeFile as jest.Mock).mock.calls[0]; + const transformedContent = writeFileCall[1]; + + // Verify specific transformations + expect(transformedContent).not.toContain('import { AmplifyHelpers }'); // Import removed + if (shouldThrowError) { + expect(transformedContent).toContain( + "throw new Error('Follow https://docs.amplify.aws/react/start/migrate-to-gen2/ to update the resource dependency')", + ); + } // Error added + expect(transformedContent).toContain("const projectInfo = {envName: `${AMPLIFY_GEN_1_ENV_NAME}`, projectName: 'testProject'}"); // Project info replaced + }); +}); + +describe('executeStackRefactor', () => { + const mockFromStack = 'mockFromStack'; + const mockToStack = 'mockToStack'; + const mockResourceMappings: ResourceMapping[] = [ + { + Source: { StackName: 'gen1Stack', LogicalResourceId: 'SourceLogicalId' }, + Destination: { + StackName: 'gen2Stack', + LogicalResourceId: 'DestinationLogicalId', + }, + }, + ]; + + beforeEach(() => { + jest.clearAllMocks(); + + jest.mocked(fs.readFile).mockImplementation(mockFsReadFileForRefactorOperations); + }); + + it('should initialize TemplateGenerator with correct parameters and call generate', async () => { + await executeStackRefactor(mockFromStack, mockToStack, mockResourceMappings); + + // Verify TemplateGenerator was initialized correctly + expect(TemplateGenerator).toHaveBeenCalledWith( + mockFromStack, + mockToStack, + mockAccountId, + expect.any(CloudFormationClient), + expect.any(SSMClient), + expect.any(CognitoIdentityProviderClient), + mockAppId, + mockEnvName, + ); + + expect(generateMock).toHaveBeenCalledWith(mockResourceMappings); + expect(generateMock).toHaveBeenCalledTimes(1); + + expect(printer.print).toHaveBeenCalled(); + }); + + it('should handle errors when generate fails', async () => { + jest.mocked(TemplateGenerator).mockImplementationOnce( + () => + ({ + generate: jest.fn().mockResolvedValue(false), + revert: jest.fn(), + } as unknown as TemplateGenerator), + ); + + await executeStackRefactor(mockFromStack, mockToStack); + + const mockUsageData = jest.requireMock('@aws-amplify/cli-internal').UsageData.Instance; + expect(mockUsageData.emitError).toHaveBeenCalled(); + expect(mockUsageData.emitSuccess).not.toHaveBeenCalled(); + }); + + it('should work without resource mappings', async () => { + await executeStackRefactor(mockFromStack, mockToStack); + + expect(generateMock).toHaveBeenCalledWith(undefined); + expect(generateMock).toHaveBeenCalledTimes(1); + }); +}); + +describe('revertGen2Migration', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest.mocked(fs.readFile).mockImplementation(mockFsReadFileForRefactorOperations); + }); + + it('should initialize TemplateGenerator with correct parameters and call revert', async () => { + await revertGen2Migration(mockFromStack, mockToStack); + + expect(TemplateGenerator).toHaveBeenCalledWith( + mockFromStack, + mockToStack, + mockAccountId, + expect.any(CloudFormationClient), + expect.any(SSMClient), + expect.any(CognitoIdentityProviderClient), + mockAppId, + mockEnvName, + ); + + expect(revertMock).toHaveBeenCalledTimes(1); + expect(printer.print).toHaveBeenCalled(); + expect(fs.rm).toHaveBeenCalledWith('amplify', { force: true, recursive: true }); + expect(fs.rename).toHaveBeenCalledWith('.amplify/migration/amplify', 'amplify'); + }); + + it('should handle errors when revert fails', async () => { + jest.mocked(TemplateGenerator).mockImplementationOnce( + () => + ({ + generate: jest.fn(), + revert: jest.fn().mockResolvedValue(false), + } as unknown as TemplateGenerator), + ); + + await revertGen2Migration(mockFromStack, mockToStack); + + const mockUsageData = jest.requireMock('@aws-amplify/cli-internal').UsageData.Instance; + expect(mockUsageData.emitError).toHaveBeenCalled(); + expect(mockUsageData.emitSuccess).not.toHaveBeenCalled(); + expect(fs.rm).not.toHaveBeenCalled(); + expect(fs.rename).not.toHaveBeenCalled(); + }); +}); + +describe('updateGitIgnoreForGen2', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + const mockGen1GitIgnore = `#amplify-do-not-edit-begin +amplify/\\#current-cloud-backend +amplify/.config/local-* +amplify/logs +amplify/mock-data +amplify/mock-api-resources +amplify/backend/amplify-meta.json +amplify/backend/.temp +build/ +dist/ +node_modules/ +aws-exports.js +awsconfiguration.json +amplifyconfiguration.json +amplifyconfiguration.dart +amplify-build-config.json +amplify-gradle-config.json +amplifytools.xcconfig +.secret-* +**.sample +#amplify-do-not-edit-end`; + + const expectedGen2Gitignore = `# amplify +.amplify +amplify_outputs* +amplifyconfiguration* +# node_modules +node_modules +build +dist`; + + const expectedFileEncoding = { encoding: 'utf-8' }; + const expectedFilePath = `${process.cwd()}/.gitignore`; + + it('should add gen2 migration files to gitignore', async () => { + jest.mocked(fs.readFile).mockResolvedValue(mockGen1GitIgnore); + + await updateGitIgnoreForGen2(); + + expect(fs.writeFile).toHaveBeenCalledWith(expectedFilePath, expectedGen2Gitignore, expectedFileEncoding); + }); + + it('should add gen2 migration files to gitignore if it does not exist', async () => { + jest.mocked(fs.readFile).mockRejectedValue(new Error('File does not exist')); + await updateGitIgnoreForGen2(); + + expect(fs.writeFile).toHaveBeenCalledWith(expectedFilePath, expectedGen2Gitignore, expectedFileEncoding); + }); +}); diff --git a/packages/amplify-migration/src/command-handlers.ts b/packages/amplify-migration/src/command-handlers.ts new file mode 100644 index 00000000000..6768cc8f21d --- /dev/null +++ b/packages/amplify-migration/src/command-handlers.ts @@ -0,0 +1,617 @@ +#!/usr/bin/env node +import path from 'node:path'; +import fs from 'node:fs/promises'; +import assert from 'node:assert'; +import { v4 as uuid } from 'uuid'; + +import { createGen2Renderer } from '@aws-amplify/amplify-gen2-codegen'; + +import { UsageData } from '@aws-amplify/cli-internal'; +import { AmplifyClient, UpdateAppCommand, GetAppCommand } from '@aws-sdk/client-amplify'; +import { CloudFormationClient } from '@aws-sdk/client-cloudformation'; +import { CognitoIdentityProviderClient, LambdaConfigType } from '@aws-sdk/client-cognito-identity-provider'; +import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity'; +import { S3Client } from '@aws-sdk/client-s3'; +import { LambdaClient } from '@aws-sdk/client-lambda'; +import { CloudWatchEventsClient } from '@aws-sdk/client-cloudwatch-events'; +import { SSMClient } from '@aws-sdk/client-ssm'; +import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts'; +import { BackendDownloader } from './backend_downloader'; +import { AppContextLogger } from './logger'; +import { BackendEnvironmentResolver } from './backend_environment_selector'; +import { Analytics, AppAnalytics } from './analytics'; +import { AppAuthDefinitionFetcher } from './app_auth_definition_fetcher'; +import { AppStorageDefinitionFetcher } from './app_storage_definition_fetcher'; +import { AmplifyCategories, IUsageData, stateManager, pathManager } from '@aws-amplify/amplify-cli-core'; +import { AuthTriggerConnection } from '@aws-amplify/amplify-gen1-codegen-auth-adapter'; +import { DataDefinitionFetcher } from './data_definition_fetcher'; +import { AmplifyStackParser } from './amplify_stack_parser'; +import { AppFunctionsDefinitionFetcher } from './app_functions_definition_fetcher'; +import { TemplateGenerator, ResourceMapping } from '@aws-amplify/migrate-template-gen'; +import { printer } from './printer'; +import { format } from './format'; +import ora from 'ora'; + +interface CodegenCommandParameters { + analytics: Analytics; + logger: AppContextLogger; + outputDirectory: string; + backendEnvironmentName: string | undefined; + dataDefinitionFetcher: DataDefinitionFetcher; + authDefinitionFetcher: AppAuthDefinitionFetcher; + storageDefinitionFetcher: AppStorageDefinitionFetcher; + functionsDefinitionFetcher: AppFunctionsDefinitionFetcher; +} + +const TEMP_GEN_2_OUTPUT_DIR = 'amplify-gen2'; +const AMPLIFY_DIR = 'amplify'; +const MIGRATION_DIR = '.amplify/migration'; +const GEN1_COMMAND = '- amplifyPush --simple'; +const GEN2_INSTALL_COMMAND = '- npm ci --cache .npm --prefer-offline'; +const GEN2_COMMAND = '- npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID'; +const GEN2_COMMAND_GENERATION_MESSAGE_SUFFIX = 'your Gen 2 backend code'; +const GEN1_REMOVE_CONFIGURATION_MESSAGE_SUFFIX = 'your Gen 1 configuration files'; +const GEN1_CUSTOM_RESOURCES_SUFFIX = 'your Gen 1 custom resources'; +export const GEN1_CONFIGURATION_FILES = ['aws-exports.js', 'amplifyconfiguration.json', 'awsconfiguration.json']; +const CUSTOM_DIR = 'custom'; +const TYPES_DIR = 'types'; +const BACKEND_DIR = 'backend'; +const GEN2_COMMAND_REPLACE_STRING = `${GEN2_INSTALL_COMMAND}\n${' '.repeat(8)}${GEN2_COMMAND}`; + +enum GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS { + DOT_AMPLIFY = '.amplify', + AMPLIFY_OUTPUTS = 'amplify_outputs*', + AMPLIFY_CONFIGURATION = 'amplifyconfiguration*', + NODE_MODULES = 'node_modules', + BUILD = 'build', + DIST = 'dist', +} + +const generateGen2Code = async ({ + outputDirectory, + backendEnvironmentName, + authDefinitionFetcher, + dataDefinitionFetcher, + storageDefinitionFetcher, + functionsDefinitionFetcher, +}: CodegenCommandParameters) => { + const fetchingAWSResourceDetails = ora('Fetching resource details from AWS').start(); + const gen2RenderOptions = { + outputDir: outputDirectory, + backendEnvironmentName: backendEnvironmentName, + auth: await authDefinitionFetcher.getDefinition(), + storage: await storageDefinitionFetcher.getDefinition(), + data: await dataDefinitionFetcher.getDefinition(), + functions: await functionsDefinitionFetcher.getDefinition(), + customResources: await getCustomResourceMap(), + unsupportedCategories: unsupportedCategories(), + }; + fetchingAWSResourceDetails.succeed('Fetched resource details from AWS'); + + const gen2Codegen = ora(`Generating ${GEN2_COMMAND_GENERATION_MESSAGE_SUFFIX}`).start(); + assert(gen2RenderOptions); + const pipeline = createGen2Renderer(gen2RenderOptions); + assert(backendEnvironmentName); + const usageData = await getUsageDataMetric(backendEnvironmentName); + + try { + await pipeline.render(); + await usageData.emitSuccess(); + } catch (e) { + await usageData.emitError(e); + gen2Codegen.fail(`Failed to generate ${GEN2_COMMAND_GENERATION_MESSAGE_SUFFIX}`); + throw e; + } + gen2Codegen.succeed(`Generated ${GEN2_COMMAND_GENERATION_MESSAGE_SUFFIX}`); +}; + +type AmplifyMetaAuth = { + service: 'Cognito'; + providerPlugin: 'awscloudformation'; +}; + +type AmplifyMetaFunction = { + service: 'Lambda'; + providerPlugin: 'awscloudformation'; + output: Record; +}; + +type AmplifyMeta = { + auth: Record; + function: Record; +}; + +const getFunctionPath = (functionName: string) => { + return path.join(AMPLIFY_DIR, BACKEND_DIR, 'function', functionName, 'src'); +}; + +const getUsageDataMetric = async (envName: string): Promise => { + const usageData = UsageData.Instance; + const accountId = await getAccountId(); + assert(accountId); + + usageData.init( + uuid(), + '', + { + command: 'to-gen-2', + argv: process.argv, + }, + accountId, + { + envName, + }, + Date.now(), + ); + + return usageData; +}; + +const getAccountId = async (): Promise => { + const stsClient = new STSClient(); + const callerIdentityResult = await stsClient.send(new GetCallerIdentityCommand()); + return callerIdentityResult.Account; +}; + +export const getAuthTriggersConnections = async (): Promise>> => { + const amplifyMeta: AmplifyMeta = stateManager.getMeta(); + if (!amplifyMeta.auth) { + return {}; + } + const resourceName = Object.entries(amplifyMeta.auth).find(([, resource]) => resource.service === 'Cognito')?.[0]; + assert(resourceName); + const authInputs = stateManager.getResourceInputsJson(undefined, AmplifyCategories.AUTH, resourceName); + if (authInputs && typeof authInputs === 'object' && 'cognitoConfig' in authInputs && typeof authInputs.cognitoConfig === 'object') { + let triggerConnections: AuthTriggerConnection[] = []; + if ('authTriggerConnections' in authInputs.cognitoConfig) { + try { + // Check if authTriggerConnections is a valid JSON string + if (typeof authInputs.cognitoConfig.authTriggerConnections === 'string') { + triggerConnections = JSON.parse(authInputs.cognitoConfig.authTriggerConnections); + } else { + // If not a valid JSON string, assume it's an array of JSON strings + triggerConnections = authInputs.cognitoConfig.authTriggerConnections.map((connection: string) => JSON.parse(connection)); + } + return triggerConnections.reduce((prev, curr) => { + prev[curr.triggerType] = getFunctionPath(curr.lambdaFunctionName); + return prev; + }, {} as Partial>); + } catch (e) { + throw new Error('Error parsing auth trigger connections'); + } + } else if ('triggers' in authInputs.cognitoConfig && typeof authInputs.cognitoConfig.triggers === 'object') { + const authTriggers = authInputs.cognitoConfig.triggers; + return Object.keys(authTriggers).reduce((prev, authTrigger) => { + const triggerResourceName = `${resourceName}${authTrigger}`; + prev[authTrigger as keyof LambdaConfigType] = getFunctionPath(triggerResourceName); + return prev; + }, {} as Partial>); + } + } + return {}; +}; + +const resolveAppId = (): string => { + const meta = stateManager.getMeta(); + return meta?.providers?.awscloudformation?.AmplifyAppId; +}; + +const unsupportedCategories = (): Map => { + const unsupportedCategories = new Map(); + const urlPrefix = 'https://docs.amplify.aws/react/build-a-backend/add-aws-services'; + const restAPIKey = 'rest api'; + + unsupportedCategories.set('geo', `${urlPrefix}/geo/`); + unsupportedCategories.set('analytics', `${urlPrefix}/analytics/`); + unsupportedCategories.set('predictions', `${urlPrefix}/predictions/`); + unsupportedCategories.set('notifications', `${urlPrefix}/in-app-messaging/`); + unsupportedCategories.set('interactions', `${urlPrefix}/interactions/`); + unsupportedCategories.set('rest api', `${urlPrefix}/rest-api/`); + + const meta = stateManager.getMeta(); + const categories = Object.keys(meta); + + const unsupportedCategoriesList = new Map(); + + categories.forEach((category) => { + if (category == 'api') { + const apiList = meta?.api; + if (apiList) { + Object.keys(apiList).forEach((api) => { + const apiObj = apiList[api]; + if (apiObj.service == 'API Gateway') { + const restAPIDocsLink = unsupportedCategories.get(restAPIKey); + assert(restAPIDocsLink); + unsupportedCategoriesList.set(restAPIKey, restAPIDocsLink); + } + }); + } + } else { + if (unsupportedCategories.has(category) && Object.entries(meta[category]).length > 0) { + const unsupportedCategoryDocLink = unsupportedCategories.get(category); + assert(unsupportedCategoryDocLink); + unsupportedCategoriesList.set(category, unsupportedCategoryDocLink); + } + } + }); + + return unsupportedCategoriesList; +}; + +export async function updateAmplifyYmlFile(amplifyClient: AmplifyClient, appId: string) { + const rootDir = pathManager.findProjectRoot(); + assert(rootDir); + const amplifyYmlPath = path.join(rootDir, 'amplify.yml'); + + try { + // Read the content of amplify.yml file if it exists + const amplifyYmlContent = await fs.readFile(amplifyYmlPath, 'utf-8'); + + await writeToAmplifyYmlFile(amplifyYmlPath, amplifyYmlContent); + } catch (error) { + if (error.code === 'ENOENT') { + // If amplify.yml file doesn't exist, make a getApp call to get buildSpec + const getAppResponse = await amplifyClient.send(new GetAppCommand({ appId })); + + assert(getAppResponse.app, 'App not found'); + const buildSpec = getAppResponse.app.buildSpec; + + if (buildSpec) { + await writeToAmplifyYmlFile(amplifyYmlPath, buildSpec); + } + } else { + // Throw the original error if it's not related to file not found + throw error; + } + } +} + +async function writeToAmplifyYmlFile(amplifyYmlPath: string, content: string) { + // eslint-disable-next-line spellcheck/spell-checker + // Replace 'amplifyPush --simple' with 'npx ampx pipeline-deploy' + content = content.replace(new RegExp(GEN1_COMMAND, 'g'), GEN2_COMMAND_REPLACE_STRING); + await fs.writeFile(amplifyYmlPath, content, { encoding: 'utf-8' }); +} + +export async function updateGitIgnoreForGen2() { + const cwd = process.cwd(); + const updateGitIgnore = ora('Updating gitignore contents').start(); + // Rewrite .gitignore to support gen2 related files + let gitIgnore = ''; + try { + gitIgnore = await fs.readFile(`${cwd}/.gitignore`, { encoding: 'utf-8' }); + } catch (e) { + // ignore absence of gitignore + } + // remove gen 1 amplify section + const regex = /#amplify-do-not-edit-begin[\s\S]*#amplify-do-not-edit-end/g; + let newGitIgnore = gitIgnore.replace(regex, ''); + // add gen 2 section + if (!newGitIgnore.includes(GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.DOT_AMPLIFY)) { + newGitIgnore = `${newGitIgnore}\n# amplify\n${GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.DOT_AMPLIFY}`; + } + if (!newGitIgnore.includes(GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.AMPLIFY_OUTPUTS)) { + newGitIgnore = `${newGitIgnore}\n${GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.AMPLIFY_OUTPUTS}`; + } + if (!newGitIgnore.includes(GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.AMPLIFY_CONFIGURATION)) { + newGitIgnore = `${newGitIgnore}\n${GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.AMPLIFY_CONFIGURATION}`; + } + if (!newGitIgnore.includes(GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.NODE_MODULES)) { + newGitIgnore = `${newGitIgnore}\n# node_modules\n${GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.NODE_MODULES}`; + } + if (!newGitIgnore.includes(GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.BUILD)) { + newGitIgnore = `${newGitIgnore}\n${GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.BUILD}`; + } + if (!newGitIgnore.includes(GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.DIST)) { + newGitIgnore = `${newGitIgnore}\n${GEN2_AMPLIFY_GITIGNORE_FILES_OR_DIRS.DIST}`; + } + // remove empty lines + newGitIgnore = newGitIgnore.replace(/^\s*[\r\n]/gm, ''); + await fs.writeFile(`${cwd}/.gitignore`, newGitIgnore, { encoding: 'utf-8' }); + updateGitIgnore.succeed('Updated gitignore contents'); +} + +const getCustomResources = (): string[] => { + const meta = stateManager.getMeta(); + const customCategory = meta?.custom; + + // If the custom category exists, return its resource names; otherwise, return an empty array + return customCategory ? Object.keys(customCategory) : []; +}; + +const getCustomResourceMap = async (): Promise> => { + const customResources = getCustomResources(); + const customResourceMap = new Map(); + + const rootDir = pathManager.findProjectRoot(); + assert(rootDir); + const amplifyGen1BackendDir = path.join(rootDir, AMPLIFY_DIR, BACKEND_DIR); + const sourceCustomResourcePath = path.join(amplifyGen1BackendDir, CUSTOM_DIR); + + for (const resource of customResources) { + const cdkStackFilePath = path.join(sourceCustomResourcePath, resource, 'cdk-stack.ts'); + const cdkStackContent = await fs.readFile(cdkStackFilePath, { encoding: 'utf-8' }); + const className = cdkStackContent.match(/export class (\w+)/)?.[1]; + if (className) { + customResourceMap.set(resource, className); + } + } + + return customResourceMap; +}; + +export async function updateCustomResources() { + const customResources = getCustomResources(); + if (customResources.length > 0) { + const movingGen1CustomResources = ora(`Moving ${GEN1_CUSTOM_RESOURCES_SUFFIX}`).start(); + const rootDir = pathManager.findProjectRoot(); + assert(rootDir); + const amplifyGen1BackendDir = path.join(rootDir, AMPLIFY_DIR, BACKEND_DIR); + const amplifyGen2Dir = path.join(TEMP_GEN_2_OUTPUT_DIR, AMPLIFY_DIR); + const sourceCustomResourcePath = path.join(amplifyGen1BackendDir, CUSTOM_DIR); + const destinationCustomResourcePath = path.join(amplifyGen2Dir, CUSTOM_DIR); + const filterFiles = ['package.json', 'yarn.lock']; + await fs.mkdir(destinationCustomResourcePath, { recursive: true }); + // Copy the custom resources, excluding package.json and yarn.lock files + await fs.cp(sourceCustomResourcePath, destinationCustomResourcePath, { + recursive: true, + filter: (src) => { + const fileName = path.basename(src); + return !filterFiles.includes(fileName); + }, + }); + + const sourceTypesPath = path.join(amplifyGen1BackendDir, TYPES_DIR); + const destinationTypesPath = path.join(amplifyGen2Dir, TYPES_DIR); + await fs.mkdir(destinationTypesPath, { recursive: true }); + await fs.cp(sourceTypesPath, destinationTypesPath, { recursive: true }); + + await updateCdkStackFile(customResources, destinationCustomResourcePath, rootDir); + movingGen1CustomResources.succeed(`Moved ${GEN1_CUSTOM_RESOURCES_SUFFIX}`); + } +} + +export async function updateCdkStackFile(customResources: string[], destinationCustomResourcePath: string, rootDir: string) { + const projectInfo = await getProjectInfo(rootDir); + + for (const resource of customResources) { + const cdkStackFilePath = path.join(destinationCustomResourcePath, resource, 'cdk-stack.ts'); + + const amplifyHelpersImport = /import\s+\*\s+as\s+AmplifyHelpers\s+from\s+['"]@aws-amplify\/cli-extensibility-helper['"];\n?/; + + try { + let cdkStackContent = await fs.readFile(cdkStackFilePath, { encoding: 'utf-8' }); + + // Check for existence of AmplifyHelpers.addResourceDependency and throw an error if found + if (hasUncommentedDependency(cdkStackContent, 'AmplifyHelpers.addResourceDependency')) { + cdkStackContent = cdkStackContent.replace( + /export class/, + `throw new Error('Follow https://docs.amplify.aws/react/start/migrate-to-gen2/ to update the resource dependency');\n\nexport class`, + ); + } + + cdkStackContent = cdkStackContent.replace( + /export class/, + `const AMPLIFY_GEN_1_ENV_NAME = process.env.AMPLIFY_GEN_1_ENV_NAME ?? "sandbox";\n\nexport class`, + ); + + cdkStackContent = cdkStackContent.replace(/extends cdk.Stack/, `extends cdk.NestedStack`); + + // Replace the cdk.CfnParameter definition to include the default property + cdkStackContent = cdkStackContent.replace( + /new cdk\.CfnParameter\(this, ['"]env['"], {[\s\S]*?}\);/, + `new cdk.CfnParameter(this, "env", { + type: "String", + description: "Current Amplify CLI env name", + default: \`\${AMPLIFY_GEN_1_ENV_NAME}\` + });`, + ); + + // Replace AmplifyHelpers.getProjectInfo() with {envName: 'envName', projectName: 'projectName'} + cdkStackContent = cdkStackContent.replace(/AmplifyHelpers\.getProjectInfo\(\)/g, projectInfo); + + // Replace AmplifyHelpers.AmplifyResourceProps with {category: 'custom', resourceName: resource} + cdkStackContent = cdkStackContent.replace( + /AmplifyHelpers\.AmplifyResourceProps/g, + `{category: 'custom', resourceName: '${resource}' }`, + ); + + // Remove the import statement for AmplifyHelpers + cdkStackContent = cdkStackContent.replace(amplifyHelpersImport, ''); + + await fs.writeFile(cdkStackFilePath, cdkStackContent, { encoding: 'utf-8' }); + } catch (error) { + throw error(`Error updating the custom resource ${resource}`); + } + } +} + +const hasUncommentedDependency = (fileContent: string, matchString: string) => { + // Split the content into lines + const lines = fileContent.split('\n'); + + // Check each line + for (const line of lines) { + const trimmedLine = line.trim(); + + // Check if the line contains the dependency and is not commented + if ( + trimmedLine.includes(matchString) && + !trimmedLine.startsWith('//') && + !trimmedLine.startsWith('/*') && + !trimmedLine.includes('*/') && + !trimmedLine.match(/^\s*\*/) + ) { + return true; + } + } + + return false; +}; + +export async function getProjectInfo(rootDir: string) { + const configDir = path.join(rootDir, AMPLIFY_DIR, '.config'); + const projectConfigFilePath = path.join(configDir, 'project-config.json'); + const projectConfig = await fs.readFile(projectConfigFilePath, { encoding: 'utf-8' }); + + const projectConfigJson = JSON.parse(projectConfig); + if (!projectConfigJson.projectName) { + throw new Error('Project name not found in project-config.json'); + } + + return `{envName: \`\${AMPLIFY_GEN_1_ENV_NAME}\`, projectName: '${projectConfigJson.projectName}'}`; +} + +export async function prepare() { + const appId = resolveAppId(); + const inspectApp = ora(`Inspecting Amplify app ${appId} with current backend`).start(); + const amplifyClient = new AmplifyClient(); + const backendEnvironmentResolver = new BackendEnvironmentResolver(appId, amplifyClient); + const backendEnvironment = await backendEnvironmentResolver.selectBackendEnvironment(); + assert(backendEnvironment); + assert(backendEnvironmentResolver); + assert(backendEnvironment.environmentName); + + const s3Client = new S3Client(); + const cloudFormationClient = new CloudFormationClient(); + const cognitoIdentityProviderClient = new CognitoIdentityProviderClient(); + const cognitoIdentityPoolClient = new CognitoIdentityClient(); + const lambdaClient = new LambdaClient({ + region: stateManager.getCurrentRegion(), + }); + const cloudWatchEventsClient = new CloudWatchEventsClient(); + const amplifyStackParser = new AmplifyStackParser(cloudFormationClient); + const ccbFetcher = new BackendDownloader(s3Client); + inspectApp.stop(); + + await amplifyClient.send( + new UpdateAppCommand({ + appId, + environmentVariables: { + AMPLIFY_GEN_1_ENV_NAME: backendEnvironment.environmentName, + }, + }), + ); + + await generateGen2Code({ + outputDirectory: TEMP_GEN_2_OUTPUT_DIR, + storageDefinitionFetcher: new AppStorageDefinitionFetcher(backendEnvironmentResolver, new BackendDownloader(s3Client), s3Client), + authDefinitionFetcher: new AppAuthDefinitionFetcher( + cognitoIdentityPoolClient, + cognitoIdentityProviderClient, + amplifyStackParser, + backendEnvironmentResolver, + () => getAuthTriggersConnections(), + ccbFetcher, + ), + dataDefinitionFetcher: new DataDefinitionFetcher(backendEnvironmentResolver, new BackendDownloader(s3Client), amplifyStackParser), + functionsDefinitionFetcher: new AppFunctionsDefinitionFetcher( + lambdaClient, + cloudWatchEventsClient, + backendEnvironmentResolver, + stateManager, + ), + analytics: new AppAnalytics(appId), + logger: new AppContextLogger(appId), + backendEnvironmentName: backendEnvironment?.environmentName, + }); + + await updateAmplifyYmlFile(amplifyClient, appId); + + await updateGitIgnoreForGen2(); + + await removeGen1ConfigurationFiles(); + + await updateCustomResources(); + + const movingGen1BackendFiles = ora(`Moving your Gen 1 backend files to ${format.highlight(MIGRATION_DIR)}`).start(); + // Move gen1 amplify to .amplify/migrations and move gen2 amplify from amplify-gen2 to amplify dir to convert current app to gen2. + const cwd = process.cwd(); + await fs.rm(MIGRATION_DIR, { force: true, recursive: true }); + await fs.mkdir(MIGRATION_DIR, { recursive: true }); + await fs.rename(AMPLIFY_DIR, `${MIGRATION_DIR}/amplify`); + await fs.rename(`${TEMP_GEN_2_OUTPUT_DIR}/amplify`, `${cwd}/amplify`); + await fs.rename(`${TEMP_GEN_2_OUTPUT_DIR}/package.json`, `${cwd}/package.json`); + await fs.rm(TEMP_GEN_2_OUTPUT_DIR, { recursive: true }); + movingGen1BackendFiles.succeed(`Moved your Gen 1 backend files to ${format.highlight(MIGRATION_DIR)}`); +} + +export async function removeGen1ConfigurationFiles() { + const removingGen1ConfigurationFiles = ora(`Removing ${GEN1_REMOVE_CONFIGURATION_MESSAGE_SUFFIX}`).start(); + try { + const projectConfig = JSON.parse(await fs.readFile(`${AMPLIFY_DIR}/.config/project-config.json`, { encoding: 'utf-8' })); + if ('frontend' in projectConfig && typeof projectConfig.frontend === 'string') { + const frontendFramework = projectConfig.frontend; + const frontendFrameworkKey = projectConfig[frontendFramework]; + if ( + frontendFramework in projectConfig && + 'config' in frontendFrameworkKey && + typeof frontendFrameworkKey.config === 'object' && + 'SourceDir' in frontendFrameworkKey.config && + typeof frontendFrameworkKey.config.SourceDir === 'string' + ) { + const sourceDirLocation = frontendFrameworkKey.config.SourceDir; + await Promise.all(GEN1_CONFIGURATION_FILES.map((file) => fs.rm(`${sourceDirLocation}/${file}`))); + } + } + } catch (e) { + // Swallow errors from not being able to locate or read config files as its not in the core migration path + } finally { + removingGen1ConfigurationFiles.succeed(`Removed ${GEN1_REMOVE_CONFIGURATION_MESSAGE_SUFFIX}`); + } +} +/** + * Executes the stack refactor operation to move Gen1 resources from Gen1 stack into Gen2 stack + * @param fromStack + * @param toStack + * @param resourceMappings + */ +export async function executeStackRefactor(fromStack: string, toStack: string, resourceMappings?: ResourceMapping[]) { + const [templateGenerator, envName] = await initializeTemplateGenerator(fromStack, toStack); + const success = await templateGenerator.generate(resourceMappings); + const usageData = await getUsageDataMetric(envName); + if (success) { + printer.print(format.success(`Generated .README file(s) successfully under ${MIGRATION_DIR}/templates directory.`)); + await usageData.emitSuccess(); + } else { + await usageData.emitError(new Error('Failed to run execute command')); + } +} + +export async function revertGen2Migration(fromStack: string, toStack: string) { + const [templateGenerator, envName] = await initializeTemplateGenerator(fromStack, toStack); + const success = await templateGenerator.revert(); + const usageData = await getUsageDataMetric(envName); + if (success) { + printer.print(format.success(`Moved resources back to Gen 1 stack successfully.`)); + const movingGen1BackendFiles = ora(`Moving your Gen 1 backend files to ${format.highlight(AMPLIFY_DIR)}`).start(); + // Move gen1 amplify from .amplify/migration/amplify to amplify + await fs.rm(AMPLIFY_DIR, { force: true, recursive: true }); + await fs.rename(`${MIGRATION_DIR}/amplify`, AMPLIFY_DIR); + movingGen1BackendFiles.succeed(`Moved your Gen 1 backend files to ${format.highlight(AMPLIFY_DIR)}`); + await usageData.emitSuccess(); + } else { + await usageData.emitError(new Error('Failed to run revert command')); + } +} + +async function initializeTemplateGenerator(fromStack: string, toStack: string) { + const accountId = await getAccountId(); + assert(accountId); + const gen1MetaFile = await fs.readFile(`${MIGRATION_DIR}/${AMPLIFY_DIR}/backend/amplify-meta.json`, { encoding: 'utf-8' }); + assert(gen1MetaFile); + const gen1Meta = JSON.parse(gen1MetaFile); + const { AmplifyAppId: appId, StackName: stackName } = gen1Meta.providers.awscloudformation; + assert(appId); + assert(stackName); + const backendEnvironmentName = stackName.split('-')?.[2]; + assert(backendEnvironmentName); + const cfnClient = new CloudFormationClient(); + const ssmClient = new SSMClient(); + const cognitoIdpClient = new CognitoIdentityProviderClient(); + + return [ + new TemplateGenerator(fromStack, toStack, accountId, cfnClient, ssmClient, cognitoIdpClient, appId, backendEnvironmentName), + backendEnvironmentName, + ]; +} diff --git a/packages/amplify-migration/src/commands/gen2/execute/execute_command.test.ts b/packages/amplify-migration/src/commands/gen2/execute/execute_command.test.ts new file mode 100644 index 00000000000..431ebdab0a8 --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/execute/execute_command.test.ts @@ -0,0 +1,117 @@ +import { Gen2ExecuteCommand } from './execute_command'; +import { runCommandAsync } from '../../../test-utils/command_runner'; +import yargs, { CommandModule } from 'yargs'; +import assert from 'node:assert'; +import { ResourceMapping } from '@aws-sdk/client-cloudformation'; + +const mockHandler = jest.fn(); +jest.mock('../../../command-handlers', () => ({ + ...jest.requireActual('../../../command-handlers'), + executeStackRefactor: (from: string, to: string, resourceMappings?: ResourceMapping[]) => mockHandler(from, to, resourceMappings), +})); + +const resourceMappings: ResourceMapping[] = [ + { + Source: { StackName: 'gen1Stack', LogicalResourceId: 'customResourceA' }, + Destination: { StackName: 'gen2Stack', LogicalResourceId: 'customResourceA' }, + }, +]; + +const stubReadFile = jest.fn().mockResolvedValue(JSON.stringify(resourceMappings)); +jest.mock('node:fs/promises', () => ({ + readFile: () => stubReadFile(), +})); + +describe('Gen2ExecuteCommand', () => { + beforeEach(() => { + mockHandler.mockClear(); + }); + + it('should run command successfully', async () => { + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await runCommandAsync(parser, 'execute --from foo --to bar'); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith('foo', 'bar', undefined); + }); + + it('should run command successfully with resourceMappings option', async () => { + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await runCommandAsync(parser, 'execute --from foo --to bar --resourceMappings file://resourceMap.json'); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith('foo', 'bar', resourceMappings); + }); + + it('should fail command when resourceMappings does not start with file protocol', async () => { + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await assert.rejects( + () => runCommandAsync(parser, 'execute --from foo --to bar --resourceMappings resourceMap.json'), + (err: Error) => { + assert.equal(err.message, 'Expected resourceMappings to start with file://'); + return true; + }, + ); + }); + + it('should fail command when resourceMappings only has file://', async () => { + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await assert.rejects( + () => runCommandAsync(parser, 'execute --from foo --to bar --resourceMappings file://'), + (err: Error) => { + assert.equal(err.message, 'Expected resourceMappings to have a path after file://'); + return true; + }, + ); + }); + + it('should fail command when resourceMappings is invalid JSON', async () => { + stubReadFile.mockResolvedValue('invalid json'); + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await assert.rejects( + () => runCommandAsync(parser, 'execute --from foo --to bar --resourceMappings file://resourceMap.json'), + (err: Error) => { + assert.equal( + err.message, + 'Failed to parse resourceMappings from resourceMap.json: Unexpected token \'i\', "invalid json" is not valid JSON', + ); + return true; + }, + ); + }); + + it('should fail command when resourceMappings is not an array', async () => { + stubReadFile.mockResolvedValue(JSON.stringify({})); + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await assert.rejects( + () => runCommandAsync(parser, 'execute --from foo --to bar --resourceMappings file://resourceMap.json'), + (err: Error) => { + assert.equal(err.message, 'Invalid resourceMappings structure'); + return true; + }, + ); + }); + + it('should fail command when resourceMappings has an invalid structure', async () => { + const clonedResourceMappings = JSON.parse(JSON.stringify(resourceMappings)) as ResourceMapping[]; + delete clonedResourceMappings[0].Source; + stubReadFile.mockResolvedValue(JSON.stringify(clonedResourceMappings)); + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await assert.rejects( + () => runCommandAsync(parser, 'execute --from foo --to bar --resourceMappings file://resourceMap.json'), + (err: Error) => { + assert.equal(err.message, 'Invalid resourceMappings structure'); + return true; + }, + ); + }); + + it('should fail command when required arguments are not provided', async () => { + const parser = yargs().command(new Gen2ExecuteCommand() as unknown as CommandModule); + await assert.rejects( + () => runCommandAsync(parser, 'execute'), + (err: Error) => { + assert.equal(err.message, 'Missing required arguments: from, to'); + return true; + }, + ); + }); +}); diff --git a/packages/amplify-migration/src/commands/gen2/execute/execute_command.ts b/packages/amplify-migration/src/commands/gen2/execute/execute_command.ts new file mode 100644 index 00000000000..5e96b15451e --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/execute/execute_command.ts @@ -0,0 +1,113 @@ +import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'; +import fs from 'node:fs/promises'; +import { executeStackRefactor } from '../../../command-handlers'; +import assert from 'node:assert'; +import { ResourceMapping } from '@aws-amplify/migrate-template-gen'; + +export interface ExecuteCommandOptions { + from: string | undefined; + to: string | undefined; + resourceMappings: string | undefined; +} + +const FILE_PROTOCOL_PREFIX = 'file://'; + +/** + * Command that executes stack refactor operation needed for Gen2 migration. + */ +export class Gen2ExecuteCommand implements CommandModule { + /** + * @inheritDoc + */ + readonly command: string; + + /** + * @inheritDoc + */ + readonly describe: string; + + constructor() { + this.command = 'execute'; + this.describe = 'Moves Amplify Gen1 resources from a Gen1 stack into a Gen2 stack'; + } + + builder = (yargs: Argv): Argv => { + return yargs + .version(false) + .option('from', { + describe: 'Gen1 Amplify stack', + type: 'string', + demandOption: true, + }) + .option('to', { + describe: 'Gen2 Amplify stack', + type: 'string', + demandOption: true, + }) + .option('resourceMappings', { + describe: `Path to the resource mappings JSON file. E.g file://complete/path/to/file. The JSON is an array of resource mappings + where each mapping consists of a Source object and Destination object with each of them containing the StackName and LogicalResourceId + of the resource to move. E.g: + [{ + "Source": { + "StackName": "myStackA", + "LogicalResourceId": "myLogicalIdA" + }, + "Destination": { + "StackName": "myStackB", + "LogicalResourceId": "myLogicalIdB" + } + }]`, + type: 'string', + demandOption: false, + }); + }; + handler = async (args: ArgumentsCamelCase): Promise => { + const { from, to, resourceMappings } = args; + assert(from); + assert(to); + + let parsedResourceMappings: ResourceMapping[] | undefined = undefined; + + if (resourceMappings) { + if (!resourceMappings.startsWith(FILE_PROTOCOL_PREFIX)) { + throw new Error(`Expected resourceMappings to start with ${FILE_PROTOCOL_PREFIX}`); + } + const resourceMapPath = resourceMappings.split(FILE_PROTOCOL_PREFIX)[1]; + if (!resourceMapPath) { + throw new Error(`Expected resourceMappings to have a path after ${FILE_PROTOCOL_PREFIX}`); + } + const resourceMappingsFromFile = await fs.readFile(resourceMapPath, { encoding: 'utf-8' }); + try { + parsedResourceMappings = JSON.parse(resourceMappingsFromFile); + } catch (error) { + throw new Error(`Failed to parse resourceMappings from ${resourceMapPath}: ${error.message}`); + } + if (!Array.isArray(parsedResourceMappings) || !parsedResourceMappings.every(this.isResourceMappingValid)) { + throw new Error('Invalid resourceMappings structure'); + } + } + await executeStackRefactor(from, to, parsedResourceMappings); + }; + + isResourceMappingValid = (resourceMapping: unknown): resourceMapping is ResourceMapping => { + return ( + typeof resourceMapping === 'object' && + resourceMapping !== null && + 'Destination' in resourceMapping && + typeof resourceMapping.Destination === 'object' && + resourceMapping.Destination !== null && + 'StackName' in resourceMapping.Destination && + typeof resourceMapping.Destination.StackName === 'string' && + 'LogicalResourceId' in resourceMapping.Destination && + typeof resourceMapping.Destination.LogicalResourceId === 'string' && + 'Source' in resourceMapping && + typeof resourceMapping.Source === 'object' && + resourceMapping.Source !== null && + 'StackName' in resourceMapping.Source && + typeof resourceMapping.Source.StackName === 'string' && + 'LogicalResourceId' in resourceMapping.Source && + typeof resourceMapping.Source.LogicalResourceId === 'string' + ); + }; +} diff --git a/packages/amplify-migration/src/commands/gen2/gen2_command.ts b/packages/amplify-migration/src/commands/gen2/gen2_command.ts new file mode 100644 index 00000000000..99de43e532e --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/gen2_command.ts @@ -0,0 +1,33 @@ +import { Argv, CommandModule } from 'yargs'; + +export type Gen2CommandOptions = Record; + +/** + * Command that starts Gen2 migration. + */ +export class Gen2Command implements CommandModule { + /** + * @inheritDoc + */ + readonly command: string; + + /** + * @inheritDoc + */ + readonly describe: string; + + constructor(private readonly subCommands: CommandModule[]) { + this.command = 'to-gen-2'; + this.describe = 'Migrates an Amplify Gen1 app to a Gen2 app'; + } + + builder = (yargs: Argv): Argv => { + return yargs.version(false).command(this.subCommands).strictCommands().recommendCommands(); + }; + handler = (): Promise => { + // CommandModule requires handler implementation. But this is never called if top level command + // is configured to require subcommand. + // Help is printed by default in that case before ever attempting to call handler. + throw new Error(`Top level gen2 handler should never be called`); + }; +} diff --git a/packages/amplify-migration/src/commands/gen2/gen2_command_factory.test.ts b/packages/amplify-migration/src/commands/gen2/gen2_command_factory.test.ts new file mode 100644 index 00000000000..44eb5bf3d45 --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/gen2_command_factory.test.ts @@ -0,0 +1,39 @@ +import yargs from 'yargs'; +import assert from 'node:assert'; +import { createGen2Command } from './gen2_command_factory'; +import { runCommandAsync } from '../../test-utils/command_runner'; + +/** + * Top level gen2 command's responsibility is to wire subcommands and delegate execution down the command chain. + * Therefore, testing primarily focuses on help output. + */ +describe('top level gen2 command', () => { + const gen2Command = createGen2Command(); + const parser = yargs().command(gen2Command); + + it('includes gen2 subcommands in help output', async () => { + const output = await runCommandAsync(parser, 'to-gen-2 --help'); + assert.match(output, /Commands:/); + assert.match(output, /Migrates an Amplify Gen1 app to a Gen2 app/); + }); + + it('fails if subcommand is not provided', async () => { + await assert.rejects( + () => runCommandAsync(parser, 'to-gen-2'), + (err: Error) => { + assert.match(err.message, /Top level gen2 handler should never be called/); + return true; + }, + ); + }); + + it('should throw if top level command handler is ever called', () => { + assert.throws( + () => gen2Command.handler({ $0: '', _: [] }), + (err: Error) => { + assert.equal(err.message, 'Top level gen2 handler should never be called'); + return true; + }, + ); + }); +}); diff --git a/packages/amplify-migration/src/commands/gen2/gen2_command_factory.ts b/packages/amplify-migration/src/commands/gen2/gen2_command_factory.ts new file mode 100644 index 00000000000..a521db75f70 --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/gen2_command_factory.ts @@ -0,0 +1,16 @@ +import { CommandModule } from 'yargs'; +import { Gen2Command } from './gen2_command'; +import { Gen2PrepareCommand } from './prepare/prepare_command'; +import { Gen2ExecuteCommand } from './execute/execute_command'; +import { Gen2RevertCommand } from './revert/revert_command'; + +export const createGen2Command = (): CommandModule => { + const gen2PrepareCommand = new Gen2PrepareCommand(); + const gen2ExecuteCommand = new Gen2ExecuteCommand(); + const gen2RevertCommand = new Gen2RevertCommand(); + return new Gen2Command([ + gen2PrepareCommand, + gen2ExecuteCommand as unknown as CommandModule, + gen2RevertCommand as unknown as CommandModule, + ]); +}; diff --git a/packages/amplify-migration/src/commands/gen2/prepare/prepare_command.test.ts b/packages/amplify-migration/src/commands/gen2/prepare/prepare_command.test.ts new file mode 100644 index 00000000000..7ec8fa4fc02 --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/prepare/prepare_command.test.ts @@ -0,0 +1,17 @@ +import { Gen2PrepareCommand } from './prepare_command'; +import { runCommandAsync } from '../../../test-utils/command_runner'; +import yargs from 'yargs'; + +const mockHandler = jest.fn(); +jest.mock('../../../command-handlers', () => ({ + ...jest.requireActual('../../../command-handlers'), + prepare: () => mockHandler(), +})); + +describe('PrepareCommand', () => { + it('should run command successfully', async () => { + const parser = yargs().command(new Gen2PrepareCommand()); + await runCommandAsync(parser, 'prepare'); + expect(mockHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/amplify-migration/src/commands/gen2/prepare/prepare_command.ts b/packages/amplify-migration/src/commands/gen2/prepare/prepare_command.ts new file mode 100644 index 00000000000..fdb33c34e85 --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/prepare/prepare_command.ts @@ -0,0 +1,31 @@ +import { Argv, CommandModule } from 'yargs'; +import { prepare } from '../../../command-handlers'; + +export type Gen2PrepareCommandOptions = Record; + +/** + * Command that prepares for Gen2 migration. + */ +export class Gen2PrepareCommand implements CommandModule { + /** + * @inheritDoc + */ + readonly command: string; + + /** + * @inheritDoc + */ + readonly describe: string; + + constructor() { + this.command = 'prepare'; + this.describe = 'Generates Amplify Gen2 code based on Gen1 configuration'; + } + + builder = (yargs: Argv): Argv => { + return yargs.version(false); + }; + handler = async (): Promise => { + await prepare(); + }; +} diff --git a/packages/amplify-migration/src/commands/gen2/revert/revert_command.test.ts b/packages/amplify-migration/src/commands/gen2/revert/revert_command.test.ts new file mode 100644 index 00000000000..21a0f8b2512 --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/revert/revert_command.test.ts @@ -0,0 +1,30 @@ +import { Gen2RevertCommand } from './revert_command'; +import { runCommandAsync } from '../../../test-utils/command_runner'; +import yargs, { CommandModule } from 'yargs'; +import assert from 'node:assert'; + +const mockHandler = jest.fn(); +jest.mock('../../../command-handlers', () => ({ + ...jest.requireActual('../../../command-handlers'), + revertGen2Migration: (from: string, to: string) => mockHandler(from, to), +})); + +describe('Gen2RevertCommand', () => { + it('should run command successfully', async () => { + const parser = yargs().command(new Gen2RevertCommand() as unknown as CommandModule); + await runCommandAsync(parser, 'revert --from foo --to bar'); + expect(mockHandler).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledWith('foo', 'bar'); + }); + + it('should fail command when arguments are not provided', async () => { + const parser = yargs().command(new Gen2RevertCommand() as unknown as CommandModule); + await assert.rejects( + () => runCommandAsync(parser, 'revert'), + (err: Error) => { + assert.equal(err.message, 'Missing required arguments: from, to'); + return true; + }, + ); + }); +}); diff --git a/packages/amplify-migration/src/commands/gen2/revert/revert_command.ts b/packages/amplify-migration/src/commands/gen2/revert/revert_command.ts new file mode 100644 index 00000000000..99be767b4d8 --- /dev/null +++ b/packages/amplify-migration/src/commands/gen2/revert/revert_command.ts @@ -0,0 +1,49 @@ +import { ArgumentsCamelCase, Argv, CommandModule } from 'yargs'; +import { revertGen2Migration } from '../../../command-handlers'; +import assert from 'node:assert'; + +export interface RevertCommandOptions { + from: string | undefined; + to: string | undefined; +} + +/** + * Command that executes stack refactor operation needed to move back resources to Gen1 stack. + */ +export class Gen2RevertCommand implements CommandModule { + /** + * @inheritDoc + */ + readonly command: string; + + /** + * @inheritDoc + */ + readonly describe: string; + + constructor() { + this.command = 'revert'; + this.describe = 'Moves Amplify Gen2 resources from a Gen2 stack into a Gen1 stack'; + } + + builder = (yargs: Argv): Argv => { + return yargs + .version(false) + .option('from', { + describe: 'Gen2 Amplify stack', + type: 'string', + demandOption: true, + }) + .option('to', { + describe: 'Gen1 Amplify stack', + type: 'string', + demandOption: true, + }); + }; + handler = async (args: ArgumentsCamelCase): Promise => { + const { from, to } = args; + assert(from); + assert(to); + await revertGen2Migration(from, to); + }; +} diff --git a/packages/amplify-migration/src/data_definition_fetcher.test.ts b/packages/amplify-migration/src/data_definition_fetcher.test.ts new file mode 100644 index 00000000000..a42ccde51f0 --- /dev/null +++ b/packages/amplify-migration/src/data_definition_fetcher.test.ts @@ -0,0 +1,216 @@ +import assert from 'node:assert'; +import { BackendEnvironment } from '@aws-sdk/client-amplify'; +import { Stack } from '@aws-sdk/client-cloudformation'; +import { BackendEnvironmentResolver } from './backend_environment_selector'; +import { DataDefinitionFetcher } from './data_definition_fetcher'; +import { AmplifyStackParser, AmplifyStacks } from './amplify_stack_parser'; +import { BackendDownloader } from './backend_downloader'; +import { fileOrDirectoryExists } from './directory_exists'; +import { pathManager } from '@aws-amplify/amplify-cli-core'; +import * as path from 'path'; +import fs from 'node:fs/promises'; +import glob from 'glob'; + +jest.mock('node:fs/promises'); +jest.mock('glob'); +jest.mock('@aws-amplify/amplify-cli-core'); +jest.mock('./directory_exists'); + +// Test constants +const MOCK_ROOT_DIR = '/mock/root/dir'; +const MOCK_CLOUD_BACKEND = '/mock/cloud/backend'; +const MOCK_APP_ID = 'mockAppId'; + +// Type definitions +interface MockBackendEnvironment extends BackendEnvironmentResolver { + getAllBackendEnvironments: () => Promise; + selectBackendEnvironment: () => Promise; +} + +// Test helpers +const createMockBackendResolver = (environmentName = 'dev'): MockBackendEnvironment => + ({ + getAllBackendEnvironments: async () => + [ + { + environmentName, + stackName: 'asdf', + }, + ] as BackendEnvironment[], + selectBackendEnvironment: async () => + ({ + environmentName, + stackName: 'asdf', + deploymentArtifacts: 'asdf', + } as BackendEnvironment), + } as MockBackendEnvironment); + +const createMockAmplifyStackParser = (stackData: Partial = {}): AmplifyStackParser => + ({ + getAmplifyStacks: async () => + ({ + dataStack: stackData as Stack, + } as AmplifyStacks), + } as unknown as AmplifyStackParser); + +describe('DataDefinitionFetcher', () => { + let dataDefinitionFetcher: DataDefinitionFetcher; + let backendEnvironmentResolver: BackendEnvironmentResolver; + let amplifyStackParser: AmplifyStackParser; + let ccbFetcher: BackendDownloader; + let mockAmplifyMeta: Record; + + beforeEach(() => { + // Setup basic mocks + backendEnvironmentResolver = new BackendEnvironmentResolver(MOCK_APP_ID, {} as any); + amplifyStackParser = new AmplifyStackParser({} as any); + ccbFetcher = { + getCurrentCloudBackend: jest.fn().mockResolvedValue(MOCK_CLOUD_BACKEND), + } as unknown as BackendDownloader; + + // Initialize fetcher + dataDefinitionFetcher = new DataDefinitionFetcher(backendEnvironmentResolver, ccbFetcher, amplifyStackParser); + + // Setup mock data + mockAmplifyMeta = { + api: { + mockResource: { + service: 'AppSync', + }, + }, + }; + + // Setup common mocks + (pathManager.findProjectRoot as jest.Mock).mockReturnValue(MOCK_ROOT_DIR); + (fileOrDirectoryExists as jest.Mock).mockResolvedValue(true); + + // Setup file system mocks + setupFileSystemMocks(); + }); + + const setupFileSystemMocks = () => { + (fs.stat as jest.Mock).mockImplementation((filePath: string) => ({ + isDirectory: () => filePath.includes('schema'), + })); + + (glob.sync as jest.Mock).mockImplementation((pattern: string) => + pattern.includes('schema') + ? [ + path.join(MOCK_ROOT_DIR, 'amplify/backend/api/mockResource/schema/schema1.graphql'), + path.join(MOCK_ROOT_DIR, 'amplify/backend/api/mockResource/schema/schema2.graphql'), + ] + : [], + ); + + (fs.readFile as jest.Mock).mockImplementation((filePath: string) => { + if (filePath.includes('amplify-meta.json')) { + return JSON.stringify(mockAmplifyMeta); + } + if (filePath.includes('schema1.graphql')) { + return 'type Query { getSchema1: String }'; + } + if (filePath.includes('schema2.graphql')) { + return 'type Mutation { updateSchema2: String }'; + } + return 'type Query { getSchema: String }'; + }); + }; + + describe('Table Mapping Tests', () => { + describe('with defined data stack', () => { + it('should correctly map cloudformation stack output to table mapping', async () => { + const mapping = { hello: 'world' }; + const mockResolver = createMockBackendResolver(); + const mockParser = createMockAmplifyStackParser({ + Outputs: [ + { + OutputKey: 'DataSourceMappingOutput', + OutputValue: JSON.stringify(mapping), + }, + ], + }); + + const fetcher = new DataDefinitionFetcher(mockResolver, ccbFetcher, mockParser); + const results = await fetcher.getDefinition(); + + assert(results?.tableMappings); + }); + + it('should return undefined mapping when JSON parsing fails', async () => { + const mockResolver = createMockBackendResolver(); + const mockParser = createMockAmplifyStackParser({ + Outputs: [ + { + OutputKey: 'DataSourceMappingOutput', + OutputValue: '(}', // Invalid JSON + }, + ], + }); + + const fetcher = new DataDefinitionFetcher(mockResolver, ccbFetcher, mockParser); + const results = await fetcher.getDefinition(); + + assert(results?.tableMappings); + assert.deepStrictEqual(results?.tableMappings, { dev: undefined }); + }); + }); + + describe('table mapping is not defined', () => { + it('return undefined for table mapping', async () => { + const mockResolver = createMockBackendResolver(); + const mockParser = createMockAmplifyStackParser({}); + + const fetcher = new DataDefinitionFetcher(mockResolver, ccbFetcher, mockParser); + const results = await fetcher.getDefinition(); + assert(results?.tableMappings); + assert.equal(JSON.stringify(results?.tableMappings), JSON.stringify({ dev: undefined })); + }); + }); + + describe('with undefined data stack', () => { + it('should handle undefined data stack gracefully', async () => { + const mockResolver = createMockBackendResolver(); + const mockParser = createMockAmplifyStackParser(undefined); + + const fetcher = new DataDefinitionFetcher(mockResolver, ccbFetcher, mockParser); + await assert.doesNotReject(fetcher.getDefinition); + const results = await fetcher.getDefinition(); + + assert(results?.tableMappings); + assert.deepStrictEqual(results?.tableMappings, { dev: undefined }); + }); + }); + }); + + describe('Schema Tests', () => { + it('should merge multiple schema files from schema folder', async () => { + const schema = await dataDefinitionFetcher.getSchema({ + mockResource: { service: 'AppSync' }, + }); + + expect(schema).toContain('type Query { getSchema1: String }'); + expect(schema).toContain('type Mutation { updateSchema2: String }'); + }); + + it('should return single schema.graphql content when only it exists', async () => { + (fs.stat as jest.Mock).mockImplementation(() => ({ + isDirectory: () => false, + })); + + const schema = await dataDefinitionFetcher.getSchema({ + mockResource: { service: 'AppSync' }, + }); + + expect(schema).toBe('type Query { getSchema: String }'); + }); + + it('should throw error when no schema is found', async () => { + (fs.stat as jest.Mock).mockRejectedValue(new Error('ENOENT')); + (fs.readFile as jest.Mock).mockRejectedValue(new Error('ENOENT')); + + await expect(dataDefinitionFetcher.getSchema({ mockResource: { service: 'AppSync' } })).rejects.toThrow( + 'No GraphQL schema found in the project', + ); + }); + }); +}); diff --git a/packages/amplify-migration/src/data_definition_fetcher.ts b/packages/amplify-migration/src/data_definition_fetcher.ts new file mode 100644 index 00000000000..67c1f5c0c23 --- /dev/null +++ b/packages/amplify-migration/src/data_definition_fetcher.ts @@ -0,0 +1,124 @@ +import path from 'node:path'; +import fs from 'node:fs/promises'; +import glob from 'glob'; +import assert from 'node:assert'; + +import { DataDefinition } from '@aws-amplify/amplify-gen2-codegen'; +import { AmplifyStackParser } from './amplify_stack_parser.js'; +import { BackendEnvironmentResolver } from './backend_environment_selector.js'; +import { BackendDownloader } from './backend_downloader.js'; +import { pathManager } from '@aws-amplify/amplify-cli-core'; +import { fileOrDirectoryExists } from './directory_exists'; + +const dataSourceMappingOutputKey = 'DataSourceMappingOutput'; + +export class DataDefinitionFetcher { + constructor( + private backendEnvironmentResolver: BackendEnvironmentResolver, + private ccbFetcher: BackendDownloader, + private amplifyStackClient: AmplifyStackParser, + ) {} + + private readJsonFile = async (filePath: string) => { + const contents = await fs.readFile(filePath, { encoding: 'utf8' }); + return JSON.parse(contents); + }; + + getSchema = async (apis: any): Promise => { + try { + let apiName; + + Object.keys(apis).forEach((api) => { + const apiObj = apis[api]; + if (apiObj.service === 'AppSync') { + apiName = api; + } + }); + + assert(apiName); + + const rootDir = pathManager.findProjectRoot(); + assert(rootDir); + const apiPath = path.join(rootDir, 'amplify', 'backend', 'api', apiName); + + // Check for schema folder first + const schemaFolderPath = path.join(apiPath, 'schema'); + try { + const stats = await fs.stat(schemaFolderPath); + if (stats.isDirectory()) { + // Read all .graphql files from schema folder + const graphqlFiles = glob.sync(path.join(schemaFolderPath, '*.graphql')); + if (graphqlFiles.length > 0) { + let mergedSchema = ''; + for (const file of graphqlFiles) { + const content = await fs.readFile(file, 'utf8'); + mergedSchema += content + '\n'; + } + return mergedSchema.trim(); + } + } + } catch (error) { + // Directory doesn't exist or other error, continue to check for schema.graphql + } + + // If schema folder doesn't exist or is empty, check for schema.graphql file + const schemaFilePath = path.join(apiPath, 'schema.graphql'); + try { + return await fs.readFile(schemaFilePath, 'utf8'); + } catch (error) { + throw new Error('No GraphQL schema found in the project'); + } + } catch (error) { + throw new Error(`Error reading GraphQL schema: ${error.message}`); + } + }; + + getDefinition = async (): Promise => { + const backendEnvironments = await this.backendEnvironmentResolver.getAllBackendEnvironments(); + + const backendEnvironment = await this.backendEnvironmentResolver.selectBackendEnvironment(); + if (!backendEnvironment?.deploymentArtifacts) return undefined; + + const currentCloudBackendDirectory = await this.ccbFetcher.getCurrentCloudBackend(backendEnvironment.deploymentArtifacts); + + const amplifyMetaPath = path.join(currentCloudBackendDirectory, 'amplify-meta.json'); + + if (!(await fileOrDirectoryExists(amplifyMetaPath))) { + throw new Error('Could not find amplify-meta.json'); + } + + const amplifyMeta = (await this.readJsonFile(amplifyMetaPath)) ?? {}; + + if ('api' in amplifyMeta && Object.keys(amplifyMeta.api).length > 0) { + const tableMappings = await Promise.all( + backendEnvironments.map(async (backendEnvironment) => { + if (!backendEnvironment?.stackName) { + return [backendEnvironment.environmentName, undefined]; + } + const amplifyStacks = await this.amplifyStackClient.getAmplifyStacks(backendEnvironment?.stackName); + if (amplifyStacks.dataStack) { + const tableMappingText = amplifyStacks.dataStack?.Outputs?.find((o) => o.OutputKey === dataSourceMappingOutputKey)?.OutputValue; + if (!tableMappingText) { + return [backendEnvironment.environmentName, undefined]; + } + try { + return [backendEnvironment.environmentName, JSON.parse(tableMappingText)]; + } catch (e) { + return [backendEnvironment.environmentName, undefined]; + } + } + return [backendEnvironment.environmentName, undefined]; + }), + ); + + const schema = await this.getSchema(amplifyMeta.api); + + return { + tableMappings: Object.fromEntries(tableMappings), + schema, + }; + } + + return undefined; + }; +} diff --git a/packages/amplify-migration/src/directory_exists.ts b/packages/amplify-migration/src/directory_exists.ts new file mode 100644 index 00000000000..479c1cfc60d --- /dev/null +++ b/packages/amplify-migration/src/directory_exists.ts @@ -0,0 +1,7 @@ +import fs from 'node:fs/promises'; +export const fileOrDirectoryExists = async (targetPath: string): Promise => { + return fs + .access(targetPath) + .then(() => true) + .catch(() => false); +}; diff --git a/packages/amplify-migration/src/error_handler.test.ts b/packages/amplify-migration/src/error_handler.test.ts new file mode 100644 index 00000000000..8169cd05abd --- /dev/null +++ b/packages/amplify-migration/src/error_handler.test.ts @@ -0,0 +1,72 @@ +import { generateCommandFailureHandler } from './error_handler'; +import { createMainParser } from './main_parser_factory'; +import { version } from '../package.json'; +import { printer } from './printer'; + +jest.mock('./printer.ts'); +jest.mock('./format', () => ({ + format: { + error: jest.fn((message) => message), + command: jest.fn((command) => command), + highlight: jest.fn((command) => command), + success: jest.fn((message) => message), + }, +})); + +jest.mock('yargs', () => { + const mockYargsInstance = { + version: jest.fn().mockReturnThis(), + options: jest.fn().mockReturnThis(), + strict: jest.fn().mockReturnThis(), + command: jest.fn().mockReturnThis(), + help: jest.fn().mockReturnThis(), + demandCommand: jest.fn().mockReturnThis(), + strictCommands: jest.fn().mockReturnThis(), + recommendCommands: jest.fn().mockReturnThis(), + fail: jest.fn().mockReturnThis(), + showHelp: jest.fn(), + }; + + return jest.fn(() => mockYargsInstance); +}); + +describe('Error handler tests', () => { + let errorHandler: (message: string, error: Error, debug?: boolean) => Promise; + beforeAll(() => { + const parser = createMainParser(version); + errorHandler = generateCommandFailureHandler(parser); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should print error without debug flag', async () => { + const printSpy = jest.spyOn(printer, 'print'); + const message = 'Unauthorized'; + const error = new Error('Unauthorized'); + expect.assertions(3); + try { + await errorHandler(message, error); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(printSpy).toBeCalledTimes(1); + expect(printSpy).toBeCalledWith(message); + } + }); + + it('should print error with debug flag', async () => { + const printSpy = jest.spyOn(printer, 'print'); + const message = 'Unauthorized'; + const error = new Error('Unauthorized'); + expect.assertions(4); + try { + await errorHandler(message, error, true); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(printSpy).toBeCalledTimes(2); + expect(printSpy).toHaveBeenNthCalledWith(1, message); + expect(printSpy.mock.calls[1][0]).toContain('at Promise'); + } + }); +}); diff --git a/packages/amplify-migration/src/error_handler.ts b/packages/amplify-migration/src/error_handler.ts new file mode 100644 index 00000000000..37d4b0c75f8 --- /dev/null +++ b/packages/amplify-migration/src/error_handler.ts @@ -0,0 +1,74 @@ +import { Argv } from 'yargs'; +import { extractSubCommands } from './extract_sub_commands'; +import { format } from './format'; +import { printer } from './printer'; + +type HandleErrorProps = { + error?: Error; + message?: string; + command?: string; + preambleMessage?: () => void; + debug: boolean; +}; + +/** + * Generates a function that is intended to be used as a callback to yargs.fail() + * All logic for actually handling errors should be delegated to handleError. + * + * For some reason the yargs object that is injected into the fail callback does not include all methods on the Argv type + * This generator allows us to inject the yargs parser into the callback so that we can call parser.exit() from the failure handler + * This prevents our top-level error handler from being invoked after the yargs error handler has already been invoked + */ +export const generateCommandFailureHandler = (parser: Argv): ((message: string, error: Error, debug?: boolean) => Promise) => { + /** + * Format error output when a command fails + * @param message error message set by the yargs:check validations + * @param error error thrown by yargs handler + * @param debug whether to print the stack trace + */ + const handleCommandFailure = async (message: string, error?: Error, debug = false) => { + const printHelp = () => { + printer.printNewLine(); + parser.showHelp(); + printer.printNewLine(); + }; + await handleErrorSafe({ + command: extractSubCommands(parser), + preambleMessage: printHelp, + error, + message, + debug, + }); + parser.exit(1, error || new Error(message)); + }; + return handleCommandFailure; +}; + +const handleErrorSafe = async (props: HandleErrorProps) => { + try { + await handleError(props); + } catch (e) { + console.error(e); + // no-op should gracefully exit + return; + } +}; + +const isUserForceClosePromptError = (err?: Error): boolean => { + return !!err && err?.message.includes('User force closed the prompt'); +}; + +const handleError = async ({ error, message, preambleMessage, debug }: HandleErrorProps) => { + // If yargs threw an error because the customer force-closed a prompt (ie Ctrl+C during a prompt, + // then the intent to exit the process is clear + if (isUserForceClosePromptError(error)) { + return; + } + + preambleMessage?.(); + + printer.print(format.error(message || String(error))); + if (debug && error && error.stack) { + printer.print(format.error(error.stack)); + } +}; diff --git a/packages/amplify-migration/src/extract_sub_commands.ts b/packages/amplify-migration/src/extract_sub_commands.ts new file mode 100644 index 00000000000..85731320345 --- /dev/null +++ b/packages/amplify-migration/src/extract_sub_commands.ts @@ -0,0 +1,8 @@ +import { Argv } from 'yargs'; + +/** + * If the parser finished processing arguments, attempt extracting subcommand information. + */ +export const extractSubCommands = (yargs: Argv): string | undefined => { + return yargs.parsed ? yargs.parsed.argv._.join(' ') : undefined; +}; diff --git a/packages/amplify-migration/src/format.ts b/packages/amplify-migration/src/format.ts new file mode 100644 index 00000000000..cd85566f959 --- /dev/null +++ b/packages/amplify-migration/src/format.ts @@ -0,0 +1,27 @@ +import * as os from 'node:os'; +import { cyan, green, red } from 'kleur/colors'; + +export class Format { + error = (error: string | Error | unknown): string => { + if (error instanceof Error) { + const message = red(`${error.name}: ${error.message}`); + + if (error.cause) { + return message + os.EOL + this.error(error.cause); + } + return message; + } else if (typeof error === 'string') { + return red(error); + } + try { + return red(JSON.stringify(error, null, 2)); + } catch (e) { + return red('Unknown error') + os.EOL + this.error(e); + } + }; + command = (command: string) => cyan(command); + highlight = (command: string) => cyan(command); + success = (message: string) => green(message); +} + +export const format = new Format(); diff --git a/packages/amplify-migration/src/logger.ts b/packages/amplify-migration/src/logger.ts new file mode 100644 index 00000000000..d0a21e34ce0 --- /dev/null +++ b/packages/amplify-migration/src/logger.ts @@ -0,0 +1,14 @@ +export interface AppContextLogger { + info(...logs: string[]): void; + warn(...logs: string[]): void; + error(...logs: string[]): void; + log(...logs: string[]): void; +} + +export class AppContextLogger { + constructor(private appId: string) {} + info = (...logs: string[]) => console.info(...logs, `App ID: ${this.appId}`); + warn = (...logs: string[]) => console.warn(...logs, `App ID: ${this.appId}`); + log = (...logs: string[]) => console.log(...logs, `App ID: ${this.appId}`); + error = (...logs: string[]) => console.error(...logs, `App ID: ${this.appId}`); +} diff --git a/packages/amplify-migration/src/main_parser_factory.test.ts b/packages/amplify-migration/src/main_parser_factory.test.ts new file mode 100644 index 00000000000..c52c7a77a36 --- /dev/null +++ b/packages/amplify-migration/src/main_parser_factory.test.ts @@ -0,0 +1,34 @@ +import { createMainParser } from './main_parser_factory'; +import { version } from '../package.json'; +import assert from 'node:assert'; +import { runCommandAsync } from './test-utils/command_runner'; + +describe('main parser', () => { + const parser = createMainParser(version); + + it('includes gen2 command in help output', async () => { + const output = await runCommandAsync(parser, '--help'); + assert.match(output, /Commands:/); + assert.match(output, /to-gen-2\s+Migrates an Amplify Gen1 app to a Gen2 app/); + }); + + it('shows version', async () => { + const output = await runCommandAsync(parser, '--version'); + assert.match(output, new RegExp(`${version}`)); + }); + + it('fails if command is not provided', async () => { + await assert.rejects( + () => runCommandAsync(parser, ''), + (err: any) => { + assert.match(err.message, /Not enough non-option arguments:/); + return true; + }, + ); + }); + + it('sets CLI_ENV to production', async () => { + await runCommandAsync(parser, '--version'); + assert(process.env.CLI_ENV === 'production'); + }); +}); diff --git a/packages/amplify-migration/src/main_parser_factory.ts b/packages/amplify-migration/src/main_parser_factory.ts new file mode 100644 index 00000000000..7d59f113601 --- /dev/null +++ b/packages/amplify-migration/src/main_parser_factory.ts @@ -0,0 +1,26 @@ +import { createGen2Command } from './commands/gen2/gen2_command_factory'; +import yargs, { Argv } from 'yargs'; + +// Set CLI_ENV to production to always stream usage data metrics to production +// This tool is not part of CLI binary and doesn't have preprod stages, +// so will always default to production. +process.env.CLI_ENV = 'production'; + +export const createMainParser = (libraryVersion: string): Argv => { + const parser = yargs() + .version(libraryVersion) + .options('debug', { + type: 'boolean', + default: false, + description: 'Print debug logs to the console', + }) + .strict() + .command(createGen2Command()) + .help() + .demandCommand() + .strictCommands() + .recommendCommands() + .fail(false); + + return parser; +}; diff --git a/packages/amplify-migration/src/migrate.ts b/packages/amplify-migration/src/migrate.ts new file mode 100644 index 00000000000..c89b54631b3 --- /dev/null +++ b/packages/amplify-migration/src/migrate.ts @@ -0,0 +1,16 @@ +#!/usr/bin/env node +import { generateCommandFailureHandler } from './error_handler.js'; +import { createMainParser } from './main_parser_factory.js'; +import { hideBin } from 'yargs/helpers'; +import { version } from '../package.json'; + +const libraryVersion = version; +const parser = createMainParser(libraryVersion); +const errorHandler = generateCommandFailureHandler(parser); +parser.parseAsync(hideBin(process.argv)).catch(async (e) => { + if (e instanceof Error) { + await errorHandler(e.message, e, isDebugFlagEnabled()); + } +}); + +const isDebugFlagEnabled = (): boolean => process.argv.includes('--debug'); diff --git a/packages/amplify-migration/src/printer.ts b/packages/amplify-migration/src/printer.ts new file mode 100644 index 00000000000..f6afd6c16da --- /dev/null +++ b/packages/amplify-migration/src/printer.ts @@ -0,0 +1,143 @@ +import { WriteStream } from 'node:tty'; +import { EOL } from 'os'; + +export class Printer { + // Properties for ellipsis animation + private timer: ReturnType | null = null; + private timerSet = false; + /** + * Spinner frames + */ + private spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + constructor( + private readonly minimumLogLevel: LogLevel, + private readonly stdout: WriteStream | NodeJS.WritableStream = process.stdout, + private readonly stderr: WriteStream | NodeJS.WritableStream = process.stderr, + private readonly refreshRate: number = 30, + ) {} + + /** + * Prints a given message to output stream followed by a newline. + */ + print = (message: string) => { + this.stdout.write(message); + this.printNewLine(); + }; + + /** + * Prints a new line to output stream + */ + printNewLine = () => { + this.stdout.write(EOL); + }; + + /** + * Logs a message to the output stream at the given log level followed by a newline + */ + log(message: string, level: LogLevel = LogLevel.INFO) { + const doLogMessage = level <= this.minimumLogLevel; + + if (!doLogMessage) { + return; + } + + const logMessage = this.minimumLogLevel === LogLevel.DEBUG ? `[${LogLevel[level]}] ${new Date().toISOString()}: ${message}` : message; + + if (level === LogLevel.ERROR) { + this.stderr.write(logMessage); + } else { + this.stdout.write(logMessage); + } + + this.printNewLine(); + } + + /** + * Logs a message with animated spinner. + * If stdout is not a TTY, the message is logged at the info level without a spinner + */ + async indicateProgress(message: string, callback: () => Promise) { + try { + this.startAnimatingSpinner(message); + await callback(); + } finally { + this.stopAnimatingSpinner(); + } + } + + /** + * Writes escape sequence to stdout + */ + private writeEscapeSequence(action: EscapeSequence) { + if (!this.isTTY()) { + return; + } + + this.stdout.write(action); + } + + /** + * Checks if the environment is TTY + */ + private isTTY() { + return this.stdout instanceof WriteStream && this.stdout.isTTY; + } + + /** + * Starts animating spinner with a message. + */ + private startAnimatingSpinner(message: string) { + if (this.timerSet) { + throw new Error('Timer is already set to animate spinner, stop the current running timer before starting a new one.'); + } + + if (!this.isTTY()) { + this.log(message, LogLevel.INFO); + return; + } + + let frameIndex = 0; + this.timerSet = true; + this.writeEscapeSequence(EscapeSequence.HIDE_CURSOR); + this.timer = setInterval(() => { + this.writeEscapeSequence(EscapeSequence.CLEAR_LINE); + this.writeEscapeSequence(EscapeSequence.MOVE_CURSOR_TO_START); + const frame = this.spinnerFrames[frameIndex]; + this.stdout.write(`${frame} ${message}`); + frameIndex = (frameIndex + 1) % this.spinnerFrames.length; + }, this.refreshRate); + } + + /** + * Stops animating spinner. + */ + private stopAnimatingSpinner() { + if (!this.isTTY()) { + return; + } + if (this.timer) { + clearInterval(this.timer); + } + this.timerSet = false; + this.writeEscapeSequence(EscapeSequence.CLEAR_LINE); + this.writeEscapeSequence(EscapeSequence.MOVE_CURSOR_TO_START); + this.writeEscapeSequence(EscapeSequence.SHOW_CURSOR); + } +} + +export enum LogLevel { + ERROR = 0, + INFO = 1, + DEBUG = 2, +} + +enum EscapeSequence { + CLEAR_LINE = '\x1b[2K', + MOVE_CURSOR_TO_START = '\x1b[0G', + SHOW_CURSOR = '\x1b[?25h', + HIDE_CURSOR = '\x1b[?25l', +} + +const minimumLogLevel = process.argv.includes('--debug') ? LogLevel.DEBUG : LogLevel.INFO; + +export const printer = new Printer(minimumLogLevel); diff --git a/packages/amplify-migration/src/test-utils/command_runner.ts b/packages/amplify-migration/src/test-utils/command_runner.ts new file mode 100644 index 00000000000..09b2b43774a --- /dev/null +++ b/packages/amplify-migration/src/test-utils/command_runner.ts @@ -0,0 +1,15 @@ +import { ArgumentsCamelCase, Argv } from 'yargs'; + +export async function runCommandAsync(parser: Argv, command: string): Promise { + return new Promise((res, rej) => { + parser + .parseAsync(command, (error: Error | undefined, __: ArgumentsCamelCase, output: string) => { + if (error) { + rej(error); + } else { + res(output); + } + }) + .catch(rej); + }); +} diff --git a/packages/amplify-migration/tsconfig.json b/packages/amplify-migration/tsconfig.json new file mode 100644 index 00000000000..f58af5c0a98 --- /dev/null +++ b/packages/amplify-migration/tsconfig.json @@ -0,0 +1,22 @@ +{ + "include": ["src/**/*"], + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib" + }, + "references": [ + { + "path": "../amplify-gen1-codegen-auth-adapter" + }, + { + "path": "../amplify-gen1-codegen-storage-adapter" + }, + { + "path": "../amplify-gen1-codegen-function-adapter" + }, + { + "path": "../amplify-gen2-codegen" + } + ] +} diff --git a/packages/amplify-nodejs-function-runtime-provider/CHANGELOG.md b/packages/amplify-nodejs-function-runtime-provider/CHANGELOG.md index c4c554278c3..d09022abb1d 100644 --- a/packages/amplify-nodejs-function-runtime-provider/CHANGELOG.md +++ b/packages/amplify-nodejs-function-runtime-provider/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.5.30-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-nodejs-function-runtime-provider@2.5.29-next-7.0...amplify-nodejs-function-runtime-provider@2.5.30-next-11.0) (2025-05-01) + +**Note:** Version bump only for package amplify-nodejs-function-runtime-provider + + + + + ## [2.5.29](https://github.com/aws-amplify/amplify-cli/compare/amplify-nodejs-function-runtime-provider@2.5.28...amplify-nodejs-function-runtime-provider@2.5.29) (2025-04-17) **Note:** Version bump only for package amplify-nodejs-function-runtime-provider diff --git a/packages/amplify-nodejs-function-runtime-provider/package.json b/packages/amplify-nodejs-function-runtime-provider/package.json index ebc97ffdeb3..3e616c2d925 100644 --- a/packages/amplify-nodejs-function-runtime-provider/package.json +++ b/packages/amplify-nodejs-function-runtime-provider/package.json @@ -1,6 +1,6 @@ { "name": "amplify-nodejs-function-runtime-provider", - "version": "2.5.29", + "version": "2.5.30-next-11.0", "description": "Provides functionality related to functions in NodeJS on AWS", "repository": { "type": "git", @@ -27,7 +27,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "execa": "^5.1.1", "exit": "^0.1.2", diff --git a/packages/amplify-nodejs-function-template-provider/CHANGELOG.md b/packages/amplify-nodejs-function-template-provider/CHANGELOG.md index e53c35678e7..65adfa199ce 100644 --- a/packages/amplify-nodejs-function-template-provider/CHANGELOG.md +++ b/packages/amplify-nodejs-function-template-provider/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.10.15-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-nodejs-function-template-provider@2.10.14-next-7.0...@aws-amplify/amplify-nodejs-function-template-provider@2.10.15-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-nodejs-function-template-provider + + + + + ## [2.10.14](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-nodejs-function-template-provider@2.10.13...@aws-amplify/amplify-nodejs-function-template-provider@2.10.14) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-nodejs-function-template-provider diff --git a/packages/amplify-nodejs-function-template-provider/package.json b/packages/amplify-nodejs-function-template-provider/package.json index 3568f308ecc..fdc223a563e 100644 --- a/packages/amplify-nodejs-function-template-provider/package.json +++ b/packages/amplify-nodejs-function-template-provider/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-nodejs-function-template-provider", - "version": "2.10.14", + "version": "2.10.15-next-11.0", "description": "Node JS templates supplied by the Amplify Team", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "@aws-amplify/amplify-prompts": "2.8.6", "graphql-transformer-core": "^8.2.17", diff --git a/packages/amplify-opensearch-simulator/CHANGELOG.md b/packages/amplify-opensearch-simulator/CHANGELOG.md index 4de5ee6f442..75ff359d8ae 100644 --- a/packages/amplify-opensearch-simulator/CHANGELOG.md +++ b/packages/amplify-opensearch-simulator/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.7.20-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-opensearch-simulator@1.7.19-next-7.0...@aws-amplify/amplify-opensearch-simulator@1.7.20-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-opensearch-simulator + + + + + ## [1.7.19](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-opensearch-simulator@1.7.18...@aws-amplify/amplify-opensearch-simulator@1.7.19) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-opensearch-simulator diff --git a/packages/amplify-opensearch-simulator/package.json b/packages/amplify-opensearch-simulator/package.json index 36fb0e3c684..727eaa1a33e 100644 --- a/packages/amplify-opensearch-simulator/package.json +++ b/packages/amplify-opensearch-simulator/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-opensearch-simulator", - "version": "1.7.19", + "version": "1.7.20-next-11.0", "description": "Opensearch local simulator", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "aws-sdk": "^2.1464.0", "detect-port": "^1.3.0", diff --git a/packages/amplify-provider-awscloudformation/CHANGELOG.md b/packages/amplify-provider-awscloudformation/CHANGELOG.md index cc3803d8038..d9ab510a8e8 100644 --- a/packages/amplify-provider-awscloudformation/CHANGELOG.md +++ b/packages/amplify-provider-awscloudformation/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.11.8-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-provider-awscloudformation@8.11.7-next-7.0...@aws-amplify/amplify-provider-awscloudformation@8.11.8-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [8.11.7](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-provider-awscloudformation@8.11.6...@aws-amplify/amplify-provider-awscloudformation@8.11.7) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-provider-awscloudformation diff --git a/packages/amplify-provider-awscloudformation/package.json b/packages/amplify-provider-awscloudformation/package.json index 652f58ddbf0..071c119b95b 100644 --- a/packages/amplify-provider-awscloudformation/package.json +++ b/packages/amplify-provider-awscloudformation/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-provider-awscloudformation", - "version": "8.11.7", + "version": "8.11.8-next-11.0", "description": "AWS CloudFormation Provider", "repository": { "type": "git", @@ -28,13 +28,13 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-category-custom": "3.1.28", - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-category-custom": "3.1.29-next-11.0", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-cli-logger": "1.3.8", - "@aws-amplify/amplify-environment-parameters": "1.9.19", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "@aws-amplify/amplify-util-import": "2.8.3", - "@aws-amplify/cli-extensibility-helper": "3.0.38", + "@aws-amplify/cli-extensibility-helper": "3.0.39-next-11.0", "@aws-amplify/graphql-transformer-core": "^2.11.1", "@aws-amplify/graphql-transformer-interfaces": "^3.12.0", "amplify-codegen": "^4.10.3", diff --git a/packages/amplify-python-function-runtime-provider/CHANGELOG.md b/packages/amplify-python-function-runtime-provider/CHANGELOG.md index 6d509b924bf..fc1a2717ce6 100644 --- a/packages/amplify-python-function-runtime-provider/CHANGELOG.md +++ b/packages/amplify-python-function-runtime-provider/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.4.52-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-python-function-runtime-provider@2.4.51-next-7.0...amplify-python-function-runtime-provider@2.4.52-next-11.0) (2025-05-01) + +**Note:** Version bump only for package amplify-python-function-runtime-provider + + + + + ## [2.4.51](https://github.com/aws-amplify/amplify-cli/compare/amplify-python-function-runtime-provider@2.4.50...amplify-python-function-runtime-provider@2.4.51) (2025-04-17) **Note:** Version bump only for package amplify-python-function-runtime-provider diff --git a/packages/amplify-python-function-runtime-provider/package.json b/packages/amplify-python-function-runtime-provider/package.json index faacc2ff78b..07689163baf 100644 --- a/packages/amplify-python-function-runtime-provider/package.json +++ b/packages/amplify-python-function-runtime-provider/package.json @@ -1,6 +1,6 @@ { "name": "amplify-python-function-runtime-provider", - "version": "2.4.51", + "version": "2.4.52-next-11.0", "description": "Provides functionality related to functions in Python on AWS", "repository": { "type": "git", @@ -25,7 +25,7 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-function-plugin-interface": "1.12.1", "execa": "^5.1.1", "glob": "^7.2.0", diff --git a/packages/amplify-storage-simulator/CHANGELOG.md b/packages/amplify-storage-simulator/CHANGELOG.md index 9af056ef3e9..5c2e8312f26 100644 --- a/packages/amplify-storage-simulator/CHANGELOG.md +++ b/packages/amplify-storage-simulator/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.11.7-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-storage-simulator@1.11.7-beta-latest.0...amplify-storage-simulator@1.11.7-next-11.0) (2025-05-01) + + +### Bug Fixes + +* migration test file extension ([74b58eb](https://github.com/aws-amplify/amplify-cli/commit/74b58ebf250b9a0ffbf7469fae76ca758a7b077a)) + + + + + +## [1.11.7-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-storage-simulator@1.11.6...amplify-storage-simulator@1.11.7-beta-latest.0) (2025-02-12) + +**Note:** Version bump only for package amplify-storage-simulator + + + + + ## [1.11.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-storage-simulator@1.11.5...amplify-storage-simulator@1.11.6) (2025-01-02) **Note:** Version bump only for package amplify-storage-simulator diff --git a/packages/amplify-storage-simulator/package.json b/packages/amplify-storage-simulator/package.json index 36a731a3e9c..099a673e0de 100644 --- a/packages/amplify-storage-simulator/package.json +++ b/packages/amplify-storage-simulator/package.json @@ -1,6 +1,6 @@ { "name": "amplify-storage-simulator", - "version": "1.11.6", + "version": "1.11.7-next-11.0", "description": "An S3 simulator to test S3 APIs", "repository": { "type": "git", diff --git a/packages/amplify-util-mock/CHANGELOG.md b/packages/amplify-util-mock/CHANGELOG.md index d0d92d72217..66f2a3b4faf 100644 --- a/packages/amplify-util-mock/CHANGELOG.md +++ b/packages/amplify-util-mock/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [5.10.16-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-util-mock@5.10.15-next-7.0...@aws-amplify/amplify-util-mock@5.10.16-next-11.0) (2025-05-01) + + +### Bug Fixes + +* pin CDK version again ([#14186](https://github.com/aws-amplify/amplify-cli/issues/14186)) ([ef7f5eb](https://github.com/aws-amplify/amplify-cli/commit/ef7f5ebe0136049865554c6ec0235abc9b816fea)) + + + + + ## [5.10.15](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-util-mock@5.10.14...@aws-amplify/amplify-util-mock@5.10.15) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-util-mock diff --git a/packages/amplify-util-mock/package.json b/packages/amplify-util-mock/package.json index 36040a4e158..a529d8de2ec 100644 --- a/packages/amplify-util-mock/package.json +++ b/packages/amplify-util-mock/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-util-mock", - "version": "5.10.15", + "version": "5.10.16-next-11.0", "description": "amplify cli plugin providing local testing", "repository": { "type": "git", @@ -31,17 +31,17 @@ "extract-api": "ts-node ../../scripts/extract-api.ts" }, "dependencies": { - "@aws-amplify/amplify-appsync-simulator": "2.16.12", - "@aws-amplify/amplify-category-function": "5.7.14", - "@aws-amplify/amplify-cli-core": "4.4.1", - "@aws-amplify/amplify-environment-parameters": "1.9.19", - "@aws-amplify/amplify-opensearch-simulator": "1.7.19", + "@aws-amplify/amplify-appsync-simulator": "2.16.13-next-11.0", + "@aws-amplify/amplify-category-function": "5.7.15-next-11.0", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", + "@aws-amplify/amplify-environment-parameters": "1.9.20-next-11.0", + "@aws-amplify/amplify-opensearch-simulator": "1.7.20-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", - "@aws-amplify/amplify-provider-awscloudformation": "8.11.7", + "@aws-amplify/amplify-provider-awscloudformation": "8.11.8-next-11.0", "@hapi/topo": "^5.0.0", "amplify-codegen": "^4.10.3", - "amplify-dynamodb-simulator": "2.9.23", - "amplify-storage-simulator": "1.11.6", + "amplify-dynamodb-simulator": "2.9.24-next-11.0", + "amplify-storage-simulator": "1.11.7-next-11.0", "axios": "^1.6.7", "chokidar": "^3.5.3", "detect-port": "^1.3.0", @@ -76,7 +76,7 @@ "@types/node": "^12.12.6", "@types/semver": "^7.1.0", "@types/which": "^1.3.2", - "amplify-nodejs-function-runtime-provider": "2.5.29", + "amplify-nodejs-function-runtime-provider": "2.5.30-next-11.0", "aws-appsync": "^4.1.4", "aws-cdk-lib": "~2.189.1", "aws-sdk": "^2.1464.0", diff --git a/packages/amplify-util-uibuilder/CHANGELOG.md b/packages/amplify-util-uibuilder/CHANGELOG.md index 3f96a2b735a..6aba67634c0 100644 --- a/packages/amplify-util-uibuilder/CHANGELOG.md +++ b/packages/amplify-util-uibuilder/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.14.20-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-util-uibuilder@1.14.19-next-7.0...@aws-amplify/amplify-util-uibuilder@1.14.20-next-11.0) (2025-05-01) + +**Note:** Version bump only for package @aws-amplify/amplify-util-uibuilder + + + + + ## [1.14.19](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-util-uibuilder@1.14.18...@aws-amplify/amplify-util-uibuilder@1.14.19) (2025-04-17) **Note:** Version bump only for package @aws-amplify/amplify-util-uibuilder diff --git a/packages/amplify-util-uibuilder/package.json b/packages/amplify-util-uibuilder/package.json index 5b418b18bd1..be09e2b6f7b 100644 --- a/packages/amplify-util-uibuilder/package.json +++ b/packages/amplify-util-uibuilder/package.json @@ -1,6 +1,6 @@ { "name": "@aws-amplify/amplify-util-uibuilder", - "version": "1.14.19", + "version": "1.14.20-next-11.0", "description": "", "main": "lib/index.js", "scripts": { @@ -15,7 +15,7 @@ }, "dependencies": { "@aws-amplify/amplify-category-api": "^5.15.0", - "@aws-amplify/amplify-cli-core": "4.4.1", + "@aws-amplify/amplify-cli-core": "4.4.2-next-11.0", "@aws-amplify/amplify-prompts": "2.8.6", "@aws-amplify/codegen-ui": "2.14.2", "@aws-amplify/codegen-ui-react": "2.14.2", diff --git a/packages/amplify-velocity-template/CHANGELOG.md b/packages/amplify-velocity-template/CHANGELOG.md index 7daca97b0d9..f18e0a139e0 100644 --- a/packages/amplify-velocity-template/CHANGELOG.md +++ b/packages/amplify-velocity-template/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.4.16-next-11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-velocity-template@1.4.16-beta-latest.0...amplify-velocity-template@1.4.16-next-11.0) (2025-05-01) + + +### Bug Fixes + +* migration test file extension ([74b58eb](https://github.com/aws-amplify/amplify-cli/commit/74b58ebf250b9a0ffbf7469fae76ca758a7b077a)) + + + + + +## [1.4.16-beta-latest.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-velocity-template@1.4.15...amplify-velocity-template@1.4.16-beta-latest.0) (2025-02-12) + +**Note:** Version bump only for package amplify-velocity-template + + + + + ## [1.4.15](https://github.com/aws-amplify/amplify-cli/compare/amplify-velocity-template@1.4.12...amplify-velocity-template@1.4.15) (2025-01-02) diff --git a/packages/amplify-velocity-template/package.json b/packages/amplify-velocity-template/package.json index e39f9830c2e..f074daf0dff 100644 --- a/packages/amplify-velocity-template/package.json +++ b/packages/amplify-velocity-template/package.json @@ -1,6 +1,6 @@ { "name": "amplify-velocity-template", - "version": "1.4.15", + "version": "1.4.16-next-11.0", "description": "Velocity Template Language(VTL) for JavaScript", "repository": { "type": "git", diff --git a/shared-scripts.sh b/shared-scripts.sh index 77b63ba8448..3426a7c9e66 100644 --- a/shared-scripts.sh +++ b/shared-scripts.sh @@ -350,6 +350,26 @@ function _runE2ETestsLinux { _loadTestAccountCredentials retry runE2eTestCb } + +function _runGen2MigrationE2ETestsLinux { + echo RUN Gen2 Migration E2E Tests Linux + _loadE2ECache + _install_packaged_cli_linux + # select region + export CLI_REGION=$(yarn ts-node ./scripts/select-region-for-e2e-test.ts) + echo "Test will run in $CLI_REGION" + # verify installation + which amplify + amplify version + source .circleci/local_publish_helpers_codebuild.sh && startLocalRegistry "$CODEBUILD_SRC_DIR/.circleci/verdaccio.yaml" + setNpmRegistryUrlToLocal + changeNpmGlobalPath + amplify version + cd packages/amplify-migration-e2e + _loadTestAccountCredentials + retry runGen2MigrationsE2ETestCb +} + function _unassumeTestAccountCredentials { echo "Unassume Role" unset AWS_ACCESS_KEY_ID diff --git a/yarn.lock b/yarn.lock index 32020af4235..09c92ad7f71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -75,14 +75,14 @@ __metadata: languageName: node linkType: hard -"@aws-amplify/amplify-app@5.0.42, @aws-amplify/amplify-app@workspace:packages/amplify-app": +"@aws-amplify/amplify-app@5.0.43-next-11.0, @aws-amplify/amplify-app@workspace:packages/amplify-app": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-app@workspace:packages/amplify-app" dependencies: "@aws-amplify/amplify-frontend-android": 3.5.8 "@aws-amplify/amplify-frontend-flutter": 1.4.7 - "@aws-amplify/amplify-frontend-ios": 3.7.12 - "@aws-amplify/amplify-frontend-javascript": 3.10.22 + "@aws-amplify/amplify-frontend-ios": 3.7.13-next-11.0 + "@aws-amplify/amplify-frontend-javascript": 3.10.23-next-11.0 "@types/glob": ^7.1.1 chalk: ^4.1.1 execa: ^5.1.1 @@ -100,12 +100,12 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-appsync-simulator@2.16.12, @aws-amplify/amplify-appsync-simulator@workspace:packages/amplify-appsync-simulator": +"@aws-amplify/amplify-appsync-simulator@2.16.13-next-11.0, @aws-amplify/amplify-appsync-simulator@workspace:packages/amplify-appsync-simulator": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-appsync-simulator@workspace:packages/amplify-appsync-simulator" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-graphiql-explorer": 2.6.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-graphiql-explorer": 2.6.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@graphql-tools/schema": ^8.3.1 "@graphql-tools/utils": ^8.5.1 @@ -113,7 +113,7 @@ __metadata: "@types/express": ^4.17.3 "@types/node": ^12.12.6 "@types/ws": ^8.2.2 - amplify-velocity-template: 1.4.15 + amplify-velocity-template: 1.4.16-next-11.0 aws-sdk: ^2.1464.0 chalk: ^4.1.1 cors: ^2.8.5 @@ -139,12 +139,12 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-analytics@5.0.41, @aws-amplify/amplify-category-analytics@workspace:packages/amplify-category-analytics": +"@aws-amplify/amplify-category-analytics@5.0.42-next-11.0, @aws-amplify/amplify-category-analytics@workspace:packages/amplify-category-analytics": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-analytics@workspace:packages/amplify-category-analytics" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 fs-extra: ^8.1.0 uuid: ^8.3.2 @@ -206,16 +206,16 @@ __metadata: languageName: node linkType: hard -"@aws-amplify/amplify-category-auth@3.7.21, @aws-amplify/amplify-category-auth@workspace:packages/amplify-category-auth": +"@aws-amplify/amplify-category-auth@3.7.22-next-11.0, @aws-amplify/amplify-category-auth@workspace:packages/amplify-category-auth": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-auth@workspace:packages/amplify-category-auth" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@aws-amplify/amplify-prompts": 2.8.6 "@aws-amplify/amplify-util-import": 2.8.3 - "@aws-amplify/cli-extensibility-helper": 3.0.38 + "@aws-amplify/cli-extensibility-helper": 3.0.39-next-11.0 "@aws-sdk/client-cognito-identity-provider": 3.624.0 "@aws-sdk/client-iam": 3.624.0 "@types/mime-types": ^2.1.1 @@ -241,11 +241,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-custom@3.1.28, @aws-amplify/amplify-category-custom@workspace:packages/amplify-category-custom": +"@aws-amplify/amplify-category-custom@3.1.29-next-11.0, @aws-amplify/amplify-category-custom@workspace:packages/amplify-category-custom": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-custom@workspace:packages/amplify-category-custom" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@types/lodash": ^4.14.149 aws-cdk-lib: ~2.189.1 @@ -259,12 +259,12 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-function@5.7.14, @aws-amplify/amplify-category-function@workspace:packages/amplify-category-function": +"@aws-amplify/amplify-category-function@5.7.15-next-11.0, @aws-amplify/amplify-category-function@workspace:packages/amplify-category-function": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-function@workspace:packages/amplify-category-function" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@aws-amplify/amplify-prompts": 2.8.6 "@types/folder-hash": ^4.0.1 @@ -287,11 +287,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-geo@3.5.21, @aws-amplify/amplify-category-geo@workspace:packages/amplify-category-geo": +"@aws-amplify/amplify-category-geo@3.5.22-next-11.0, @aws-amplify/amplify-category-geo@workspace:packages/amplify-category-geo": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-geo@workspace:packages/amplify-category-geo" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@aws-sdk/client-location": 3.624.0 ajv: ^6.12.6 @@ -306,11 +306,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-hosting@3.5.41, @aws-amplify/amplify-category-hosting@workspace:packages/amplify-category-hosting": +"@aws-amplify/amplify-category-hosting@3.5.42-next-11.0, @aws-amplify/amplify-category-hosting@workspace:packages/amplify-category-hosting": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-hosting@workspace:packages/amplify-category-hosting" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 chalk: ^4.1.1 fs-extra: ^8.1.0 @@ -322,11 +322,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-interactions@5.1.34, @aws-amplify/amplify-category-interactions@workspace:packages/amplify-category-interactions": +"@aws-amplify/amplify-category-interactions@5.1.35-next-11.0, @aws-amplify/amplify-category-interactions@workspace:packages/amplify-category-interactions": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-interactions@workspace:packages/amplify-category-interactions" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 fs-extra: ^8.1.0 fuzzy: ^0.1.3 @@ -334,14 +334,14 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-notifications@2.26.31, @aws-amplify/amplify-category-notifications@workspace:packages/amplify-category-notifications": +"@aws-amplify/amplify-category-notifications@2.26.32-next-11.0, @aws-amplify/amplify-category-notifications@workspace:packages/amplify-category-notifications": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-notifications@workspace:packages/amplify-category-notifications" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 - "@aws-amplify/amplify-provider-awscloudformation": 8.11.7 + "@aws-amplify/amplify-provider-awscloudformation": 8.11.8-next-11.0 aws-sdk: ^2.1464.0 chalk: ^4.1.1 fs-extra: ^8.1.0 @@ -352,11 +352,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-predictions@5.5.21, @aws-amplify/amplify-category-predictions@workspace:packages/amplify-category-predictions": +"@aws-amplify/amplify-category-predictions@5.5.22-next-11.0, @aws-amplify/amplify-category-predictions@workspace:packages/amplify-category-predictions": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-predictions@workspace:packages/amplify-category-predictions" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@aws-sdk/client-rekognition": 3.624.0 aws-sdk: ^2.1464.0 @@ -366,15 +366,15 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-category-storage@5.5.20, @aws-amplify/amplify-category-storage@workspace:packages/amplify-category-storage": +"@aws-amplify/amplify-category-storage@5.5.21-next-11.0, @aws-amplify/amplify-category-storage@workspace:packages/amplify-category-storage": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-category-storage@workspace:packages/amplify-category-storage" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@aws-amplify/amplify-util-import": 2.8.3 - "@aws-amplify/cli-extensibility-helper": 3.0.38 + "@aws-amplify/cli-extensibility-helper": 3.0.39-next-11.0 amplify-headless-interface: 1.17.7 amplify-util-headless-input: 1.9.18 aws-cdk-lib: ~2.189.1 @@ -391,7 +391,7 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-cli-core@4.4.1, @aws-amplify/amplify-cli-core@workspace:packages/amplify-cli-core": +"@aws-amplify/amplify-cli-core@4.4.2-next-11.0, @aws-amplify/amplify-cli-core@workspace:packages/amplify-cli-core": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-cli-core@workspace:packages/amplify-cli-core" dependencies: @@ -461,12 +461,12 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-console-hosting@2.5.38, @aws-amplify/amplify-console-hosting@workspace:packages/amplify-console-hosting": +"@aws-amplify/amplify-console-hosting@2.5.39-next-11.0, @aws-amplify/amplify-console-hosting@workspace:packages/amplify-console-hosting": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-console-hosting@workspace:packages/amplify-console-hosting" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 archiver: ^5.3.0 chalk: ^4.1.1 cli-table3: ^0.6.0 @@ -484,8 +484,8 @@ __metadata: version: 0.0.0-use.local resolution: "@aws-amplify/amplify-console-integration-tests@workspace:packages/amplify-console-integration-tests" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-e2e-core": 5.7.4 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-e2e-core": 5.7.5-next-11.0 "@types/ini": ^1.3.30 aws-sdk: ^2.1464.0 dotenv: ^8.2.0 @@ -500,13 +500,13 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-container-hosting@2.8.18, @aws-amplify/amplify-container-hosting@workspace:packages/amplify-container-hosting": +"@aws-amplify/amplify-container-hosting@2.8.19-next-11.0, @aws-amplify/amplify-container-hosting@workspace:packages/amplify-container-hosting": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-container-hosting@workspace:packages/amplify-container-hosting" dependencies: "@aws-amplify/amplify-category-api": ^5.15.0 - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 fs-extra: ^8.1.0 inquirer: ^7.3.3 mime-types: ^2.1.26 @@ -514,11 +514,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-dotnet-function-template-provider@2.7.4, @aws-amplify/amplify-dotnet-function-template-provider@workspace:packages/amplify-dotnet-function-template-provider": +"@aws-amplify/amplify-dotnet-function-template-provider@2.7.5-next-11.0, @aws-amplify/amplify-dotnet-function-template-provider@workspace:packages/amplify-dotnet-function-template-provider": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-dotnet-function-template-provider@workspace:packages/amplify-dotnet-function-template-provider" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@types/inquirer": ^6.5.0 "@types/lodash": ^4.14.149 @@ -527,11 +527,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-e2e-core@5.7.4, @aws-amplify/amplify-e2e-core@workspace:packages/amplify-e2e-core": +"@aws-amplify/amplify-e2e-core@5.7.5-next-11.0, @aws-amplify/amplify-e2e-core@workspace:packages/amplify-e2e-core": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-e2e-core@workspace:packages/amplify-e2e-core" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-sdk/client-sts": 3.624.0 "@aws-sdk/credential-providers": 3.624.0 "@types/glob": ^7.1.1 @@ -559,11 +559,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-environment-parameters@1.9.19, @aws-amplify/amplify-environment-parameters@workspace:packages/amplify-environment-parameters": +"@aws-amplify/amplify-environment-parameters@1.9.20-next-11.0, @aws-amplify/amplify-environment-parameters@workspace:packages/amplify-environment-parameters": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-environment-parameters@workspace:packages/amplify-environment-parameters" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 ajv: ^6.12.6 aws-sdk: ^2.1464.0 lodash: ^4.17.21 @@ -592,11 +592,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-frontend-ios@3.7.12, @aws-amplify/amplify-frontend-ios@workspace:packages/amplify-frontend-ios": +"@aws-amplify/amplify-frontend-ios@3.7.13-next-11.0, @aws-amplify/amplify-frontend-ios@workspace:packages/amplify-frontend-ios": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-frontend-ios@workspace:packages/amplify-frontend-ios" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 execa: ^5.1.1 fs-extra: ^8.1.0 graphql-config: ^2.2.1 @@ -604,11 +604,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-frontend-javascript@3.10.22, @aws-amplify/amplify-frontend-javascript@workspace:packages/amplify-frontend-javascript": +"@aws-amplify/amplify-frontend-javascript@3.10.23-next-11.0, @aws-amplify/amplify-frontend-javascript@workspace:packages/amplify-frontend-javascript": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-frontend-javascript@workspace:packages/amplify-frontend-javascript" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@babel/core": ^7.23.2 "@babel/plugin-transform-modules-commonjs": 7.10.4 chalk: ^4.1.1 @@ -633,6 +633,75 @@ __metadata: languageName: node linkType: hard +"@aws-amplify/amplify-gen1-codegen-auth-adapter@0.1.0-next-9.0, @aws-amplify/amplify-gen1-codegen-auth-adapter@workspace:packages/amplify-gen1-codegen-auth-adapter": + version: 0.0.0-use.local + resolution: "@aws-amplify/amplify-gen1-codegen-auth-adapter@workspace:packages/amplify-gen1-codegen-auth-adapter" + dependencies: + "@aws-amplify/amplify-gen2-codegen": 0.1.0-next-9.0 + "@aws-amplify/auth-construct": ^1.1.5 + "@aws-sdk/client-amplify": ^3.592.0 + "@aws-sdk/client-cognito-identity": ^3.592.0 + "@aws-sdk/client-cognito-identity-provider": ^3.592.0 + jest: ^29.5.0 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + +"@aws-amplify/amplify-gen1-codegen-data-adapter@workspace:packages/amplify-gen1-codegen-data-adapter": + version: 0.0.0-use.local + resolution: "@aws-amplify/amplify-gen1-codegen-data-adapter@workspace:packages/amplify-gen1-codegen-data-adapter" + dependencies: + "@aws-amplify/amplify-gen2-codegen": 0.1.0-next-9.0 + "@aws-amplify/auth-construct": ^1.1.5 + "@aws-sdk/client-amplify": ^3.592.0 + "@aws-sdk/client-cloudformation": ^3.592.0 + "@aws-sdk/client-cognito-identity": ^3.592.0 + "@aws-sdk/client-cognito-identity-provider": ^3.592.0 + jest: ^29.5.0 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + +"@aws-amplify/amplify-gen1-codegen-function-adapter@0.1.0-next-9.0, @aws-amplify/amplify-gen1-codegen-function-adapter@workspace:packages/amplify-gen1-codegen-function-adapter": + version: 0.0.0-use.local + resolution: "@aws-amplify/amplify-gen1-codegen-function-adapter@workspace:packages/amplify-gen1-codegen-function-adapter" + dependencies: + "@aws-amplify/amplify-gen2-codegen": 0.1.0-next-9.0 + "@aws-sdk/client-lambda": ^3.637.0 + jest: ^29.5.0 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + +"@aws-amplify/amplify-gen1-codegen-storage-adapter@0.1.0-next-9.0, @aws-amplify/amplify-gen1-codegen-storage-adapter@workspace:packages/amplify-gen1-codegen-storage-adapter": + version: 0.0.0-use.local + resolution: "@aws-amplify/amplify-gen1-codegen-storage-adapter@workspace:packages/amplify-gen1-codegen-storage-adapter" + dependencies: + "@aws-amplify/amplify-gen2-codegen": 0.1.0-next-9.0 + "@aws-sdk/client-amplify": ^3.592.0 + "@aws-sdk/client-cognito-identity": ^3.592.0 + "@aws-sdk/client-cognito-identity-provider": ^3.592.0 + "@aws-sdk/client-s3": ^3.592.0 + jest: ^29.5.0 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + +"@aws-amplify/amplify-gen2-codegen@0.1.0-next-9.0, @aws-amplify/amplify-gen2-codegen@workspace:packages/amplify-gen2-codegen": + version: 0.0.0-use.local + resolution: "@aws-amplify/amplify-gen2-codegen@workspace:packages/amplify-gen2-codegen" + dependencies: + "@aws-amplify/auth-construct": ^1.1.5 + "@aws-sdk/client-cognito-identity-provider": ^3.592.0 + "@aws-sdk/client-lambda": ^3.651.1 + "@aws-sdk/client-s3": ^3.651.1 + "@types/node": ^20.14.2 + aws-cdk-lib: ~2.187.0 + jest: ^29.5.0 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + "@aws-amplify/amplify-go-function-template-provider@1.4.8, @aws-amplify/amplify-go-function-template-provider@workspace:packages/amplify-go-function-template-provider": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-go-function-template-provider@workspace:packages/amplify-go-function-template-provider" @@ -644,7 +713,7 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-graphiql-explorer@2.6.1, @aws-amplify/amplify-graphiql-explorer@workspace:packages/amplify-graphiql-explorer": +"@aws-amplify/amplify-graphiql-explorer@2.6.2-next-11.0, @aws-amplify/amplify-graphiql-explorer@workspace:packages/amplify-graphiql-explorer": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-graphiql-explorer@workspace:packages/amplify-graphiql-explorer" dependencies: @@ -723,12 +792,33 @@ __metadata: languageName: unknown linkType: soft +"@aws-amplify/amplify-migration-e2e@workspace:packages/amplify-migration-e2e": + version: 0.0.0-use.local + resolution: "@aws-amplify/amplify-migration-e2e@workspace:packages/amplify-migration-e2e" + dependencies: + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-e2e-core": 5.7.5-next-11.0 + "@aws-amplify/amplify-gen2-codegen": 0.1.0-next-9.0 + "@aws-sdk/client-appsync": ^3.666.0 + "@aws-sdk/client-cloudcontrol": ^3.658.1 + "@aws-sdk/client-cloudformation": ^3.787.0 + "@aws-sdk/client-cognito-identity": ^3.670.0 + "@aws-sdk/client-s3": ^3.674.0 + execa: ^5.1.1 + fs-extra: ^8.1.0 + jest: ^29.5.0 + lodash: ^4.17.21 + ts-node: ^10.4.0 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + "@aws-amplify/amplify-migration-tests@workspace:packages/amplify-migration-tests": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-migration-tests@workspace:packages/amplify-migration-tests" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-e2e-core": 5.7.4 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-e2e-core": 5.7.5-next-11.0 "@aws-cdk/cloudformation-diff": ~2.68.0 "@aws-sdk/client-s3": 3.624.0 amplify-headless-interface: 1.17.7 @@ -746,11 +836,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-nodejs-function-template-provider@2.10.14, @aws-amplify/amplify-nodejs-function-template-provider@workspace:packages/amplify-nodejs-function-template-provider": +"@aws-amplify/amplify-nodejs-function-template-provider@2.10.15-next-11.0, @aws-amplify/amplify-nodejs-function-template-provider@workspace:packages/amplify-nodejs-function-template-provider": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-nodejs-function-template-provider@workspace:packages/amplify-nodejs-function-template-provider" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@aws-amplify/amplify-prompts": 2.8.6 "@types/fs-extra": ^8.0.1 @@ -761,11 +851,11 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-opensearch-simulator@1.7.19, @aws-amplify/amplify-opensearch-simulator@workspace:packages/amplify-opensearch-simulator": +"@aws-amplify/amplify-opensearch-simulator@1.7.20-next-11.0, @aws-amplify/amplify-opensearch-simulator@workspace:packages/amplify-opensearch-simulator": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-opensearch-simulator@workspace:packages/amplify-opensearch-simulator" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@types/node": ^12.12.6 "@types/openpgp": ^4.4.19 @@ -795,17 +885,17 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-provider-awscloudformation@8.11.7, @aws-amplify/amplify-provider-awscloudformation@workspace:packages/amplify-provider-awscloudformation": +"@aws-amplify/amplify-provider-awscloudformation@8.11.8-next-11.0, @aws-amplify/amplify-provider-awscloudformation@workspace:packages/amplify-provider-awscloudformation": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-provider-awscloudformation@workspace:packages/amplify-provider-awscloudformation" dependencies: - "@aws-amplify/amplify-category-custom": 3.1.28 - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-category-custom": 3.1.29-next-11.0 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-cli-logger": 1.3.8 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@aws-amplify/amplify-util-import": 2.8.3 - "@aws-amplify/cli-extensibility-helper": 3.0.38 + "@aws-amplify/cli-extensibility-helper": 3.0.39-next-11.0 "@aws-amplify/graphql-transformer-core": ^2.11.1 "@aws-amplify/graphql-transformer-interfaces": ^3.12.0 "@types/columnify": ^1.5.0 @@ -868,18 +958,18 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-util-mock@5.10.15, @aws-amplify/amplify-util-mock@workspace:packages/amplify-util-mock": +"@aws-amplify/amplify-util-mock@5.10.16-next-11.0, @aws-amplify/amplify-util-mock@workspace:packages/amplify-util-mock": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-util-mock@workspace:packages/amplify-util-mock" dependencies: - "@aws-amplify/amplify-appsync-simulator": 2.16.12 - "@aws-amplify/amplify-category-function": 5.7.14 - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-appsync-simulator": 2.16.13-next-11.0 + "@aws-amplify/amplify-category-function": 5.7.15-next-11.0 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 - "@aws-amplify/amplify-opensearch-simulator": 1.7.19 + "@aws-amplify/amplify-opensearch-simulator": 1.7.20-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 - "@aws-amplify/amplify-provider-awscloudformation": 8.11.7 + "@aws-amplify/amplify-provider-awscloudformation": 8.11.8-next-11.0 "@aws-amplify/graphql-auth-transformer": ^3.6.12 "@aws-amplify/graphql-default-value-transformer": ^2.3.20 "@aws-amplify/graphql-function-transformer": ^2.1.32 @@ -902,9 +992,9 @@ __metadata: "@types/semver": ^7.1.0 "@types/which": ^1.3.2 amplify-codegen: ^4.10.3 - amplify-dynamodb-simulator: 2.9.23 - amplify-nodejs-function-runtime-provider: 2.5.29 - amplify-storage-simulator: 1.11.6 + amplify-dynamodb-simulator: 2.9.24-next-11.0 + amplify-nodejs-function-runtime-provider: 2.5.30-next-11.0 + amplify-storage-simulator: 1.11.7-next-11.0 aws-appsync: ^4.1.4 aws-cdk-lib: ~2.189.1 aws-sdk: ^2.1464.0 @@ -938,12 +1028,12 @@ __metadata: languageName: unknown linkType: soft -"@aws-amplify/amplify-util-uibuilder@1.14.19, @aws-amplify/amplify-util-uibuilder@workspace:packages/amplify-util-uibuilder": +"@aws-amplify/amplify-util-uibuilder@1.14.20-next-11.0, @aws-amplify/amplify-util-uibuilder@workspace:packages/amplify-util-uibuilder": version: 0.0.0-use.local resolution: "@aws-amplify/amplify-util-uibuilder@workspace:packages/amplify-util-uibuilder" dependencies: "@aws-amplify/amplify-category-api": ^5.15.0 - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 "@aws-amplify/appsync-modelgen-plugin": ^2.6.0 "@aws-amplify/codegen-ui": 2.14.2 @@ -1039,6 +1129,21 @@ __metadata: languageName: node linkType: hard +"@aws-amplify/auth-construct@npm:^1.1.5": + version: 1.8.0 + resolution: "@aws-amplify/auth-construct@npm:1.8.0" + dependencies: + "@aws-amplify/backend-output-schemas": ^1.6.0 + "@aws-amplify/backend-output-storage": ^1.3.0 + "@aws-amplify/plugin-types": ^1.10.0 + "@aws-sdk/util-arn-parser": ^3.723.0 + peerDependencies: + aws-cdk-lib: ^2.180.0 + constructs: ^10.0.0 + checksum: 708b218dc5316fef9f9670c0199c626c157163614334716dfd0c539ec3d28010c12496fe708e5f19b087bef814e250a119456de3ceae83784909cae36cba5905 + languageName: node + linkType: hard + "@aws-amplify/auth@npm:5.6.10": version: 5.6.10 resolution: "@aws-amplify/auth@npm:5.6.10" @@ -1052,6 +1157,28 @@ __metadata: languageName: node linkType: hard +"@aws-amplify/backend-output-schemas@npm:^1.6.0": + version: 1.6.0 + resolution: "@aws-amplify/backend-output-schemas@npm:1.6.0" + peerDependencies: + zod: ^3.22.2 + checksum: 9187461013069651e40e0e049dc58318a92f12352cf6dabba59284945fe7b605ce8ec5c938b05e90963605c113fb8ae591ea8b364e42e460dfc6d480877828c7 + languageName: node + linkType: hard + +"@aws-amplify/backend-output-storage@npm:^1.3.0": + version: 1.3.0 + resolution: "@aws-amplify/backend-output-storage@npm:1.3.0" + dependencies: + "@aws-amplify/backend-output-schemas": ^1.6.0 + "@aws-amplify/platform-core": ^1.8.0 + "@aws-amplify/plugin-types": ^1.10.0 + peerDependencies: + aws-cdk-lib: ^2.180.0 + checksum: 7f4559377644022f61b319549317139cae3e32fa63823a0a375d79f23d9074d6b200fef227b2819d3438d350602155cf4c991b992b3924e234b5273e5b3e5ca7 + languageName: node + linkType: hard + "@aws-amplify/cache@npm:5.1.16": version: 5.1.16 resolution: "@aws-amplify/cache@npm:5.1.16" @@ -1062,52 +1189,52 @@ __metadata: languageName: node linkType: hard -"@aws-amplify/cli-extensibility-helper@3.0.38, @aws-amplify/cli-extensibility-helper@workspace:packages/amplify-cli-extensibility-helper": +"@aws-amplify/cli-extensibility-helper@3.0.39-next-11.0, @aws-amplify/cli-extensibility-helper@workspace:packages/amplify-cli-extensibility-helper": version: 0.0.0-use.local resolution: "@aws-amplify/cli-extensibility-helper@workspace:packages/amplify-cli-extensibility-helper" dependencies: - "@aws-amplify/amplify-category-custom": 3.1.28 - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-category-custom": 3.1.29-next-11.0 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 aws-cdk-lib: ~2.189.1 languageName: unknown linkType: soft -"@aws-amplify/cli-internal@13.0.1, @aws-amplify/cli-internal@workspace:packages/amplify-cli": +"@aws-amplify/cli-internal@13.0.2-next-11.0, @aws-amplify/cli-internal@workspace:packages/amplify-cli": version: 0.0.0-use.local resolution: "@aws-amplify/cli-internal@workspace:packages/amplify-cli" dependencies: - "@aws-amplify/amplify-app": 5.0.42 - "@aws-amplify/amplify-category-analytics": 5.0.41 + "@aws-amplify/amplify-app": 5.0.43-next-11.0 + "@aws-amplify/amplify-category-analytics": 5.0.42-next-11.0 "@aws-amplify/amplify-category-api": ^5.15.0 - "@aws-amplify/amplify-category-auth": 3.7.21 - "@aws-amplify/amplify-category-custom": 3.1.28 - "@aws-amplify/amplify-category-function": 5.7.14 - "@aws-amplify/amplify-category-geo": 3.5.21 - "@aws-amplify/amplify-category-hosting": 3.5.41 - "@aws-amplify/amplify-category-interactions": 5.1.34 - "@aws-amplify/amplify-category-notifications": 2.26.31 - "@aws-amplify/amplify-category-predictions": 5.5.21 - "@aws-amplify/amplify-category-storage": 5.5.20 - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-category-auth": 3.7.22-next-11.0 + "@aws-amplify/amplify-category-custom": 3.1.29-next-11.0 + "@aws-amplify/amplify-category-function": 5.7.15-next-11.0 + "@aws-amplify/amplify-category-geo": 3.5.22-next-11.0 + "@aws-amplify/amplify-category-hosting": 3.5.42-next-11.0 + "@aws-amplify/amplify-category-interactions": 5.1.35-next-11.0 + "@aws-amplify/amplify-category-notifications": 2.26.32-next-11.0 + "@aws-amplify/amplify-category-predictions": 5.5.22-next-11.0 + "@aws-amplify/amplify-category-storage": 5.5.21-next-11.0 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-cli-logger": 1.3.8 "@aws-amplify/amplify-cli-shared-interfaces": 1.2.5 - "@aws-amplify/amplify-console-hosting": 2.5.38 - "@aws-amplify/amplify-container-hosting": 2.8.18 - "@aws-amplify/amplify-dotnet-function-template-provider": 2.7.4 - "@aws-amplify/amplify-environment-parameters": 1.9.19 + "@aws-amplify/amplify-console-hosting": 2.5.39-next-11.0 + "@aws-amplify/amplify-container-hosting": 2.8.19-next-11.0 + "@aws-amplify/amplify-dotnet-function-template-provider": 2.7.5-next-11.0 + "@aws-amplify/amplify-environment-parameters": 1.9.20-next-11.0 "@aws-amplify/amplify-frontend-android": 3.5.8 "@aws-amplify/amplify-frontend-flutter": 1.4.7 - "@aws-amplify/amplify-frontend-ios": 3.7.12 - "@aws-amplify/amplify-frontend-javascript": 3.10.22 + "@aws-amplify/amplify-frontend-ios": 3.7.13-next-11.0 + "@aws-amplify/amplify-frontend-javascript": 3.10.23-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@aws-amplify/amplify-go-function-template-provider": 1.4.8 - "@aws-amplify/amplify-nodejs-function-template-provider": 2.10.14 + "@aws-amplify/amplify-nodejs-function-template-provider": 2.10.15-next-11.0 "@aws-amplify/amplify-prompts": 2.8.6 - "@aws-amplify/amplify-provider-awscloudformation": 8.11.7 + "@aws-amplify/amplify-provider-awscloudformation": 8.11.8-next-11.0 "@aws-amplify/amplify-python-function-template-provider": 1.4.7 "@aws-amplify/amplify-util-import": 2.8.3 - "@aws-amplify/amplify-util-mock": 5.10.15 - "@aws-amplify/amplify-util-uibuilder": 1.14.19 + "@aws-amplify/amplify-util-mock": 5.10.16-next-11.0 + "@aws-amplify/amplify-util-uibuilder": 1.14.20-next-11.0 "@aws-cdk/cloudformation-diff": ~2.68.0 "@types/archiver": ^5.3.1 "@types/columnify": ^1.5.1 @@ -1123,13 +1250,13 @@ __metadata: "@types/treeify": ^1.0.0 "@types/update-notifier": ^5.1.0 amplify-codegen: ^4.10.3 - amplify-dotnet-function-runtime-provider: 2.1.4 - amplify-go-function-runtime-provider: 2.3.51 + amplify-dotnet-function-runtime-provider: 2.1.5-next-11.0 + amplify-go-function-runtime-provider: 2.3.52-next-11.0 amplify-headless-interface: 1.17.7 - amplify-java-function-runtime-provider: 2.3.51 + amplify-java-function-runtime-provider: 2.3.52-next-11.0 amplify-java-function-template-provider: 1.5.24 - amplify-nodejs-function-runtime-provider: 2.5.29 - amplify-python-function-runtime-provider: 2.4.51 + amplify-nodejs-function-runtime-provider: 2.5.30-next-11.0 + amplify-python-function-runtime-provider: 2.4.52-next-11.0 aws-cdk-lib: ~2.189.1 aws-sdk: ^2.1464.0 chalk: ^4.1.1 @@ -1172,7 +1299,7 @@ __metadata: version: 0.0.0-use.local resolution: "@aws-amplify/cli@workspace:packages/amplify-cli-npm" dependencies: - "@aws-amplify/cli-internal": 13.0.1 + "@aws-amplify/cli-internal": 13.0.2-next-11.0 "@types/tar": ^6.1.1 axios: ^1.6.7 rimraf: ^3.0.2 @@ -1685,6 +1812,56 @@ __metadata: languageName: node linkType: hard +"@aws-amplify/migrate-template-gen@0.1.0-next-11.0, @aws-amplify/migrate-template-gen@workspace:packages/amplify-migration-template-gen": + version: 0.0.0-use.local + resolution: "@aws-amplify/migrate-template-gen@workspace:packages/amplify-migration-template-gen" + dependencies: + "@aws-sdk/client-cloudformation": ^3.744.0 + "@aws-sdk/client-cognito-identity-provider": ^3.592.0 + "@aws-sdk/client-ssm": ^3.592.0 + "@jest/globals": ^29.7.0 + jest: ^29.5.0 + ora: ^4.0.3 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + +"@aws-amplify/migrate@workspace:packages/amplify-migration": + version: 0.0.0-use.local + resolution: "@aws-amplify/migrate@workspace:packages/amplify-migration" + dependencies: + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-gen1-codegen-auth-adapter": 0.1.0-next-9.0 + "@aws-amplify/amplify-gen1-codegen-function-adapter": 0.1.0-next-9.0 + "@aws-amplify/amplify-gen1-codegen-storage-adapter": 0.1.0-next-9.0 + "@aws-amplify/amplify-gen2-codegen": 0.1.0-next-9.0 + "@aws-amplify/cli-internal": 13.0.2-next-11.0 + "@aws-amplify/migrate-template-gen": 0.1.0-next-11.0 + "@aws-sdk/client-amplify": ^3.592.0 + "@aws-sdk/client-amplifybackend": ^3.592.0 + "@aws-sdk/client-cloudformation": ^3.592.0 + "@aws-sdk/client-cloudwatch-events": ^3.592.0 + "@aws-sdk/client-cognito-identity": ^3.592.0 + "@aws-sdk/client-cognito-identity-provider": ^3.592.0 + "@aws-sdk/client-lambda": ^3.637.0 + "@aws-sdk/client-s3": ^3.592.0 + "@aws-sdk/client-ssm": ^3.592.0 + "@aws-sdk/client-sts": ^3.658.1 + "@types/node": ^20.14.2 + "@types/unzipper": ^0.10.9 + glob: ^7.2.0 + jest: ^29.7.0 + kleur: ^4.1.5 + ora: ^4.0.3 + typescript: ^5.4.5 + unzipper: ^0.12.1 + uuid: ^8.3.2 + yargs: ^17.7.2 + bin: + migrate: lib/migrate.js + languageName: unknown + linkType: soft + "@aws-amplify/notifications@npm:1.6.10": version: 1.6.10 resolution: "@aws-amplify/notifications@npm:1.6.10" @@ -1698,6 +1875,38 @@ __metadata: languageName: node linkType: hard +"@aws-amplify/platform-core@npm:^1.8.0": + version: 1.8.0 + resolution: "@aws-amplify/platform-core@npm:1.8.0" + dependencies: + "@aws-amplify/plugin-types": ^1.10.0 + "@aws-sdk/client-sts": ^3.750.0 + is-ci: ^4.1.0 + lodash.mergewith: ^4.6.2 + lodash.snakecase: ^4.1.1 + semver: ^7.6.3 + uuid: ^11.1.0 + zod: ^3.22.2 + peerDependencies: + aws-cdk-lib: ^2.180.0 + constructs: ^10.0.0 + checksum: 1d0c6af8f50cbb86411b525e2f42ba5b20add7f97ac8c871ca04fe584340209976d95ab33d0cea58f11ebd2f808fe9c3616c99e8f0565896d95d68605a44c719 + languageName: node + linkType: hard + +"@aws-amplify/plugin-types@npm:^1.10.0": + version: 1.10.0 + resolution: "@aws-amplify/plugin-types@npm:1.10.0" + dependencies: + "@aws-cdk/toolkit-lib": 0.2.0 + peerDependencies: + "@aws-sdk/types": ^3.734.0 + aws-cdk-lib: ^2.180.0 + constructs: ^10.0.0 + checksum: 8d7af3ff1553441e984866855e4cd20c300c517509292148e311e63fa239b2ea20f4c68ac4b7437e63ff8913a070566ec61a71d3a3accb28927526dbed7441a8 + languageName: node + linkType: hard + "@aws-amplify/predictions@npm:5.5.10": version: 5.5.10 resolution: "@aws-amplify/predictions@npm:5.5.10" @@ -1758,9 +1967,9 @@ __metadata: linkType: hard "@aws-cdk/asset-awscli-v1@npm:^2.2.229": - version: 2.2.230 - resolution: "@aws-cdk/asset-awscli-v1@npm:2.2.230" - checksum: 4a43bdaffaabed33f4fba2cca46dfea6dac22e8379b61f15c6d5f2560b54eb5c2b10da123c0bf328292998cd5dc1dcab69a8599a064299384e69d5f8f39afd33 + version: 2.2.233 + resolution: "@aws-cdk/asset-awscli-v1@npm:2.2.233" + checksum: 17883bff00f41071ee1f1cd0051d30a1ebc6034aa8ab66183c4430dd8361d7e8708e6294768e8626e041bd0936fe60ed4dfb59cdda994a19e4d6d2930eb4db72 languageName: node linkType: hard @@ -1781,6 +1990,16 @@ __metadata: languageName: node linkType: hard +"@aws-cdk/aws-service-spec@npm:^0.1.69": + version: 0.1.70 + resolution: "@aws-cdk/aws-service-spec@npm:0.1.70" + dependencies: + "@aws-cdk/service-spec-types": ^0.0.136 + "@cdklabs/tskb": ^0.0.3 + checksum: e36619e13ac01db2b41c3add247202f19227db02646e053a379b70c08f8bd044084db71341f572cb063269a8e90e4c51c1c0bfcdd801dfe5e35e7244eb693ebb + languageName: node + linkType: hard + "@aws-cdk/cfnspec@npm:2.68.0": version: 2.68.0 resolution: "@aws-cdk/cfnspec@npm:2.68.0" @@ -1791,6 +2010,16 @@ __metadata: languageName: node linkType: hard +"@aws-cdk/cloud-assembly-schema@npm:>=43.5.0, @aws-cdk/cloud-assembly-schema@npm:^43.1.0": + version: 43.5.0 + resolution: "@aws-cdk/cloud-assembly-schema@npm:43.5.0" + dependencies: + jsonschema: ~1.4.1 + semver: ^7.7.1 + checksum: 82e1b3f725482410aa8ecad3f5822966d260c79ea168d948799c71faa1514b5cad2914749618c8547c45891de6bec3cbb395623cff3eac82a1da1e7af1b1cd4b + languageName: node + linkType: hard + "@aws-cdk/cloud-assembly-schema@npm:^41.0.0": version: 41.2.0 resolution: "@aws-cdk/cloud-assembly-schema@npm:41.2.0" @@ -1801,6 +2030,21 @@ __metadata: languageName: node linkType: hard +"@aws-cdk/cloudformation-diff@npm:^2.180.0": + version: 2.181.1 + resolution: "@aws-cdk/cloudformation-diff@npm:2.181.1" + dependencies: + "@aws-cdk/aws-service-spec": ^0.1.69 + "@aws-cdk/service-spec-types": ^0.0.135 + chalk: ^4 + diff: ^7.0.0 + fast-deep-equal: ^3.1.3 + string-width: ^4 + table: ^6 + checksum: fb5475ac12cf4fa885fd73c50e5875427e5ea6f445ac987a1080a869f957f1519d3f741916dc9cca3d9242c3de5e20d50979ffe296c69ee8f7663244fdaee0f5 + languageName: node + linkType: hard + "@aws-cdk/cloudformation-diff@npm:~2.68.0": version: 2.68.0 resolution: "@aws-cdk/cloudformation-diff@npm:2.68.0" @@ -1815,6 +2059,104 @@ __metadata: languageName: node linkType: hard +"@aws-cdk/cx-api@npm:^2.187.0, @aws-cdk/cx-api@npm:^2.190.0": + version: 2.192.0 + resolution: "@aws-cdk/cx-api@npm:2.192.0" + dependencies: + semver: ^7.7.1 + peerDependencies: + "@aws-cdk/cloud-assembly-schema": ">=41.0.0" + checksum: cac266bfa61e6621cfcc9634cc5fed2aa68525a1d10bb4d591d37c4b5323e27317c48249b63b4630ca1ff74c2ede35129db91f6e06d4d1427e46da3d5ccaf235 + languageName: node + linkType: hard + +"@aws-cdk/region-info@npm:^2.187.0": + version: 2.192.0 + resolution: "@aws-cdk/region-info@npm:2.192.0" + checksum: faf9f78c70ac7d9b9ec3498cf3dd30c0d7de718c372303f92acc3a48538d6ab5fbff3215c005532a61eff875ed58870851b92664b4467ea5550cb778a23497ce + languageName: node + linkType: hard + +"@aws-cdk/service-spec-types@npm:^0.0.135": + version: 0.0.135 + resolution: "@aws-cdk/service-spec-types@npm:0.0.135" + dependencies: + "@cdklabs/tskb": ^0.0.3 + checksum: 24d47a2f343ccc381694aff9f91e4f6ff99902fc465c07c06d4254a32703a76b8bcb1f110bf57c7f55ec7825222f74761963d493df0224a96c28fbfc1a6d7217 + languageName: node + linkType: hard + +"@aws-cdk/service-spec-types@npm:^0.0.136": + version: 0.0.136 + resolution: "@aws-cdk/service-spec-types@npm:0.0.136" + dependencies: + "@cdklabs/tskb": ^0.0.3 + checksum: a83a395a9adbc4d82e77c4d689416aafdee88f554cbe5875730fd08825bc0b8d98ba274e029f6e5ec81a7a7fb5018d57055143d308e10b0eabf5395779064efa + languageName: node + linkType: hard + +"@aws-cdk/toolkit-lib@npm:0.2.0": + version: 0.2.0 + resolution: "@aws-cdk/toolkit-lib@npm:0.2.0" + dependencies: + "@aws-cdk/cloud-assembly-schema": ^43.1.0 + "@aws-cdk/cloudformation-diff": ^2.180.0 + "@aws-cdk/cx-api": ^2.187.0 + "@aws-cdk/region-info": ^2.187.0 + "@aws-sdk/client-appsync": ^3 + "@aws-sdk/client-cloudcontrol": ^3 + "@aws-sdk/client-cloudformation": ^3 + "@aws-sdk/client-cloudwatch-logs": ^3 + "@aws-sdk/client-codebuild": ^3 + "@aws-sdk/client-ec2": ^3 + "@aws-sdk/client-ecr": ^3 + "@aws-sdk/client-ecs": ^3 + "@aws-sdk/client-elastic-load-balancing-v2": ^3 + "@aws-sdk/client-iam": ^3 + "@aws-sdk/client-kms": ^3 + "@aws-sdk/client-lambda": ^3 + "@aws-sdk/client-route-53": ^3 + "@aws-sdk/client-s3": ^3 + "@aws-sdk/client-secrets-manager": ^3 + "@aws-sdk/client-sfn": ^3 + "@aws-sdk/client-ssm": ^3 + "@aws-sdk/client-sts": ^3 + "@aws-sdk/credential-providers": ^3 + "@aws-sdk/ec2-metadata-service": ^3 + "@aws-sdk/lib-storage": ^3 + "@smithy/middleware-endpoint": ^4.1.0 + "@smithy/node-http-handler": ^4.0.4 + "@smithy/property-provider": ^4.0.2 + "@smithy/shared-ini-file-loader": ^4.0.2 + "@smithy/util-retry": ^4.0.2 + "@smithy/util-stream": ^4.2.0 + "@smithy/util-waiter": ^4.0.3 + archiver: ^7.0.1 + camelcase: ^6 + cdk-assets: ^3.2.0 + cdk-from-cfn: ^0.205.0 + chalk: ^4 + chokidar: ^3 + decamelize: ^5 + fs-extra: ^9 + glob: ^11.0.1 + json-diff: ^1.0.6 + minimatch: ^10.0.1 + p-limit: ^3 + promptly: ^3.2.0 + proxy-agent: ^6.5.0 + semver: ^7.7.1 + split2: ^4.2.0 + strip-ansi: ^6 + table: ^6 + uuid: ^11.1.0 + wrap-ansi: ^7 + yaml: ^1 + yargs: ^15 + checksum: 9611316c466e5c7a5797daee02d830f39868ee02b5708aed8f19be967dd3dd27b8df59af7d2fec0f24d2af2635eefd07069153f04cf3ca77e97a8ac2e3f31953 + languageName: node + linkType: hard + "@aws-crypto/crc32@npm:2.0.0": version: 2.0.0 resolution: "@aws-crypto/crc32@npm:2.0.0" @@ -2020,6 +2362,17 @@ __metadata: languageName: node linkType: hard +"@aws-crypto/util@npm:5.2.0, @aws-crypto/util@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/util@npm:5.2.0" + dependencies: + "@aws-sdk/types": ^3.222.0 + "@smithy/util-utf8": ^2.0.0 + tslib: ^2.6.2 + checksum: 0362d4c197b1fd64b423966945130207d1fe23e1bb2878a18e361f7743c8d339dad3f8729895a29aa34fff6a86c65f281cf5167c4bf253f21627ae80b6dd2951 + languageName: node + linkType: hard + "@aws-crypto/util@npm:^1.2.2": version: 1.2.2 resolution: "@aws-crypto/util@npm:1.2.2" @@ -2042,17 +2395,6 @@ __metadata: languageName: node linkType: hard -"@aws-crypto/util@npm:^5.2.0": - version: 5.2.0 - resolution: "@aws-crypto/util@npm:5.2.0" - dependencies: - "@aws-sdk/types": ^3.222.0 - "@smithy/util-utf8": ^2.0.0 - tslib: ^2.6.2 - checksum: 0362d4c197b1fd64b423966945130207d1fe23e1bb2878a18e361f7743c8d339dad3f8729895a29aa34fff6a86c65f281cf5167c4bf253f21627ae80b6dd2951 - languageName: node - linkType: hard - "@aws-sdk/abort-controller@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/abort-controller@npm:3.186.0" @@ -2073,6 +2415,100 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-amplify@npm:^3.592.0": + version: 3.797.0 + resolution: "@aws-sdk/client-amplify@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: 8e588d401176762c75399a85135f0cb9b9da2dd0a28ee4413ce26a7aba17469942b8678a37d8cc719c9cd735b4e009112853317eb5c992ad8252cb81ef84bb22 + languageName: node + linkType: hard + +"@aws-sdk/client-amplifybackend@npm:^3.592.0": + version: 3.797.0 + resolution: "@aws-sdk/client-amplifybackend@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: 0bfaa1c8b96b893f7ba9d2793a21bc401539ad07c6917cad64020a087d0c6ea91cd3b7740650a3e8eabf7b03ca1f42b5d8253e90303d68c04b5815fa8c5a261d + languageName: node + linkType: hard + "@aws-sdk/client-appsync@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/client-appsync@npm:3.624.0" @@ -2123,6 +2559,201 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-appsync@npm:^3, @aws-sdk/client-appsync@npm:^3.666.0": + version: 3.797.0 + resolution: "@aws-sdk/client-appsync@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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-stream": ^4.2.0 + "@smithy/util-utf8": ^4.0.0 + tslib: ^2.6.2 + checksum: 4759db11a5af1d56adcfebfd5ee938d89d2888c6f5b55677d707ad4f7fa6190d26bfd824a070596834a4075ae5718171126dc1b6879a91e322f0f3b7676d684c + languageName: node + linkType: hard + +"@aws-sdk/client-cloudcontrol@npm:^3, @aws-sdk/client-cloudcontrol@npm:^3.658.1": + version: 3.797.0 + resolution: "@aws-sdk/client-cloudcontrol@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: 020bf3f3ff59fa69e590524a33a06662636582b528875fc162044d0212b47e619bf08b2e2585b66d1d231c2a552c832a4cc7ff755f81ca2ae612f65f0a2b3875 + languageName: node + linkType: hard + +"@aws-sdk/client-cloudformation@npm:^3, @aws-sdk/client-cloudformation@npm:^3.592.0, @aws-sdk/client-cloudformation@npm:^3.744.0, @aws-sdk/client-cloudformation@npm:^3.787.0": + version: 3.797.0 + resolution: "@aws-sdk/client-cloudformation@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: 3165a3cc0448b8ad9f2d0eca58a98f90282a6e4518c62c50fb2a3704ff669058fcbdafe05c963b3c338c49b7e62a07f9016563b9ed5e6b5149d61addeeab3d48 + languageName: node + linkType: hard + +"@aws-sdk/client-cloudwatch-events@npm:^3.592.0": + version: 3.797.0 + resolution: "@aws-sdk/client-cloudwatch-events@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: 62cb47a7a578bb1dd11c9871ecfdeb54bd4b0c0f192c124c8a3a2c2bbe13d314b4897d8cec41454badf4eef9c815761301f6fcbdf04d9e089f48d34c5eede765 + languageName: node + linkType: hard + "@aws-sdk/client-cloudwatch-logs@npm:3.6.1": version: 3.6.1 resolution: "@aws-sdk/client-cloudwatch-logs@npm:3.6.1" @@ -2162,6 +2793,105 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-cloudwatch-logs@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-cloudwatch-logs@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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-utf8": ^4.0.0 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: 21634c0d46eec8e30388374c350c975b12899364bb23a0018638d2f561aac075b04abb0a3d9b1f9059fae28fa307e2f40f4a0d7238770e1a63b6e4ae6fc298d7 + languageName: node + linkType: hard + +"@aws-sdk/client-codebuild@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-codebuild@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: 5a38e49cbdef3d0e36f0b4f610b83ab1ab12c4786eff456332d875ce785755a2fd59467b868b3d8ba1d4d76e40a0597609bcdb58ec13e03b487084e91959d638 + languageName: node + linkType: hard + "@aws-sdk/client-cognito-identity-provider@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/client-cognito-identity-provider@npm:3.624.0" @@ -2211,6 +2941,53 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-cognito-identity-provider@npm:^3.592.0": + version: 3.797.0 + resolution: "@aws-sdk/client-cognito-identity-provider@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: a0605a05e728eab69e73fa7bec3eded158e36e05d74409795c48d9e3dfb6d0564fa597a05624567134a8336f8f39bafb446c82fe84bf6eec061787fba2efa31f + languageName: node + linkType: hard + "@aws-sdk/client-cognito-identity@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/client-cognito-identity@npm:3.624.0" @@ -2260,6 +3037,53 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-cognito-identity@npm:3.797.0, @aws-sdk/client-cognito-identity@npm:^3.592.0, @aws-sdk/client-cognito-identity@npm:^3.670.0": + version: 3.797.0 + resolution: "@aws-sdk/client-cognito-identity@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: 2b0d7230bda3502cff47815223382840a52e99cf1aba7322e4e50efaf0c964f5defa56bcb834ab82850b52a53ef76e6dee9aa0d75a92cffa389a917fde5fb7e9 + languageName: node + linkType: hard + "@aws-sdk/client-comprehend@npm:3.6.1": version: 3.6.1 resolution: "@aws-sdk/client-comprehend@npm:3.6.1" @@ -2404,6 +3228,203 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-ec2@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-ec2@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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-sdk-ec2": 3.796.0 + "@aws-sdk/middleware-user-agent": 3.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: e937320db9449adc4f4a624c4b8354c807d1342eff4578897a7e201e1dce42eb2be91d73c35384951a94eb49c0bef412073a86beb0c62ce29882a1d5b7f06795 + languageName: node + linkType: hard + +"@aws-sdk/client-ecr@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-ecr@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + tslib: ^2.6.2 + checksum: c5d5adee3633636c0a993e14c88cbce4e996172178d2458b910160d6c4476df1b53837044355c419685f78b26f219c674c3b71146d17b44152c1d62c8f860ec3 + languageName: node + linkType: hard + +"@aws-sdk/client-ecs@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-ecs@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: a01c9f8e3bfe777ad61c8141a243e9815a52385448280384612c6d9af13dee6ee004453cf882431ac8b7d155be561696de6bad700b94c6e798ce27bcb494dc91 + languageName: node + linkType: hard + +"@aws-sdk/client-elastic-load-balancing-v2@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-elastic-load-balancing-v2@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + tslib: ^2.6.2 + checksum: 3bc9635e413e4e08bb4ff1ed764bfb15a780847fd08e6d9fa9ad63629146dda1cef99b992a193f53392676f526c1d64ab201acc34eeb9c843963a3ac69280d2c + languageName: node + linkType: hard + "@aws-sdk/client-firehose@npm:3.6.1": version: 3.6.1 resolution: "@aws-sdk/client-firehose@npm:3.6.1" @@ -2493,6 +3514,54 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-iam@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-iam@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + tslib: ^2.6.2 + checksum: 8e92c6a32a597a2e1048cdf719a19dbaa0b17e3e2c14badbae2c91bc02902b0348820c2a84e47a45600fac120a9ba27ea59d88ed7e341da25bcd73e1bb09cbe8 + languageName: node + linkType: hard + "@aws-sdk/client-kinesis@npm:3.6.1": version: 3.6.1 resolution: "@aws-sdk/client-kinesis@npm:3.6.1" @@ -2536,6 +3605,53 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-kms@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-kms@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: 5f1799cb07c39f7227b8b77c0403481f9f3967621e1ce03209df0c382cc26d4f13c496b8c7c39cea0eb636fa0ab1284bda54f4d1923eee37470409c90b5a24eb + languageName: node + linkType: hard + "@aws-sdk/client-lambda@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/client-lambda@npm:3.624.0" @@ -2590,6 +3706,58 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-lambda@npm:^3, @aws-sdk/client-lambda@npm:^3.637.0, @aws-sdk/client-lambda@npm:^3.651.1": + version: 3.797.0 + resolution: "@aws-sdk/client-lambda@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: d05b339dd492210b82119af69924597fda67d408d31206f635fe4bcdd86b06d28a136298e0d27f24b703f19b69fd3430b68295b3a71b6d889e97df5fd90e5530 + languageName: node + linkType: hard + "@aws-sdk/client-lex-runtime-service@npm:3.186.3": version: 3.186.3 resolution: "@aws-sdk/client-lex-runtime-service@npm:3.186.3" @@ -2991,7 +4159,57 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-s3@npm:3.624.0, @aws-sdk/client-s3@npm:^3.25.0": +"@aws-sdk/client-route-53@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-route-53@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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-sdk-route53": 3.775.0 + "@aws-sdk/middleware-user-agent": 3.796.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.796.0 + "@aws-sdk/xml-builder": 3.775.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 + "@smithy/util-waiter": ^4.0.3 + tslib: ^2.6.2 + checksum: d47b7fc49f0de49993c2970765e4161b1beb0d5e614700572aaec4e8e93789d17358b7c21557c380e15d973e25a68b7c3d561ca057f1065aa795cfe9675f38ea + languageName: node + linkType: hard + +"@aws-sdk/client-s3@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/client-s3@npm:3.624.0" dependencies: @@ -3057,6 +4275,168 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-s3@npm:^3, @aws-sdk/client-s3@npm:^3.25.0, @aws-sdk/client-s3@npm:^3.592.0, @aws-sdk/client-s3@npm:^3.651.1, @aws-sdk/client-s3@npm:^3.674.0": + version: 3.797.0 + resolution: "@aws-sdk/client-s3@npm:3.797.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.796.0 + "@aws-sdk/credential-provider-node": 3.797.0 + "@aws-sdk/middleware-bucket-endpoint": 3.775.0 + "@aws-sdk/middleware-expect-continue": 3.775.0 + "@aws-sdk/middleware-flexible-checksums": 3.796.0 + "@aws-sdk/middleware-host-header": 3.775.0 + "@aws-sdk/middleware-location-constraint": 3.775.0 + "@aws-sdk/middleware-logger": 3.775.0 + "@aws-sdk/middleware-recursion-detection": 3.775.0 + "@aws-sdk/middleware-sdk-s3": 3.796.0 + "@aws-sdk/middleware-ssec": 3.775.0 + "@aws-sdk/middleware-user-agent": 3.796.0 + "@aws-sdk/region-config-resolver": 3.775.0 + "@aws-sdk/signature-v4-multi-region": 3.796.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.796.0 + "@aws-sdk/xml-builder": 3.775.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-blob-browser": ^4.0.2 + "@smithy/hash-node": ^4.0.2 + "@smithy/hash-stream-node": ^4.0.2 + "@smithy/invalid-dependency": ^4.0.2 + "@smithy/md5-js": ^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 + checksum: d3ae207b81bfc5f8be46620c03b1c1788ccf73e7aa1ec9a41c7cd72fea31b2e906a87c54581776623b7830f5de58580140386b070acc00106d594279a6815a55 + languageName: node + linkType: hard + +"@aws-sdk/client-secrets-manager@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-secrets-manager@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: 45de0359bcf6e2963d9e438478051bdf2187bf474edf987388b7fa5adfcf5187ef86aa083a592759ad0bed3a5f1159076a2f0d4af9e4582f96f02d54014d1736 + languageName: node + linkType: hard + +"@aws-sdk/client-sfn@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/client-sfn@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: cd530a0217b5e50909cbd84c49bfa8dc5cfd67b462693f2b3cfbe0c2170b9da6066ef6d78e249f61473e6e8885e7a6c764edbd7031bbdc00a8db110fe5481ff7 + languageName: node + linkType: hard + "@aws-sdk/client-ssm@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/client-ssm@npm:3.624.0" @@ -3108,6 +4488,56 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-ssm@npm:^3, @aws-sdk/client-ssm@npm:^3.592.0": + version: 3.797.0 + resolution: "@aws-sdk/client-ssm@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + "@smithy/util-waiter": ^4.0.3 + "@types/uuid": ^9.0.1 + tslib: ^2.6.2 + uuid: ^9.0.1 + checksum: 2b749632ac7c1211cdce23a4767378815afdac7acda9f22acdca82d7aa36a18e1030b1c34bc3411f755a81132c8b3c59deb25c0622e4b03e4012456c4138532d + languageName: node + linkType: hard + "@aws-sdk/client-sso-oidc@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/client-sso-oidc@npm:3.624.0" @@ -3242,6 +4672,52 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/client-sso@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.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.796.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.796.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 + checksum: e28005c2e9eecae246d3d7469f6d69ffc776cf465cf2044a813d652e63dbb61ae54727bf8fe56452d31d59c6300b62c0a3d5d34c5520c17b1b3e289573182226 + languageName: node + linkType: hard + "@aws-sdk/client-sts@npm:3.186.3": version: 3.186.3 resolution: "@aws-sdk/client-sts@npm:3.186.3" @@ -3334,6 +4810,53 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sts@npm:^3, @aws-sdk/client-sts@npm:^3.658.1, @aws-sdk/client-sts@npm:^3.750.0": + version: 3.797.0 + resolution: "@aws-sdk/client-sts@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-node": 3.797.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.796.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.796.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 + checksum: 14e51cfb96599e0323d304b428339e4dc0eee9f3ff299a3e8780654d4510e0a23a3d68b8391b4933aa9ab3e27ce54a46f2c7570379e7f431e9d012dca2830464 + languageName: node + linkType: hard + "@aws-sdk/client-textract@npm:3.6.1": version: 3.6.1 resolution: "@aws-sdk/client-textract@npm:3.6.1" @@ -3454,6 +4977,25 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/core@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/core@npm:3.796.0" + 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.1.0 + "@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 + checksum: 36823871b23ef740acdae48b8acd0525af99e5c3e2f0b2863ed1ab1db8d29d02628d69b9838347326c468508147d2b66846ed1ec3d100b5720a1f0cc12ef2bd8 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-cognito-identity@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.624.0" @@ -3467,6 +5009,19 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-cognito-identity@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.797.0" + dependencies: + "@aws-sdk/client-cognito-identity": 3.797.0 + "@aws-sdk/types": 3.775.0 + "@smithy/property-provider": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 963adeb5390993112326c53e904cfa47cbfb6c2f0946862572b630bc179ca546977d58f32f41f4cd23e41744d307388cb8ca52708d6068213cf769c9eae9760b + languageName: node + linkType: hard + "@aws-sdk/credential-provider-env@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/credential-provider-env@npm:3.186.0" @@ -3501,6 +5056,19 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-env@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.796.0" + dependencies: + "@aws-sdk/core": 3.796.0 + "@aws-sdk/types": 3.775.0 + "@smithy/property-provider": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 8fbb3f33c50658872c699d7a0bf1102a8b1a6d550ce3e29be475a7304eebcac2794e82cbf6805f3d8472a7560ca2271b480283663a9635e88692c2ac9f2cac0e + languageName: node + linkType: hard + "@aws-sdk/credential-provider-http@npm:3.622.0": version: 3.622.0 resolution: "@aws-sdk/credential-provider-http@npm:3.622.0" @@ -3518,6 +5086,24 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-http@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.796.0" + dependencies: + "@aws-sdk/core": 3.796.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 + checksum: e877f9a432a154daf408d9a568ed262f70abd82263796b456b99505f90c81e6c0abdfe910869f662dc0364607ac33e6a982f466c6822ebfbe7f10f4e33035de4 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-imds@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/credential-provider-imds@npm:3.186.0" @@ -3591,6 +5177,27 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-ini@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.797.0" + dependencies: + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-env": 3.796.0 + "@aws-sdk/credential-provider-http": 3.796.0 + "@aws-sdk/credential-provider-process": 3.796.0 + "@aws-sdk/credential-provider-sso": 3.797.0 + "@aws-sdk/credential-provider-web-identity": 3.797.0 + "@aws-sdk/nested-clients": 3.797.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 + checksum: 1f3d587290a8290694e3e744218e847a99fcfcef2e10785f03d5cf141841ad004dc3c3902834cc5838bfdca6325f42301cd2cfe356fda9d68fdc717830e3fe64 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-node@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/credential-provider-node@npm:3.186.0" @@ -3645,6 +5252,26 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-node@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.797.0" + dependencies: + "@aws-sdk/credential-provider-env": 3.796.0 + "@aws-sdk/credential-provider-http": 3.796.0 + "@aws-sdk/credential-provider-ini": 3.797.0 + "@aws-sdk/credential-provider-process": 3.796.0 + "@aws-sdk/credential-provider-sso": 3.797.0 + "@aws-sdk/credential-provider-web-identity": 3.797.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 + checksum: 0ac14ce7bdf130ac4688329b0f9c67eef45d26c2e8b739424a1d1f82c0d6d425c737e43ff89d57d340eab5077dff730f2ecffe3cd2f60cf25199e9a9dce830e4 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-process@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/credential-provider-process@npm:3.186.0" @@ -3683,6 +5310,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-process@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.796.0" + dependencies: + "@aws-sdk/core": 3.796.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 + checksum: f37c2f43349b3f7d1ecf77ab67650b617ded39372803e0353acaeffb2afe57256391daeb45d13bab4705c5ce1cf10134601b738ddcd44f5dab21ebf0dfffe66e + languageName: node + linkType: hard + "@aws-sdk/credential-provider-sso@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/credential-provider-sso@npm:3.186.0" @@ -3711,6 +5352,22 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-sso@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.797.0" + dependencies: + "@aws-sdk/client-sso": 3.797.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/token-providers": 3.797.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 + checksum: fcd6b82bc5c573889a8bac050fb39ea50ad12ca83a99d0a3e6d839a62fa9d6fb9b2165b47a3cc178e18e9cfd9c04d30f1c7e957b2fb0b52815ac4ae3b1d5578f + languageName: node + linkType: hard + "@aws-sdk/credential-provider-web-identity@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/credential-provider-web-identity@npm:3.186.0" @@ -3736,6 +5393,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-web-identity@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.797.0" + dependencies: + "@aws-sdk/core": 3.796.0 + "@aws-sdk/nested-clients": 3.797.0 + "@aws-sdk/types": 3.775.0 + "@smithy/property-provider": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 91f23052c4424357f8f803b50f95d903303b6d01bddc843b4e149acecf9e965ac7d4a111b77e8bc51c02f3ff87702606ef4f1f5e39ebc4fc535d5fd6bb500c7c + languageName: node + linkType: hard + "@aws-sdk/credential-providers@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/credential-providers@npm:3.624.0" @@ -3760,6 +5431,48 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-providers@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/credential-providers@npm:3.797.0" + dependencies: + "@aws-sdk/client-cognito-identity": 3.797.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/credential-provider-cognito-identity": 3.797.0 + "@aws-sdk/credential-provider-env": 3.796.0 + "@aws-sdk/credential-provider-http": 3.796.0 + "@aws-sdk/credential-provider-ini": 3.797.0 + "@aws-sdk/credential-provider-node": 3.797.0 + "@aws-sdk/credential-provider-process": 3.796.0 + "@aws-sdk/credential-provider-sso": 3.797.0 + "@aws-sdk/credential-provider-web-identity": 3.797.0 + "@aws-sdk/nested-clients": 3.797.0 + "@aws-sdk/types": 3.775.0 + "@smithy/config-resolver": ^4.1.0 + "@smithy/core": ^3.2.0 + "@smithy/credential-provider-imds": ^4.0.2 + "@smithy/node-config-provider": ^4.0.2 + "@smithy/property-provider": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 3cf276c8cad221315830885079867a1e8be9d463cd667acde71e262e8697a345ef9f0fb33ea4700fc7e71d34fe5855fe83c81bfa800c6ed2a1fb28968f4b66b4 + languageName: node + linkType: hard + +"@aws-sdk/ec2-metadata-service@npm:^3": + version: 3.797.0 + resolution: "@aws-sdk/ec2-metadata-service@npm:3.797.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/node-config-provider": ^4.0.2 + "@smithy/node-http-handler": ^4.0.4 + "@smithy/protocol-http": ^5.1.0 + "@smithy/types": ^4.2.0 + "@smithy/util-stream": ^4.2.0 + tslib: ^2.6.2 + checksum: 5d8371f40d8e45d656e89cc7403c797e77923628567a18baa71db57ddd20269c357aef67a2e44d7a6a51cf10cbc3558ff0857643dc7535b32c8be0c8145273b2 + languageName: node + linkType: hard + "@aws-sdk/endpoint-cache@npm:3.572.0": version: 3.572.0 resolution: "@aws-sdk/endpoint-cache@npm:3.572.0" @@ -4000,18 +5713,20 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/lib-storage@npm:^3.25.0": - version: 3.44.0 - resolution: "@aws-sdk/lib-storage@npm:3.44.0" +"@aws-sdk/lib-storage@npm:^3, @aws-sdk/lib-storage@npm:^3.25.0": + version: 3.797.0 + resolution: "@aws-sdk/lib-storage@npm:3.797.0" dependencies: + "@smithy/abort-controller": ^4.0.2 + "@smithy/middleware-endpoint": ^4.1.0 + "@smithy/smithy-client": ^4.2.0 buffer: 5.6.0 events: 3.3.0 stream-browserify: 3.0.0 - tslib: ^2.3.0 + tslib: ^2.6.2 peerDependencies: - "@aws-sdk/abort-controller": ^3.0.0 - "@aws-sdk/client-s3": ^3.0.0 - checksum: bb62cd63593f59317ce45c886cf14b23c496758694217ad6f200effdae5291554dc166ba9af175b07f1696c0fd8b7d7954c36e89d46e6122d37a5959997c116c + "@aws-sdk/client-s3": ^3.797.0 + checksum: ede77e4719fc433a13be3c5bf980b64774ba350894538bc881092a09e7b327f056f35342ccc8ef3261044db4ffcfa7279357e4858271a44a23b8b385ef5bf7e9 languageName: node linkType: hard @@ -4041,6 +5756,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-bucket-endpoint@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@aws-sdk/util-arn-parser": 3.723.0 + "@smithy/node-config-provider": ^4.0.2 + "@smithy/protocol-http": ^5.1.0 + "@smithy/types": ^4.2.0 + "@smithy/util-config-provider": ^4.0.0 + tslib: ^2.6.2 + checksum: 67403c36a67aca9d1e0834f0ef22ab998846eb6ba408ae473862564da075e5dade2a1286e2f096c90c4defc166b89c1d56af74b761424d630a170301529f5c66 + languageName: node + linkType: hard + "@aws-sdk/middleware-content-length@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/middleware-content-length@npm:3.186.0" @@ -4100,6 +5830,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-expect-continue@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-expect-continue@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/protocol-http": ^5.1.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: b0f40cd2ecc4f3a28effc1af427d8c3984bcd5fa03c360073c32264ef134e1d32f6777c631e767ca9fa7c6d3f380cd930736f328fd3b0c896f1756981549ae0d + languageName: node + linkType: hard + "@aws-sdk/middleware-flexible-checksums@npm:3.620.0": version: 3.620.0 resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.620.0" @@ -4116,6 +5858,27 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-flexible-checksums@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.796.0" + dependencies: + "@aws-crypto/crc32": 5.2.0 + "@aws-crypto/crc32c": 5.2.0 + "@aws-crypto/util": 5.2.0 + "@aws-sdk/core": 3.796.0 + "@aws-sdk/types": 3.775.0 + "@smithy/is-array-buffer": ^4.0.0 + "@smithy/node-config-provider": ^4.0.2 + "@smithy/protocol-http": ^5.1.0 + "@smithy/types": ^4.2.0 + "@smithy/util-middleware": ^4.0.2 + "@smithy/util-stream": ^4.2.0 + "@smithy/util-utf8": ^4.0.0 + tslib: ^2.6.2 + checksum: e24b9e5311e8f13399deaeb95ed67bc0fa5a30afe579ff763bcc80cd364f0af83e86022d85f1d9ff1a7a1b8ae5d4a2f207c8104eadab412a4148e1b08fafaa3d + languageName: node + linkType: hard + "@aws-sdk/middleware-host-header@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/middleware-host-header@npm:3.186.0" @@ -4150,6 +5913,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-host-header@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/protocol-http": ^5.1.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 1e7fb71bc596250c20d51a6ec5f33801746db6ca553f2baf3f3261db5abf18dc5f0c31514c81496f6efaf39643aaa90226ca801b36d9bd76943221f15256a266 + languageName: node + linkType: hard + "@aws-sdk/middleware-location-constraint@npm:3.609.0": version: 3.609.0 resolution: "@aws-sdk/middleware-location-constraint@npm:3.609.0" @@ -4161,6 +5936,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-location-constraint@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-location-constraint@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 85f136cab13b6f955d28e88245dd8706e273652403a6ad8edf993502539e4c48963a5069e3db3c81c96de2db3bdd20a4acca7fdad1f59d9505671e6d3904d70f + languageName: node + linkType: hard + "@aws-sdk/middleware-logger@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/middleware-logger@npm:3.186.0" @@ -4192,6 +5978,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-logger@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-logger@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: ab46864523cd58e965bd7071781a8719dec7037aeef4c08345138ad90d6e0162ac911f79b40bd2bd3308776edbaa5dc987f9e104da0980537947227a4cddce12 + languageName: node + linkType: hard + "@aws-sdk/middleware-recursion-detection@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/middleware-recursion-detection@npm:3.186.0" @@ -4215,6 +6012,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-recursion-detection@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/protocol-http": ^5.1.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 652ac6384034cb7219d8f44cbbc059dbc68cc6f8c9e61dbd19bc5488ff267564cc899ab52cdc45aa79d0e2d4162ce52e00c518a3eb4371f5b72465e715cd5387 + languageName: node + linkType: hard + "@aws-sdk/middleware-retry@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/middleware-retry@npm:3.186.0" @@ -4259,6 +6068,22 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-sdk-ec2@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/middleware-sdk-ec2@npm:3.796.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@aws-sdk/util-format-url": 3.775.0 + "@smithy/middleware-endpoint": ^4.1.0 + "@smithy/protocol-http": ^5.1.0 + "@smithy/signature-v4": ^5.1.0 + "@smithy/smithy-client": ^4.2.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: a99ec238408d1954874fbe7362f8734c5a41dfe30ff74b0698db5b8e10012fabdc2032a27d8a493c2674d9a679211c0af4a68c3fdf3bd20071893a4c9961c812 + languageName: node + linkType: hard + "@aws-sdk/middleware-sdk-rds@npm:3.620.0": version: 3.620.0 resolution: "@aws-sdk/middleware-sdk-rds@npm:3.620.0" @@ -4274,6 +6099,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-sdk-route53@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-sdk-route53@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 537a50d1c7341b80b6e812dcd9ad6816536b5ca204d85dd9addbacad07fcab0844f106192d50284f6d5e6d477b6e4a0b611d5af1516113714736f41510ce5acd + languageName: node + linkType: hard + "@aws-sdk/middleware-sdk-s3@npm:3.624.0": version: 3.624.0 resolution: "@aws-sdk/middleware-sdk-s3@npm:3.624.0" @@ -4296,6 +6132,28 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-sdk-s3@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.796.0" + dependencies: + "@aws-sdk/core": 3.796.0 + "@aws-sdk/types": 3.775.0 + "@aws-sdk/util-arn-parser": 3.723.0 + "@smithy/core": ^3.2.0 + "@smithy/node-config-provider": ^4.0.2 + "@smithy/protocol-http": ^5.1.0 + "@smithy/signature-v4": ^5.1.0 + "@smithy/smithy-client": ^4.2.0 + "@smithy/types": ^4.2.0 + "@smithy/util-config-provider": ^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 + checksum: e8c38a5e0d75ecbedc009d23d14227abc0c0515114be324482fb00845c58bf9523ae8b94cee1e927f645cd0b8e0a4e02a31dae917a65813ab2ed778869bcce85 + languageName: node + linkType: hard + "@aws-sdk/middleware-sdk-sts@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/middleware-sdk-sts@npm:3.186.0" @@ -4367,6 +6225,17 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-ssec@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/middleware-ssec@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 6f5148efa3b470a25ee44d200b18676a417f2990cf681a54a87f4a79597714777f7aee7d7ac47194cb836609496856fa6a3b307df76d36f11890b9f3770bb0c3 + languageName: node + linkType: hard + "@aws-sdk/middleware-stack@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/middleware-stack@npm:3.186.0" @@ -4420,6 +6289,67 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/middleware-user-agent@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.796.0" + dependencies: + "@aws-sdk/core": 3.796.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 + checksum: 6caeec2f05b705da1eae0b2fe93df9d2215ee19b5adc5110684c70fe7bdbcc3fa3f455d6e2f7dc0a7c2534451975242781818897fa5e846acc1c0bbe270b3805 + languageName: node + linkType: hard + +"@aws-sdk/nested-clients@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/nested-clients@npm:3.797.0" + dependencies: + "@aws-crypto/sha256-browser": 5.2.0 + "@aws-crypto/sha256-js": 5.2.0 + "@aws-sdk/core": 3.796.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.796.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.796.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 + checksum: bb7554df8049cd0ecb2406faba024cb8b364d70996e42f454b44770a9b3b79d9f9ff05ec7c4f3519d41b3f9cb25691ec92e624f0c13bf11dc12f01b898f446a7 + languageName: node + linkType: hard + "@aws-sdk/node-config-provider@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/node-config-provider@npm:3.186.0" @@ -4566,6 +6496,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/region-config-resolver@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.775.0" + 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 + checksum: 774d3e65873c725634b208604df3aac379ba9a9b8c4910a0ba54360bae8855d22e7dd1310d15f1946d9b8b16bca03b268d29984d0b25f2ba33094aeb30da6072 + languageName: node + linkType: hard + "@aws-sdk/service-error-classification@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/service-error-classification@npm:3.186.0" @@ -4613,6 +6557,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/signature-v4-multi-region@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.796.0" + dependencies: + "@aws-sdk/middleware-sdk-s3": 3.796.0 + "@aws-sdk/types": 3.775.0 + "@smithy/protocol-http": ^5.1.0 + "@smithy/signature-v4": ^5.1.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: e914e6f40d47496217d5e8ab7a6732d86e691e42791a5956bea14c6c12425bf03a7da45af4a1670f1a02cc104feef5f57c9cd4a23253eb1c6562ae1707309a77 + languageName: node + linkType: hard + "@aws-sdk/signature-v4@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/signature-v4@npm:3.186.0" @@ -4677,6 +6635,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/token-providers@npm:3.797.0": + version: 3.797.0 + resolution: "@aws-sdk/token-providers@npm:3.797.0" + dependencies: + "@aws-sdk/nested-clients": 3.797.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 + checksum: 9675715660484fa7d99365a01088680231079e739bedec43c26fae960ea6edc1dbc3b846a936a46b0406cf153ce4816ad5348c77e4379362f9caeca335ef9d08 + languageName: node + linkType: hard + "@aws-sdk/types@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/types@npm:3.186.0" @@ -4700,7 +6672,7 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/types@npm:3.609.0, @aws-sdk/types@npm:^3.1.0, @aws-sdk/types@npm:^3.222.0, @aws-sdk/types@npm:^3.25.0": +"@aws-sdk/types@npm:3.609.0": version: 3.609.0 resolution: "@aws-sdk/types@npm:3.609.0" dependencies: @@ -4710,6 +6682,16 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/types@npm:3.775.0, @aws-sdk/types@npm:^3.1.0, @aws-sdk/types@npm:^3.222.0, @aws-sdk/types@npm:^3.25.0": + version: 3.775.0 + resolution: "@aws-sdk/types@npm:3.775.0" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 106515a677a8619ecffc6a652d6866a29f9191f2f883d4a98e44ea1926a112994c4c4733e77f7d997f5f1a4cecf2ce605059a449001d5630ed73f6cd4d4d94a3 + languageName: node + linkType: hard + "@aws-sdk/url-parser-native@npm:3.6.1": version: 3.6.1 resolution: "@aws-sdk/url-parser-native@npm:3.6.1" @@ -4744,7 +6726,7 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-arn-parser@npm:3.568.0, @aws-sdk/util-arn-parser@npm:^3.310.0": +"@aws-sdk/util-arn-parser@npm:3.568.0": version: 3.568.0 resolution: "@aws-sdk/util-arn-parser@npm:3.568.0" dependencies: @@ -4753,6 +6735,15 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-arn-parser@npm:3.723.0, @aws-sdk/util-arn-parser@npm:^3.310.0, @aws-sdk/util-arn-parser@npm:^3.723.0": + version: 3.723.0 + resolution: "@aws-sdk/util-arn-parser@npm:3.723.0" + dependencies: + tslib: ^2.6.2 + checksum: 5d2adfded61acaf222ed21bf8e5a8b067fe469dfaab03a6b69c591a090c48d309b1f3c4fd64826f71ef9883390adb77a9bf884667b242615f221236bc5a8b326 + languageName: node + linkType: hard + "@aws-sdk/util-base64-browser@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/util-base64-browser@npm:3.186.0" @@ -4904,6 +6895,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-endpoints@npm:3.787.0": + version: 3.787.0 + resolution: "@aws-sdk/util-endpoints@npm:3.787.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/types": ^4.2.0 + "@smithy/util-endpoints": ^3.0.2 + tslib: ^2.6.2 + checksum: b9645d56e60817b323c85ed363ee9c0c3a8196aa81e364acfcd2b14ab05f3df7457b6543487bc0a2e55ee8e64be22b1b84f119614a4235a92d72dec7168ccdb7 + languageName: node + linkType: hard + "@aws-sdk/util-format-url@npm:3.609.0": version: 3.609.0 resolution: "@aws-sdk/util-format-url@npm:3.609.0" @@ -4916,6 +6919,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-format-url@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/util-format-url@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/querystring-builder": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 4128a39b8b7c6f3b8e4cbf28ea1a85b97ebcd52c9e4468ffac3bae3d6fd56f23c7f4dff5ee3dc9689d5c04cd272d4159094e7d504f0ebf779894eb94bd5f7447 + languageName: node + linkType: hard + "@aws-sdk/util-hex-encoding@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/util-hex-encoding@npm:3.186.0" @@ -5013,6 +7028,18 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-user-agent-browser@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.775.0" + dependencies: + "@aws-sdk/types": 3.775.0 + "@smithy/types": ^4.2.0 + bowser: ^2.11.0 + tslib: ^2.6.2 + checksum: 527f3225a28a49de6893c2a396d80cd13cfa64f9cc9d16b575a708e5d4a6006e145aaec6166071012aacdb732604c3a554d69ab6f099fb021d0d15565bb7fb4c + languageName: node + linkType: hard + "@aws-sdk/util-user-agent-node@npm:3.186.0": version: 3.186.0 resolution: "@aws-sdk/util-user-agent-node@npm:3.186.0" @@ -5057,6 +7084,24 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/util-user-agent-node@npm:3.796.0": + version: 3.796.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.796.0" + dependencies: + "@aws-sdk/middleware-user-agent": 3.796.0 + "@aws-sdk/types": 3.775.0 + "@smithy/node-config-provider": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 07de07ffc030a648c838f2b04fde9ecf6424e51b044e752f234f3c34b12aac2bc5a13aac021499d1cc2a98e9fe353acf192a2e84f5ba91579a7b97157d8650c9 + languageName: node + linkType: hard + "@aws-sdk/util-utf8-browser@npm:3.186.0, @aws-sdk/util-utf8-browser@npm:^3.0.0": version: 3.186.0 resolution: "@aws-sdk/util-utf8-browser@npm:3.186.0" @@ -5126,6 +7171,16 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/xml-builder@npm:3.775.0": + version: 3.775.0 + resolution: "@aws-sdk/xml-builder@npm:3.775.0" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 636f0c3c463b391edf9118b6b07c650dc14218ec7a2b0c309a931f5df539d7b472185f5ae36f0fabbdf1422189ba7a641133246299b111e5b8fa7ad39f85a080 + languageName: node + linkType: hard + "@babel/cli@npm:^7.10.5": version: 7.16.0 resolution: "@babel/cli@npm:7.16.0" @@ -5180,26 +7235,26 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.14.0, @babel/core@npm:^7.16.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.24.4, @babel/core@npm:^7.7.2": - version: 7.26.9 - resolution: "@babel/core@npm:7.26.9" +"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.14.0, @babel/core@npm:^7.16.0, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.24.4, @babel/core@npm:^7.7.2": + version: 7.26.10 + resolution: "@babel/core@npm:7.26.10" dependencies: "@ampproject/remapping": ^2.2.0 "@babel/code-frame": ^7.26.2 - "@babel/generator": ^7.26.9 + "@babel/generator": ^7.26.10 "@babel/helper-compilation-targets": ^7.26.5 "@babel/helper-module-transforms": ^7.26.0 - "@babel/helpers": ^7.26.9 - "@babel/parser": ^7.26.9 + "@babel/helpers": ^7.26.10 + "@babel/parser": ^7.26.10 "@babel/template": ^7.26.9 - "@babel/traverse": ^7.26.9 - "@babel/types": ^7.26.9 + "@babel/traverse": ^7.26.10 + "@babel/types": ^7.26.10 convert-source-map: ^2.0.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.3 semver: ^6.3.1 - checksum: ed7212ff42a9453765787019b7d191b167afcacd4bd8fec10b055344ef53fa0cc648c9a80159ae4ecf870016a6318731e087042dcb68d1a2a9d34eb290dc014b + checksum: e046e0e988ab53841b512ee9d263ca409f6c46e2a999fe53024688b92db394346fa3aeae5ea0866331f62133982eee05a675d22922a4603c3f603aa09a581d62 languageName: node linkType: hard @@ -5230,16 +7285,16 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.23.0, @babel/generator@npm:^7.26.9, @babel/generator@npm:^7.7.2": - version: 7.26.9 - resolution: "@babel/generator@npm:7.26.9" +"@babel/generator@npm:^7.14.0, @babel/generator@npm:^7.23.0, @babel/generator@npm:^7.26.10, @babel/generator@npm:^7.27.0, @babel/generator@npm:^7.7.2": + version: 7.27.0 + resolution: "@babel/generator@npm:7.27.0" dependencies: - "@babel/parser": ^7.26.9 - "@babel/types": ^7.26.9 + "@babel/parser": ^7.27.0 + "@babel/types": ^7.27.0 "@jridgewell/gen-mapping": ^0.3.5 "@jridgewell/trace-mapping": ^0.3.25 jsesc: ^3.0.2 - checksum: 6b78872128205224a9a9761b9ea7543a9a7902a04b82fc2f6801ead4de8f59056bab3fd17b1f834ca7b049555fc4c79234b9a6230dd9531a06525306050becad + checksum: 7cb10693d2b365c278f109a745dc08856cae139d262748b77b70ce1d97da84627f79648cab6940d847392c0e5d180441669ed958b3aee98d9c7d274b37c553bd languageName: node linkType: hard @@ -5492,13 +7547,13 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.26.9": - version: 7.26.9 - resolution: "@babel/helpers@npm:7.26.9" +"@babel/helpers@npm:^7.26.10": + version: 7.27.0 + resolution: "@babel/helpers@npm:7.27.0" dependencies: - "@babel/template": ^7.26.9 - "@babel/types": ^7.26.9 - checksum: 3d4dbc4a33fe4181ed810cac52318b578294745ceaec07e2f6ecccf6cda55d25e4bfcea8f085f333bf911c9e1fc13320248dd1d5315ab47ad82ce1077410df05 + "@babel/template": ^7.27.0 + "@babel/types": ^7.27.0 + checksum: a3c64fd2d8b164c041808826cc00769d814074ea447daaacaf2e3714b66d3f4237ef6e420f61d08f463d6608f3468c2ac5124ab7c68f704e20384def5ade95f4 languageName: node linkType: hard @@ -5513,14 +7568,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.26.9, @babel/parser@npm:^7.7.0": - version: 7.26.9 - resolution: "@babel/parser@npm:7.26.9" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.26.10, @babel/parser@npm:^7.27.0, @babel/parser@npm:^7.7.0": + version: 7.27.0 + resolution: "@babel/parser@npm:7.27.0" dependencies: - "@babel/types": ^7.26.9 + "@babel/types": ^7.27.0 bin: parser: ./bin/babel-parser.js - checksum: 4b9ef3c9a0d4c328e5e5544f50fe8932c36f8a2c851e7f14a85401487cd3da75cad72c2e1bcec1eac55599a6bbb2fdc091f274c4fcafa6bdd112d4915ff087fc + checksum: ba2ed3f41735826546a3ef2a7634a8d10351df221891906e59b29b0a0cd748f9b0e7a6f07576858a9de8e77785aad925c8389ddef146de04ea2842047c9d2859 languageName: node linkType: hard @@ -6621,29 +8676,29 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.16.7, @babel/template@npm:^7.20.7, @babel/template@npm:^7.22.15, @babel/template@npm:^7.26.9, @babel/template@npm:^7.3.3": - version: 7.26.9 - resolution: "@babel/template@npm:7.26.9" +"@babel/template@npm:^7.16.7, @babel/template@npm:^7.20.7, @babel/template@npm:^7.22.15, @babel/template@npm:^7.26.9, @babel/template@npm:^7.27.0, @babel/template@npm:^7.3.3": + version: 7.27.0 + resolution: "@babel/template@npm:7.27.0" dependencies: "@babel/code-frame": ^7.26.2 - "@babel/parser": ^7.26.9 - "@babel/types": ^7.26.9 - checksum: 019b1c4129cc01ad63e17529089c2c559c74709d225f595eee017af227fee11ae8a97a6ab19ae6768b8aa22d8d75dcb60a00b28f52e9fa78140672d928bc1ae9 + "@babel/parser": ^7.27.0 + "@babel/types": ^7.27.0 + checksum: 13af543756127edb5f62bf121f9b093c09a2b6fe108373887ccffc701465cfbcb17e07cf48aa7f440415b263f6ec006e9415c79dfc2e8e6010b069435f81f340 languageName: node linkType: hard -"@babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.21.5, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.9, @babel/traverse@npm:^7.7.0, @babel/traverse@npm:^7.7.2": - version: 7.26.9 - resolution: "@babel/traverse@npm:7.26.9" +"@babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.14.0, @babel/traverse@npm:^7.16.8, @babel/traverse@npm:^7.21.5, @babel/traverse@npm:^7.25.9, @babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.7.0, @babel/traverse@npm:^7.7.2": + version: 7.27.0 + resolution: "@babel/traverse@npm:7.27.0" dependencies: "@babel/code-frame": ^7.26.2 - "@babel/generator": ^7.26.9 - "@babel/parser": ^7.26.9 - "@babel/template": ^7.26.9 - "@babel/types": ^7.26.9 + "@babel/generator": ^7.27.0 + "@babel/parser": ^7.27.0 + "@babel/template": ^7.27.0 + "@babel/types": ^7.27.0 debug: ^4.3.1 globals: ^11.1.0 - checksum: 51dd57fa39ea34d04816806bfead04c74f37301269d24c192d1406dc6e244fea99713b3b9c5f3e926d9ef6aa9cd5c062ad4f2fc1caa9cf843d5e864484ac955e + checksum: c7af29781960dacaae51762e8bc6c4b13d6ab4b17312990fbca9fc38e19c4ad7fecaae24b1cf52fb844e8e6cdc76c70ad597f90e496bcb3cc0a1d66b41a0aa5b languageName: node linkType: hard @@ -6658,13 +8713,13 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.6, @babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.6, @babel/types@npm:^7.20.0, @babel/types@npm:^7.21.5, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.9, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.7.0": - version: 7.26.9 - resolution: "@babel/types@npm:7.26.9" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.6, @babel/types@npm:^7.16.7, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.6, @babel/types@npm:^7.20.0, @babel/types@npm:^7.21.5, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0, @babel/types@npm:^7.25.9, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.0, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.7.0": + version: 7.27.0 + resolution: "@babel/types@npm:7.27.0" dependencies: "@babel/helper-string-parser": ^7.25.9 "@babel/helper-validator-identifier": ^7.25.9 - checksum: 999c56269ba00e5c57aa711fbe7ff071cd6990bafd1b978341ea7572cc78919986e2aa6ee51dacf4b6a7a6fa63ba4eb3f1a03cf55eee31b896a56d068b895964 + checksum: 6f1592eabe243c89a608717b07b72969be9d9d2fce1dee21426238757ea1fa60fdfc09b29de9e48d8104311afc6e6fb1702565a9cc1e09bc1e76f2b2ddb0f6e1 languageName: node linkType: hard @@ -6682,6 +8737,13 @@ __metadata: languageName: node linkType: hard +"@cdklabs/tskb@npm:^0.0.3": + version: 0.0.3 + resolution: "@cdklabs/tskb@npm:0.0.3" + checksum: 01b976c661f13dbafb57b9c481fd08f05104847da59cf9a07f4d1c95c3955a904d242c2ea503827bcd01d5203f0810f8d3a0465cd0339f585e0b15f3a7c49d85 + languageName: node + linkType: hard + "@commitlint/cli@npm:^17.6.1": version: 17.6.1 resolution: "@commitlint/cli@npm:17.6.1" @@ -7106,6 +9168,15 @@ __metadata: languageName: node linkType: hard +"@ewoudenberg/difflib@npm:0.1.0": + version: 0.1.0 + resolution: "@ewoudenberg/difflib@npm:0.1.0" + dependencies: + heap: ">= 0.2.0" + checksum: 3060807c91f39c5c1e5421fe51573bb2bffcab48cb32e9dcc69ce0d43e658dbf5959421382541012628ca6b842d252fc157a79f70cea8088a864572fec2bd094 + languageName: node + linkType: hard + "@fluentui/react-component-event-listener@npm:~0.63.0": version: 0.63.1 resolution: "@fluentui/react-component-event-listener@npm:0.63.1" @@ -7486,7 +9557,7 @@ __metadata: languageName: node linkType: hard -"@istanbuljs/schema@npm:^0.1.2": +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": version: 0.1.3 resolution: "@istanbuljs/schema@npm:0.1.3" checksum: 61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a @@ -7507,50 +9578,50 @@ __metadata: languageName: node linkType: hard -"@jest/console@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/console@npm:29.5.0" +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" dependencies: - "@jest/types": ^29.5.0 + "@jest/types": ^29.6.3 "@types/node": "*" chalk: ^4.0.0 - jest-message-util: ^29.5.0 - jest-util: ^29.5.0 + jest-message-util: ^29.7.0 + jest-util: ^29.7.0 slash: ^3.0.0 - checksum: 59dfbdb6c3c15652f8d7267071f24d6335afbed0b1cf71aed70b6ce8deb1d86e7f4aadb978f639435650107fd22476b59e63a3d3a9ac99b1aca739b795a54410 + checksum: 7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c languageName: node linkType: hard -"@jest/core@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/core@npm:29.5.0" +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" dependencies: - "@jest/console": ^29.5.0 - "@jest/reporters": ^29.5.0 - "@jest/test-result": ^29.5.0 - "@jest/transform": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/console": ^29.7.0 + "@jest/reporters": ^29.7.0 + "@jest/test-result": ^29.7.0 + "@jest/transform": ^29.7.0 + "@jest/types": ^29.6.3 "@types/node": "*" ansi-escapes: ^4.2.1 chalk: ^4.0.0 ci-info: ^3.2.0 exit: ^0.1.2 graceful-fs: ^4.2.9 - jest-changed-files: ^29.5.0 - jest-config: ^29.5.0 - jest-haste-map: ^29.5.0 - jest-message-util: ^29.5.0 - jest-regex-util: ^29.4.3 - jest-resolve: ^29.5.0 - jest-resolve-dependencies: ^29.5.0 - jest-runner: ^29.5.0 - jest-runtime: ^29.5.0 - jest-snapshot: ^29.5.0 - jest-util: ^29.5.0 - jest-validate: ^29.5.0 - jest-watcher: ^29.5.0 + jest-changed-files: ^29.7.0 + jest-config: ^29.7.0 + jest-haste-map: ^29.7.0 + jest-message-util: ^29.7.0 + jest-regex-util: ^29.6.3 + jest-resolve: ^29.7.0 + jest-resolve-dependencies: ^29.7.0 + jest-runner: ^29.7.0 + jest-runtime: ^29.7.0 + jest-snapshot: ^29.7.0 + jest-util: ^29.7.0 + jest-validate: ^29.7.0 + jest-watcher: ^29.7.0 micromatch: ^4.0.4 - pretty-format: ^29.5.0 + pretty-format: ^29.7.0 slash: ^3.0.0 strip-ansi: ^6.0.0 peerDependencies: @@ -7558,7 +9629,7 @@ __metadata: peerDependenciesMeta: node-notifier: optional: true - checksum: e4b3e0de48614b2c339083b9159f00a024839984bd89b9afa4cfff4c38f6ce485c2009f2efa1c1e3bb3b87386288bc15798c6aebb7937d7820e8048d75461a4d + checksum: 934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 languageName: node linkType: hard @@ -7586,34 +9657,34 @@ __metadata: languageName: node linkType: hard -"@jest/environment@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/environment@npm:29.5.0" +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" dependencies: - "@jest/fake-timers": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/fake-timers": ^29.7.0 + "@jest/types": ^29.6.3 "@types/node": "*" - jest-mock: ^29.5.0 - checksum: 1fbe63cbfb9c3f6c9fc9d8f6917a5aceee1828d589569bbffcf5fb4bb56bc021dc3a6f239cde3099144767c97763ae134904ee522f236cd8c0d071bd7f9ef63b + jest-mock: ^29.7.0 + checksum: c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 languageName: node linkType: hard -"@jest/expect-utils@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/expect-utils@npm:29.5.0" +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" dependencies: - jest-get-type: ^29.4.3 - checksum: e7f44de651b5ef71c6e1b7a0350a704258167c20b6e8165b3100346d5c7f8eb4cd2c229ea2c048e9161666d1c086fbbc422f111f3b77da3fb89a99d52d4b3690 + jest-get-type: ^29.6.3 + checksum: 60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a languageName: node linkType: hard -"@jest/expect@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/expect@npm:29.5.0" +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" dependencies: - expect: ^29.5.0 - jest-snapshot: ^29.5.0 - checksum: 447e7450af8ba61ac34d8a2ca11c56c62f6f0fb33ff13130f11a1ec9526a08d756ee72da622316a2c52ecfe726fe14432bdfb46e45aff5676f8d1a8efc8d201c + expect: ^29.7.0 + jest-snapshot: ^29.7.0 + checksum: b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e languageName: node linkType: hard @@ -7645,17 +9716,17 @@ __metadata: languageName: node linkType: hard -"@jest/fake-timers@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/fake-timers@npm:29.5.0" +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" dependencies: - "@jest/types": ^29.5.0 + "@jest/types": ^29.6.3 "@sinonjs/fake-timers": ^10.0.2 "@types/node": "*" - jest-message-util: ^29.5.0 - jest-mock: ^29.5.0 - jest-util: ^29.5.0 - checksum: dbf52fd302bf6b3d7ec49499f12835b7d7d4069d61adc62dac233021eba61186bbad3add1ceb3225a23a8745dd04fa0dcc2c38d350ecb0f26eec63f2cf5e6aff + jest-message-util: ^29.7.0 + jest-mock: ^29.7.0 + jest-util: ^29.7.0 + checksum: cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c languageName: node linkType: hard @@ -7670,28 +9741,28 @@ __metadata: languageName: node linkType: hard -"@jest/globals@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/globals@npm:29.5.0" +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" dependencies: - "@jest/environment": ^29.5.0 - "@jest/expect": ^29.5.0 - "@jest/types": ^29.5.0 - jest-mock: ^29.5.0 - checksum: 0c25f07d8125e45cf3c21442e625f6a636eaf7f4cf1cf3f9f66bae059aeb31d3dc61dfff9479eb861a5089dca34c95e231ad88b8925bee42387abecbfe5ecbc2 + "@jest/environment": ^29.7.0 + "@jest/expect": ^29.7.0 + "@jest/types": ^29.6.3 + jest-mock: ^29.7.0 + checksum: a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea languageName: node linkType: hard -"@jest/reporters@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/reporters@npm:29.5.0" +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" dependencies: "@bcoe/v8-coverage": ^0.2.3 - "@jest/console": ^29.5.0 - "@jest/test-result": ^29.5.0 - "@jest/transform": ^29.5.0 - "@jest/types": ^29.5.0 - "@jridgewell/trace-mapping": ^0.3.15 + "@jest/console": ^29.7.0 + "@jest/test-result": ^29.7.0 + "@jest/transform": ^29.7.0 + "@jest/types": ^29.6.3 + "@jridgewell/trace-mapping": ^0.3.18 "@types/node": "*" chalk: ^4.0.0 collect-v8-coverage: ^1.0.0 @@ -7699,13 +9770,13 @@ __metadata: glob: ^7.1.3 graceful-fs: ^4.2.9 istanbul-lib-coverage: ^3.0.0 - istanbul-lib-instrument: ^5.1.0 + istanbul-lib-instrument: ^6.0.0 istanbul-lib-report: ^3.0.0 istanbul-lib-source-maps: ^4.0.0 istanbul-reports: ^3.1.3 - jest-message-util: ^29.5.0 - jest-util: ^29.5.0 - jest-worker: ^29.5.0 + jest-message-util: ^29.7.0 + jest-util: ^29.7.0 + jest-worker: ^29.7.0 slash: ^3.0.0 string-length: ^4.0.1 strip-ansi: ^6.0.0 @@ -7715,7 +9786,7 @@ __metadata: peerDependenciesMeta: node-notifier: optional: true - checksum: 72b771a7749ac2eb9b671f2a886dc98cbe914dfa1a4266854b040e4cc563bf9f5db02b8ff8654b7bfbc3b28caa6d48ca0dde9707454ea4f79d77bd13b6357929 + checksum: a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 languageName: node linkType: hard @@ -7739,14 +9810,14 @@ __metadata: languageName: node linkType: hard -"@jest/source-map@npm:^29.4.3": - version: 29.4.3 - resolution: "@jest/source-map@npm:29.4.3" +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" dependencies: - "@jridgewell/trace-mapping": ^0.3.15 + "@jridgewell/trace-mapping": ^0.3.18 callsites: ^3.0.0 graceful-fs: ^4.2.9 - checksum: 353f9989dcb416e8a2559ad2831b4b3e8446a9f8259782cec97f89903b5c00baa76ea3e23a3f1c83c1ccb3999a9e318b8c6a4bab29e4b66a4abdbb760e445a50 + checksum: a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 languageName: node linkType: hard @@ -7762,27 +9833,27 @@ __metadata: languageName: node linkType: hard -"@jest/test-result@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/test-result@npm:29.5.0" +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" dependencies: - "@jest/console": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/console": ^29.7.0 + "@jest/types": ^29.6.3 "@types/istanbul-lib-coverage": ^2.0.0 collect-v8-coverage: ^1.0.0 - checksum: 5d637c9935ea0438b2a7c106d48756967e5a96fa4426a9b16ea2a3e73e1538eabd10fd4faa8eb46aa4fee710a165e0fd2ce0603dacde5e8a1bba541100854b1d + checksum: 7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 languageName: node linkType: hard -"@jest/test-sequencer@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/test-sequencer@npm:29.5.0" +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" dependencies: - "@jest/test-result": ^29.5.0 + "@jest/test-result": ^29.7.0 graceful-fs: ^4.2.9 - jest-haste-map: ^29.5.0 + jest-haste-map: ^29.7.0 slash: ^3.0.0 - checksum: 6fb7549a5dbe2da6817eb853134f76cf2b320b283900c5e63c997ecfadc616379372a49ac8c0f4ffdb9616eed4a5908c74cb7a560a395a6e1dc0d072b865657b + checksum: 593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b languageName: node linkType: hard @@ -7809,26 +9880,26 @@ __metadata: languageName: node linkType: hard -"@jest/transform@npm:^29.5.0": - version: 29.5.0 - resolution: "@jest/transform@npm:29.5.0" +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" dependencies: "@babel/core": ^7.11.6 - "@jest/types": ^29.5.0 - "@jridgewell/trace-mapping": ^0.3.15 + "@jest/types": ^29.6.3 + "@jridgewell/trace-mapping": ^0.3.18 babel-plugin-istanbul: ^6.1.1 chalk: ^4.0.0 convert-source-map: ^2.0.0 fast-json-stable-stringify: ^2.1.0 graceful-fs: ^4.2.9 - jest-haste-map: ^29.5.0 - jest-regex-util: ^29.4.3 - jest-util: ^29.5.0 + jest-haste-map: ^29.7.0 + jest-regex-util: ^29.6.3 + jest-util: ^29.7.0 micromatch: ^4.0.4 pirates: ^4.0.4 slash: ^3.0.0 write-file-atomic: ^4.0.2 - checksum: 113598311d84ec7e4a4aadd340e332bbfbbd66e20eabea8b2f084b80cf97c1bc9e1ff90278c4f04b227afa95e3386d702363715f9923062c370c042c31911d94 + checksum: 7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 languageName: node linkType: hard @@ -7858,7 +9929,7 @@ __metadata: languageName: node linkType: hard -"@jest/types@npm:^29.5.0, @jest/types@npm:^29.6.3": +"@jest/types@npm:^29.6.3": version: 29.6.3 resolution: "@jest/types@npm:29.6.3" dependencies: @@ -7924,7 +9995,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" dependencies: @@ -9102,6 +11173,16 @@ __metadata: languageName: node linkType: hard +"@smithy/abort-controller@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/abort-controller@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: d5647478fa61d5d1cf3ac8fe5b91955c679ecf48e0d71638c0ce908fbcc87f166e42722d181f33ae3c37761de89e48c5eecf620f6fd0e99cd86edbb8365dd38d + languageName: node + linkType: hard + "@smithy/chunked-blob-reader-native@npm:^3.0.1": version: 3.0.1 resolution: "@smithy/chunked-blob-reader-native@npm:3.0.1" @@ -9112,6 +11193,16 @@ __metadata: languageName: node linkType: hard +"@smithy/chunked-blob-reader-native@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/chunked-blob-reader-native@npm:4.0.0" + dependencies: + "@smithy/util-base64": ^4.0.0 + tslib: ^2.6.2 + checksum: 4387f4e8841f20c1c4e689078141de7e6f239e7883be3a02810a023aa30939b15576ee00227b991972d2c5a2f3b6152bcaeca0975c9fa8d3669354c647bd532a + languageName: node + linkType: hard + "@smithy/chunked-blob-reader@npm:^4.0.0": version: 4.0.0 resolution: "@smithy/chunked-blob-reader@npm:4.0.0" @@ -9121,6 +11212,15 @@ __metadata: languageName: node linkType: hard +"@smithy/chunked-blob-reader@npm:^5.0.0": + version: 5.0.0 + resolution: "@smithy/chunked-blob-reader@npm:5.0.0" + dependencies: + tslib: ^2.6.2 + checksum: 55ba0fe366ddaa3f93e1faf8a70df0b67efedbd0008922295efe215df09b68df0ba3043293e65b17e7d1be71448d074c2bfc54e5eb6bd18f59b425822c2b9e9a + languageName: node + linkType: hard + "@smithy/config-resolver@npm:^3.0.10, @smithy/config-resolver@npm:^3.0.5": version: 3.0.10 resolution: "@smithy/config-resolver@npm:3.0.10" @@ -9134,6 +11234,19 @@ __metadata: languageName: node linkType: hard +"@smithy/config-resolver@npm:^4.1.0": + version: 4.1.0 + resolution: "@smithy/config-resolver@npm:4.1.0" + 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 + checksum: db67064f27981452788ef8cffa3146a1896b50911379febda7315e0657e4fe3bba6c52414670b9778eb986fe06f7e50d10e7373fa644975a0491d27333e909de + languageName: node + linkType: hard + "@smithy/core@npm:^2.3.2, @smithy/core@npm:^2.5.1": version: 2.5.1 resolution: "@smithy/core@npm:2.5.1" @@ -9150,6 +11263,22 @@ __metadata: languageName: node linkType: hard +"@smithy/core@npm:^3.2.0": + version: 3.2.0 + resolution: "@smithy/core@npm:3.2.0" + 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 + checksum: ad514aec318c4863851c8167fdade41ac3393f245038de73b546fcdc6e3ad794c12a661d5248cb56a2e893c2b236db95a93d06c91e53b04e6c2967c31e300567 + languageName: node + linkType: hard + "@smithy/credential-provider-imds@npm:^3.2.0, @smithy/credential-provider-imds@npm:^3.2.5": version: 3.2.5 resolution: "@smithy/credential-provider-imds@npm:3.2.5" @@ -9163,6 +11292,19 @@ __metadata: languageName: node linkType: hard +"@smithy/credential-provider-imds@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/credential-provider-imds@npm:4.0.2" + 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 + checksum: 516482c103bd42d93de584ec75fa75d1184541816a7b430db109f8ec18abcaf8eb7bd072660fbf417f37a3df5c7438a1ba92aabd5a185ed736be1a6e885f0999 + languageName: node + linkType: hard + "@smithy/eventstream-codec@npm:^3.1.7": version: 3.1.7 resolution: "@smithy/eventstream-codec@npm:3.1.7" @@ -9175,6 +11317,18 @@ __metadata: languageName: node linkType: hard +"@smithy/eventstream-codec@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/eventstream-codec@npm:4.0.2" + dependencies: + "@aws-crypto/crc32": 5.2.0 + "@smithy/types": ^4.2.0 + "@smithy/util-hex-encoding": ^4.0.0 + tslib: ^2.6.2 + checksum: 865a44e74c81e3177608f8931e22c92c851f586c1344962db3810b2e23d39d4da2d5f7a933d24481c0b6df219404e34db463e76294d3f71445f7d4e5721aa4ea + languageName: node + linkType: hard + "@smithy/eventstream-serde-browser@npm:^3.0.5": version: 3.0.11 resolution: "@smithy/eventstream-serde-browser@npm:3.0.11" @@ -9186,6 +11340,17 @@ __metadata: languageName: node linkType: hard +"@smithy/eventstream-serde-browser@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/eventstream-serde-browser@npm:4.0.2" + dependencies: + "@smithy/eventstream-serde-universal": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 6974a13448b733b4d98eb368a202a5c2d86389efe10e1d147be1392fb016e010d490368773d915d719973a4d29c419fab7b0eff2dd0abf1f65d1cd3bf26f4fc2 + languageName: node + linkType: hard + "@smithy/eventstream-serde-config-resolver@npm:^3.0.3": version: 3.0.8 resolution: "@smithy/eventstream-serde-config-resolver@npm:3.0.8" @@ -9196,6 +11361,16 @@ __metadata: languageName: node linkType: hard +"@smithy/eventstream-serde-config-resolver@npm:^4.1.0": + version: 4.1.0 + resolution: "@smithy/eventstream-serde-config-resolver@npm:4.1.0" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 41ae76c9ad429e09908658db36f79f0c172496d9ba2727b3566692915bd8ef6df56d692ec1b30d9fa69cfa287d138bccd70422db404d4eef0792fc358d338787 + languageName: node + linkType: hard + "@smithy/eventstream-serde-node@npm:^3.0.4": version: 3.0.10 resolution: "@smithy/eventstream-serde-node@npm:3.0.10" @@ -9207,6 +11382,17 @@ __metadata: languageName: node linkType: hard +"@smithy/eventstream-serde-node@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/eventstream-serde-node@npm:4.0.2" + dependencies: + "@smithy/eventstream-serde-universal": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 6f22010305ac1514d19d78dbb14866bd9b9739a72ef557355514ceb264be6aeb40532758c2e3f70e811a762e7efacd8a6eb64c4d859bdcb79253bf92ee8f60ad + languageName: node + linkType: hard + "@smithy/eventstream-serde-universal@npm:^3.0.10": version: 3.0.10 resolution: "@smithy/eventstream-serde-universal@npm:3.0.10" @@ -9218,6 +11404,17 @@ __metadata: languageName: node linkType: hard +"@smithy/eventstream-serde-universal@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/eventstream-serde-universal@npm:4.0.2" + dependencies: + "@smithy/eventstream-codec": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 1e486919a7d454c4f5a7f8756d74061943dcf5a72b1ba6f735c0e4e34afabe357713e42daed99ea2c4f52995fb37185bd2b02e6778c2aaf02206068e2ebc306c + languageName: node + linkType: hard + "@smithy/fetch-http-handler@npm:^3.2.4": version: 3.2.9 resolution: "@smithy/fetch-http-handler@npm:3.2.9" @@ -9244,6 +11441,19 @@ __metadata: languageName: node linkType: hard +"@smithy/fetch-http-handler@npm:^5.0.2": + version: 5.0.2 + resolution: "@smithy/fetch-http-handler@npm:5.0.2" + 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 + checksum: 3bf84a1fe93c07558a5ba520ab0aca62518c13659d5794094764aaef95acfbcf58ba938c51b9269c485304fdbe7353eb3cd37d7e4c57863d7c50478a9e3ff4fc + languageName: node + linkType: hard + "@smithy/hash-blob-browser@npm:^3.1.2": version: 3.1.7 resolution: "@smithy/hash-blob-browser@npm:3.1.7" @@ -9256,6 +11466,18 @@ __metadata: languageName: node linkType: hard +"@smithy/hash-blob-browser@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/hash-blob-browser@npm:4.0.2" + dependencies: + "@smithy/chunked-blob-reader": ^5.0.0 + "@smithy/chunked-blob-reader-native": ^4.0.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 08b6f1893803c51e7a40256c9c819a0f3a6ff26acf235abe1b2667393094bab982232d0a395d9533e2d4b7af9ab8bedb2ee71ed6bc502669ee5d2901bdabc54a + languageName: node + linkType: hard + "@smithy/hash-node@npm:^3.0.3": version: 3.0.8 resolution: "@smithy/hash-node@npm:3.0.8" @@ -9268,6 +11490,18 @@ __metadata: languageName: node linkType: hard +"@smithy/hash-node@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/hash-node@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + "@smithy/util-buffer-from": ^4.0.0 + "@smithy/util-utf8": ^4.0.0 + tslib: ^2.6.2 + checksum: aaec3fb2146d4347e97067de4dd91759de9d0254d03e234dcced1cbd52cf8b3a77067d571bd5767cb6295da7aa7261b87a789bd597cbc45a380cd90bb47f3490 + languageName: node + linkType: hard + "@smithy/hash-stream-node@npm:^3.1.2": version: 3.1.7 resolution: "@smithy/hash-stream-node@npm:3.1.7" @@ -9279,6 +11513,17 @@ __metadata: languageName: node linkType: hard +"@smithy/hash-stream-node@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/hash-stream-node@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + "@smithy/util-utf8": ^4.0.0 + tslib: ^2.6.2 + checksum: 4c1477c4064e47e026c0ba051eb643a3ed2f850a4e87a8ee5ff91547e3765ebb64e797ce99553aa341448df0bfa1d9e9d7132216ac66c6d9e45aae82ecb1c4d6 + languageName: node + linkType: hard + "@smithy/invalid-dependency@npm:^3.0.3": version: 3.0.8 resolution: "@smithy/invalid-dependency@npm:3.0.8" @@ -9289,6 +11534,16 @@ __metadata: languageName: node linkType: hard +"@smithy/invalid-dependency@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/invalid-dependency@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: f0b884ba25c371d3d3f507aebc24e598e23edeadf0a74dfd7092fc49c496cd427ab517454ebde454b2a05916719e01aa98f34603a5396455cc2dc009ad8799e8 + languageName: node + linkType: hard + "@smithy/is-array-buffer@npm:^2.0.0": version: 2.0.0 resolution: "@smithy/is-array-buffer@npm:2.0.0" @@ -9307,6 +11562,15 @@ __metadata: languageName: node linkType: hard +"@smithy/is-array-buffer@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/is-array-buffer@npm:4.0.0" + dependencies: + tslib: ^2.6.2 + checksum: ae393fbd5944d710443cd5dd225d1178ef7fb5d6259c14f3e1316ec75e401bda6cf86f7eb98bfd38e5ed76e664b810426a5756b916702cbd418f0933e15e7a3b + languageName: node + linkType: hard + "@smithy/md5-js@npm:^3.0.3": version: 3.0.8 resolution: "@smithy/md5-js@npm:3.0.8" @@ -9318,6 +11582,17 @@ __metadata: languageName: node linkType: hard +"@smithy/md5-js@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/md5-js@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + "@smithy/util-utf8": ^4.0.0 + tslib: ^2.6.2 + checksum: b50962dc5155d44bbc0bc317eaab1144ade8d691c3f0c0e844b45fc1e16423742e9a1628b2358657b405e05ee681cebd3abc72e94a9c362b82bd4efbee5b8076 + languageName: node + linkType: hard + "@smithy/middleware-content-length@npm:^3.0.5": version: 3.0.10 resolution: "@smithy/middleware-content-length@npm:3.0.10" @@ -9329,6 +11604,17 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-content-length@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/middleware-content-length@npm:4.0.2" + dependencies: + "@smithy/protocol-http": ^5.1.0 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 4ab343b68a15cf461f3b5996460a0730463975d9da739cf40cfb5993794023269a8bd857366f855844290fabb2b340abb6ff473cec4bfd3d6653a64f17e00c4a + languageName: node + linkType: hard + "@smithy/middleware-endpoint@npm:^3.1.0, @smithy/middleware-endpoint@npm:^3.2.1": version: 3.2.1 resolution: "@smithy/middleware-endpoint@npm:3.2.1" @@ -9341,7 +11627,23 @@ __metadata: "@smithy/url-parser": ^3.0.8 "@smithy/util-middleware": ^3.0.8 tslib: ^2.6.2 - checksum: d1d6406840262388a5845a29d9a2e956a2f3c42f0fb981cd34b95145a5a509bebd25b3e4fad73951b56ff71757d00f7e8ec23bc75c6362a97dacab114ecf9140 + checksum: d1d6406840262388a5845a29d9a2e956a2f3c42f0fb981cd34b95145a5a509bebd25b3e4fad73951b56ff71757d00f7e8ec23bc75c6362a97dacab114ecf9140 + languageName: node + linkType: hard + +"@smithy/middleware-endpoint@npm:^4.1.0": + version: 4.1.0 + resolution: "@smithy/middleware-endpoint@npm:4.1.0" + 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 + checksum: 1d38c793dbe5b32f01f96c6812935753ee14ebf5c9adf9f3782b6d3b36cf48537a7e123bfb7f7447effffa5dde0bd0d79c581cf07e8175fe1fb2b69fe158a41a languageName: node linkType: hard @@ -9362,6 +11664,23 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-retry@npm:^4.1.0": + version: 4.1.0 + resolution: "@smithy/middleware-retry@npm:4.1.0" + 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 + checksum: fe62a5be9f7e81bff08ce495792c8f5c6cffecd3a872125b8e3487b9bb2dd7341ee8804a714ab4cc8b00468f80d6d83fa19b146dfb0a8d38da9502c582655f52 + languageName: node + linkType: hard + "@smithy/middleware-serde@npm:^3.0.3, @smithy/middleware-serde@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/middleware-serde@npm:3.0.8" @@ -9372,6 +11691,16 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-serde@npm:^4.0.3": + version: 4.0.3 + resolution: "@smithy/middleware-serde@npm:4.0.3" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 0a3b037c8f1cade46abf9c782fe11da3f1a92d59f3c61d5806fe26a0f3c8b20d5e7e9ab919549ba33762e63fb02c60b5e436b9459647af39300b37d975acde2e + languageName: node + linkType: hard + "@smithy/middleware-stack@npm:^3.0.3, @smithy/middleware-stack@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/middleware-stack@npm:3.0.8" @@ -9382,6 +11711,16 @@ __metadata: languageName: node linkType: hard +"@smithy/middleware-stack@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/middleware-stack@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: ef94882966431729f7a7bddf8206b6495d67736b1f26fd88d6d6c283a96f9fffd12632ed7352e5f060f17d3ee1845a9a9da1247c26e4c46ff7011aac20b4aacc + languageName: node + linkType: hard + "@smithy/node-config-provider@npm:^3.1.4, @smithy/node-config-provider@npm:^3.1.9": version: 3.1.9 resolution: "@smithy/node-config-provider@npm:3.1.9" @@ -9394,6 +11733,18 @@ __metadata: languageName: node linkType: hard +"@smithy/node-config-provider@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/node-config-provider@npm:4.0.2" + dependencies: + "@smithy/property-provider": ^4.0.2 + "@smithy/shared-ini-file-loader": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 1a3b26835577e6c698a2ce59cd1dd9a3653c75e24847d35a45cd80124d72e0118b84daff47ee1ae0cdb89c134efdf7c7754d0ccf1e1c4b57467865b269b5cd0b + languageName: node + linkType: hard + "@smithy/node-http-handler@npm:^3.1.4, @smithy/node-http-handler@npm:^3.2.5": version: 3.2.5 resolution: "@smithy/node-http-handler@npm:3.2.5" @@ -9407,6 +11758,19 @@ __metadata: languageName: node linkType: hard +"@smithy/node-http-handler@npm:^4.0.4": + version: 4.0.4 + resolution: "@smithy/node-http-handler@npm:4.0.4" + 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 + checksum: fb621c6ebcf012a99fc442d82965ca18d752f66be6f937a400e3b4e3feef1c259c028c27df9e78fc9ac7c40679b25276cbaa8d7ab82fd111bda64003ef831358 + languageName: node + linkType: hard + "@smithy/property-provider@npm:^3.1.3, @smithy/property-provider@npm:^3.1.8": version: 3.1.8 resolution: "@smithy/property-provider@npm:3.1.8" @@ -9417,6 +11781,16 @@ __metadata: languageName: node linkType: hard +"@smithy/property-provider@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/property-provider@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 6effc5ef7895eb4802c6b4c704d5616f50cd0c376da1644176d3aef71396cb65f9df20f4dd85c8301a9fa24f8ac53601e0634463f4364f0d867928efa5eb5e3d + languageName: node + linkType: hard + "@smithy/protocol-http@npm:^4.1.0, @smithy/protocol-http@npm:^4.1.4, @smithy/protocol-http@npm:^4.1.5": version: 4.1.5 resolution: "@smithy/protocol-http@npm:4.1.5" @@ -9427,6 +11801,16 @@ __metadata: languageName: node linkType: hard +"@smithy/protocol-http@npm:^5.1.0": + version: 5.1.0 + resolution: "@smithy/protocol-http@npm:5.1.0" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: bb2f600853c0282630f5f32286a07a37294a57dbbec25ea0c6fbb6be32341b1be83e37933c2e3540e513c90dcb08f492bcb05980cde0b92b083e67ade6d56eb0 + languageName: node + linkType: hard + "@smithy/querystring-builder@npm:^3.0.3, @smithy/querystring-builder@npm:^3.0.7, @smithy/querystring-builder@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/querystring-builder@npm:3.0.8" @@ -9438,6 +11822,17 @@ __metadata: languageName: node linkType: hard +"@smithy/querystring-builder@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/querystring-builder@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + "@smithy/util-uri-escape": ^4.0.0 + tslib: ^2.6.2 + checksum: 2ae27840e21982926182df809872e07d6b10b2fd93b58e02fa3f9588de23d333ddf02f0f3517de8a02a949489733bdcecb8c847980f8fb12ce1f8c3b6d127e86 + languageName: node + linkType: hard + "@smithy/querystring-parser@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/querystring-parser@npm:3.0.8" @@ -9448,6 +11843,16 @@ __metadata: languageName: node linkType: hard +"@smithy/querystring-parser@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/querystring-parser@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: e6115fce0a07b1509f407cd3eca371cce1d9c09c7e3bd9156e35506b8ab1100f9864fb8779d4dbe0169501af23f062ebc2176afc012e9132e917781cd11a2f82 + languageName: node + linkType: hard + "@smithy/service-error-classification@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/service-error-classification@npm:3.0.8" @@ -9457,6 +11862,15 @@ __metadata: languageName: node linkType: hard +"@smithy/service-error-classification@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/service-error-classification@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + checksum: a1f16a891cf96fad624e928d2e55d8b438b2acbb57098d615486bf01488a22f949223127a15e93b273e099a227cbe2ce7be3a3f538d1a4619fb2554dcf33d3dd + languageName: node + linkType: hard + "@smithy/shared-ini-file-loader@npm:^3.1.4, @smithy/shared-ini-file-loader@npm:^3.1.9": version: 3.1.9 resolution: "@smithy/shared-ini-file-loader@npm:3.1.9" @@ -9467,6 +11881,16 @@ __metadata: languageName: node linkType: hard +"@smithy/shared-ini-file-loader@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/shared-ini-file-loader@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 1e3d4921b6efbd1aa448a775dcb9a490d0221dd0a4fee434c5d83376de478013b3ad06d58a3d52db781124d4a53bd289fffcdb52eabffe9de152b0010332cee2 + languageName: node + linkType: hard + "@smithy/signature-v4@npm:^4.1.0": version: 4.2.1 resolution: "@smithy/signature-v4@npm:4.2.1" @@ -9483,6 +11907,22 @@ __metadata: languageName: node linkType: hard +"@smithy/signature-v4@npm:^5.1.0": + version: 5.1.0 + resolution: "@smithy/signature-v4@npm:5.1.0" + 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 + checksum: 7f3aed4999b47f04485846a90a08d0863c8bf4201a38616faf4bcb3166892a5b2946e7d0f1d5dc068b667913713873e21ab8374d60c1ff02828972d8c9201282 + languageName: node + linkType: hard + "@smithy/smithy-client@npm:^3.1.12, @smithy/smithy-client@npm:^3.4.2": version: 3.4.2 resolution: "@smithy/smithy-client@npm:3.4.2" @@ -9498,6 +11938,21 @@ __metadata: languageName: node linkType: hard +"@smithy/smithy-client@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/smithy-client@npm:4.2.0" + 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 + checksum: 0d7ac7549edd92a7c50abcfdb9607f729533ff1aa5f70fbe7e3f9a47a272f0f1487094fb72f421d0bbf303b335e349bfd3db3f5b9e841037c3f23f47febb0f6b + languageName: node + linkType: hard + "@smithy/types@npm:^3.3.0, @smithy/types@npm:^3.5.0, @smithy/types@npm:^3.6.0": version: 3.6.0 resolution: "@smithy/types@npm:3.6.0" @@ -9507,6 +11962,15 @@ __metadata: languageName: node linkType: hard +"@smithy/types@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/types@npm:4.2.0" + dependencies: + tslib: ^2.6.2 + checksum: a8bd92c7e548bcbe7be211152de041ec164cfcc857d7574a87b1667c38827e5616563c13bd38a1d44b88bbfa3ee8f591dc597d4e2d50f3bc74e320ea82d7c49e + languageName: node + linkType: hard + "@smithy/url-parser@npm:^3.0.3, @smithy/url-parser@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/url-parser@npm:3.0.8" @@ -9518,6 +11982,17 @@ __metadata: languageName: node linkType: hard +"@smithy/url-parser@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/url-parser@npm:4.0.2" + dependencies: + "@smithy/querystring-parser": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 3da40fc18871c145bcbbb036a3d767ae113b954e94c745770f268dc877378cbafa6fc06759ea5a5e5c159a88e7331739b35b69f4d110ba0bd04b2d0923443f32 + languageName: node + linkType: hard + "@smithy/util-base64@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-base64@npm:3.0.0" @@ -9529,6 +12004,17 @@ __metadata: languageName: node linkType: hard +"@smithy/util-base64@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-base64@npm:4.0.0" + dependencies: + "@smithy/util-buffer-from": ^4.0.0 + "@smithy/util-utf8": ^4.0.0 + tslib: ^2.6.2 + checksum: ad18ec66cc357c189eef358d96876b114faf7086b13e47e009b265d0ff80cec046052500489c183957b3a036768409acdd1a373e01074cc002ca6983f780cffc + languageName: node + linkType: hard + "@smithy/util-body-length-browser@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-body-length-browser@npm:3.0.0" @@ -9538,6 +12024,15 @@ __metadata: languageName: node linkType: hard +"@smithy/util-body-length-browser@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-body-length-browser@npm:4.0.0" + dependencies: + tslib: ^2.6.2 + checksum: 574a10934024a86556e9dcde1a9776170284326c3dfcc034afa128cc5a33c1c8179fca9cfb622ef8be5f2004316cc3f427badccceb943e829105536ec26306d9 + languageName: node + linkType: hard + "@smithy/util-body-length-node@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-body-length-node@npm:3.0.0" @@ -9547,6 +12042,15 @@ __metadata: languageName: node linkType: hard +"@smithy/util-body-length-node@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-body-length-node@npm:4.0.0" + dependencies: + tslib: ^2.6.2 + checksum: e91fd3816767606c5f786166ada26440457fceb60f96653b3d624dcf762a8c650e513c275ff3f647cb081c63c283cc178853a7ed9aa224abc8ece4eeeef7a1dd + languageName: node + linkType: hard + "@smithy/util-buffer-from@npm:^2.0.0": version: 2.0.0 resolution: "@smithy/util-buffer-from@npm:2.0.0" @@ -9567,6 +12071,16 @@ __metadata: languageName: node linkType: hard +"@smithy/util-buffer-from@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-buffer-from@npm:4.0.0" + dependencies: + "@smithy/is-array-buffer": ^4.0.0 + tslib: ^2.6.2 + checksum: be7cd33b6cb91503982b297716251e67cdca02819a15797632091cadab2dc0b4a147fff0709a0aa9bbc0b82a2644a7ed7c8afdd2194d5093cee2e9605b3a9f6f + languageName: node + linkType: hard + "@smithy/util-config-provider@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-config-provider@npm:3.0.0" @@ -9576,6 +12090,15 @@ __metadata: languageName: node linkType: hard +"@smithy/util-config-provider@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-config-provider@npm:4.0.0" + dependencies: + tslib: ^2.6.2 + checksum: cd9498d5f77a73aadd575084bcb22d2bb5945bac4605d605d36f2efe3f165f2b60f4dc88b7a62c2ed082ffa4b2c2f19621d0859f18399edbc2b5988d92e4649f + languageName: node + linkType: hard + "@smithy/util-defaults-mode-browser@npm:^3.0.14": version: 3.0.25 resolution: "@smithy/util-defaults-mode-browser@npm:3.0.25" @@ -9589,6 +12112,19 @@ __metadata: languageName: node linkType: hard +"@smithy/util-defaults-mode-browser@npm:^4.0.8": + version: 4.0.8 + resolution: "@smithy/util-defaults-mode-browser@npm:4.0.8" + 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 + checksum: ada59bc98f2538d189363bc8e22c7cb72b9ddd06142fbca54921efa5742cc248e8ea06f79ec679cb916683d3ac9e3a35bafb6377ee5d4cff8715e46de1445b81 + languageName: node + linkType: hard + "@smithy/util-defaults-mode-node@npm:^3.0.14": version: 3.0.25 resolution: "@smithy/util-defaults-mode-node@npm:3.0.25" @@ -9604,6 +12140,21 @@ __metadata: languageName: node linkType: hard +"@smithy/util-defaults-mode-node@npm:^4.0.8": + version: 4.0.8 + resolution: "@smithy/util-defaults-mode-node@npm:4.0.8" + 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 + checksum: cbdfe00d5cd645250ca49416ebddcfc1055da3412826cf0baa75d4af6e58765ac7ea4b262a48c5bbd4e60e4329e362b76f96c319db1112b2d92b506c82bc002a + languageName: node + linkType: hard + "@smithy/util-endpoints@npm:^2.0.5": version: 2.1.4 resolution: "@smithy/util-endpoints@npm:2.1.4" @@ -9615,6 +12166,17 @@ __metadata: languageName: node linkType: hard +"@smithy/util-endpoints@npm:^3.0.2": + version: 3.0.2 + resolution: "@smithy/util-endpoints@npm:3.0.2" + dependencies: + "@smithy/node-config-provider": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 5d2fe3956dc528842c071329bc69bd6567462858c1fbb1cc7ca19622227a803b54d95f44f3ac703852bce6349f455bfec599aea51df56d02e3c8b12e6481c27a + languageName: node + linkType: hard + "@smithy/util-hex-encoding@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-hex-encoding@npm:3.0.0" @@ -9624,6 +12186,15 @@ __metadata: languageName: node linkType: hard +"@smithy/util-hex-encoding@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-hex-encoding@npm:4.0.0" + dependencies: + tslib: ^2.6.2 + checksum: 70dbb3aa1a79aff3329d07a66411ff26398df338bdd8a6d077b438231afe3dc86d9a7022204baddecd8bc633f059d5c841fa916d81dd7447ea79b64148f386d2 + languageName: node + linkType: hard + "@smithy/util-middleware@npm:^3.0.3, @smithy/util-middleware@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/util-middleware@npm:3.0.8" @@ -9634,6 +12205,16 @@ __metadata: languageName: node linkType: hard +"@smithy/util-middleware@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/util-middleware@npm:4.0.2" + dependencies: + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 18c3882c94f1b1bbb3825c30d1e41ae77a8da3dcd93ebbf1c486f34d5db9e06431789aef54d1b1fbb0424b115fc1e1ae17d27efe4af4277173d901a76147fef8 + languageName: node + linkType: hard + "@smithy/util-retry@npm:^3.0.3, @smithy/util-retry@npm:^3.0.8": version: 3.0.8 resolution: "@smithy/util-retry@npm:3.0.8" @@ -9645,6 +12226,17 @@ __metadata: languageName: node linkType: hard +"@smithy/util-retry@npm:^4.0.2": + version: 4.0.2 + resolution: "@smithy/util-retry@npm:4.0.2" + dependencies: + "@smithy/service-error-classification": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: c2b98faa4171f620aa17a0f0a91dff9215a4729242a040ad25aee4c716752a64944cc58031ae71bcf3fc320b3e84cb3da4648e5bbccd782126a721e588d98b87 + languageName: node + linkType: hard + "@smithy/util-stream@npm:^3.1.3, @smithy/util-stream@npm:^3.2.1": version: 3.2.1 resolution: "@smithy/util-stream@npm:3.2.1" @@ -9661,6 +12253,22 @@ __metadata: languageName: node linkType: hard +"@smithy/util-stream@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-stream@npm:4.2.0" + 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 + checksum: 52449a6ec68a483fdeef816128c923c744e278f6cf4d5b6fbe50e29fa8b6e5813df26221389f22bce143deb91f047ac56f87db85306908c5d0b87460e162bf63 + languageName: node + linkType: hard + "@smithy/util-uri-escape@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/util-uri-escape@npm:3.0.0" @@ -9670,6 +12278,15 @@ __metadata: languageName: node linkType: hard +"@smithy/util-uri-escape@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-uri-escape@npm:4.0.0" + dependencies: + tslib: ^2.6.2 + checksum: 23984624060756adba8aa4ab1693fe6b387ee5064d8ec4dfd39bb5908c4ee8b9c3f2dc755da9b07505d8e3ce1338c1867abfa74158931e4728bf3cfcf2c05c3d + languageName: node + linkType: hard + "@smithy/util-utf8@npm:^2.0.0": version: 2.0.0 resolution: "@smithy/util-utf8@npm:2.0.0" @@ -9690,6 +12307,16 @@ __metadata: languageName: node linkType: hard +"@smithy/util-utf8@npm:^4.0.0": + version: 4.0.0 + resolution: "@smithy/util-utf8@npm:4.0.0" + dependencies: + "@smithy/util-buffer-from": ^4.0.0 + tslib: ^2.6.2 + checksum: 28a5a5372cbf0b3d2e32dd16f79b04c2aec6f704cf13789db922e9686fde38dde0171491cfa4c2c201595d54752a319faaeeed3c325329610887694431e28c98 + languageName: node + linkType: hard + "@smithy/util-waiter@npm:^3.1.2": version: 3.1.7 resolution: "@smithy/util-waiter@npm:3.1.7" @@ -9701,6 +12328,17 @@ __metadata: languageName: node linkType: hard +"@smithy/util-waiter@npm:^4.0.3": + version: 4.0.3 + resolution: "@smithy/util-waiter@npm:4.0.3" + dependencies: + "@smithy/abort-controller": ^4.0.2 + "@smithy/types": ^4.2.0 + tslib: ^2.6.2 + checksum: 0ca992cd85719b367655943df08e8f7f0dd0f4ffe335809de7ed4c133ee2db5b58a2661cfc43040cf91512ef21783c8302fc2352f95910ecf3f0a50b0e32c2ff + languageName: node + linkType: hard + "@surma/rollup-plugin-off-main-thread@npm:^2.2.3": version: 2.2.3 resolution: "@surma/rollup-plugin-off-main-thread@npm:2.2.3" @@ -10497,12 +13135,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^18.16.0, @types/node@npm:^18.16.1": - version: 18.19.31 - resolution: "@types/node@npm:18.19.31" +"@types/node@npm:*, @types/node@npm:^20.14.2": + version: 20.17.31 + resolution: "@types/node@npm:20.17.31" dependencies: - undici-types: ~5.26.4 - checksum: bfebae8389220c0188492c82eaf328f4ba15e6e9b4abee33d6bf36d3b13f188c2f53eb695d427feb882fff09834f467405e2ed9be6aeb6ad4705509822d2ea08 + undici-types: ~6.19.2 + checksum: ab6fb60a7957bef39a9fcd3b9fbd8ea2f4a51d7b8522b0fd56e11b9fbbd3ff6cbd361e3bd510cdf9bbef54956332374c6646e28cce8ae597244623a0659153d0 languageName: node linkType: hard @@ -10520,6 +13158,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^18.16.0, @types/node@npm:^18.16.1": + version: 18.19.31 + resolution: "@types/node@npm:18.19.31" + dependencies: + undici-types: ~5.26.4 + checksum: bfebae8389220c0188492c82eaf328f4ba15e6e9b4abee33d6bf36d3b13f188c2f53eb695d427feb882fff09834f467405e2ed9be6aeb6ad4705509822d2ea08 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.1 resolution: "@types/normalize-package-data@npm:2.4.1" @@ -10772,6 +13419,15 @@ __metadata: languageName: node linkType: hard +"@types/unzipper@npm:^0.10.9": + version: 0.10.11 + resolution: "@types/unzipper@npm:0.10.11" + dependencies: + "@types/node": "*" + checksum: 2ed949c2e6f7b22b2a91bde8a127265d1e3201baf1e8603e22836db58295170bdd1e25f4a4487c2daedab4a0340458f6adc0fa27f95429211f4dee261383a3b0 + languageName: node + linkType: hard + "@types/update-notifier@npm:^5.1.0": version: 5.1.0 resolution: "@types/update-notifier@npm:5.1.0" @@ -10789,6 +13445,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^9.0.1": + version: 9.0.8 + resolution: "@types/uuid@npm:9.0.8" + checksum: b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 + languageName: node + linkType: hard + "@types/which@npm:^1.3.2": version: 1.3.2 resolution: "@types/which@npm:1.3.2" @@ -11400,6 +14063,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: ^5.0.0 + checksum: 90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 + languageName: node + linkType: hard + "accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -11504,7 +14176,7 @@ __metadata: languageName: node linkType: hard -"agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.3 resolution: "agent-base@npm:7.1.3" checksum: 6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 @@ -11703,11 +14375,11 @@ __metadata: languageName: node linkType: hard -"amplify-dotnet-function-runtime-provider@2.1.4, amplify-dotnet-function-runtime-provider@workspace:packages/amplify-dotnet-function-runtime-provider": +"amplify-dotnet-function-runtime-provider@2.1.5-next-11.0, amplify-dotnet-function-runtime-provider@workspace:packages/amplify-dotnet-function-runtime-provider": version: 0.0.0-use.local resolution: "amplify-dotnet-function-runtime-provider@workspace:packages/amplify-dotnet-function-runtime-provider" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@aws-amplify/amplify-prompts": 2.8.6 "@types/node": ^12.12.6 @@ -11719,11 +14391,11 @@ __metadata: languageName: unknown linkType: soft -"amplify-dynamodb-simulator@2.9.23, amplify-dynamodb-simulator@workspace:packages/amplify-dynamodb-simulator": +"amplify-dynamodb-simulator@2.9.24-next-11.0, amplify-dynamodb-simulator@workspace:packages/amplify-dynamodb-simulator": version: 0.0.0-use.local resolution: "amplify-dynamodb-simulator@workspace:packages/amplify-dynamodb-simulator" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 aws-sdk: ^2.1464.0 detect-port: ^1.3.0 execa: ^5.1.1 @@ -11744,10 +14416,10 @@ __metadata: version: 0.0.0-use.local resolution: "amplify-e2e-tests@workspace:packages/amplify-e2e-tests" dependencies: - "@aws-amplify/amplify-category-auth": 3.7.21 - "@aws-amplify/amplify-cli-core": 4.4.1 - "@aws-amplify/amplify-e2e-core": 5.7.4 - "@aws-amplify/amplify-opensearch-simulator": 1.7.19 + "@aws-amplify/amplify-category-auth": 3.7.22-next-11.0 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 + "@aws-amplify/amplify-e2e-core": 5.7.5-next-11.0 + "@aws-amplify/amplify-opensearch-simulator": 1.7.20-next-11.0 "@aws-amplify/graphql-transformer-core": ^2.11.1 "@aws-sdk/client-appsync": 3.624.0 "@aws-sdk/client-dynamodb": 3.624.0 @@ -11761,9 +14433,9 @@ __metadata: "@types/node": ^18.16.1 "@types/openpgp": ^4.4.18 "@types/ws": ^7.4.4 - amplify-dynamodb-simulator: 2.9.23 + amplify-dynamodb-simulator: 2.9.24-next-11.0 amplify-headless-interface: 1.17.7 - amplify-storage-simulator: 1.11.6 + amplify-storage-simulator: 1.11.7-next-11.0 aws-amplify: ^5.3.16 aws-appsync: ^4.1.1 aws-cdk-lib: ~2.189.1 @@ -11796,11 +14468,11 @@ __metadata: languageName: unknown linkType: soft -"amplify-go-function-runtime-provider@2.3.51, amplify-go-function-runtime-provider@workspace:packages/amplify-go-function-runtime-provider": +"amplify-go-function-runtime-provider@2.3.52-next-11.0, amplify-go-function-runtime-provider@workspace:packages/amplify-go-function-runtime-provider": version: 0.0.0-use.local resolution: "amplify-go-function-runtime-provider@workspace:packages/amplify-go-function-runtime-provider" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@types/archiver": ^5.1.1 "@types/node": ^12.12.6 @@ -11826,11 +14498,11 @@ __metadata: languageName: unknown linkType: soft -"amplify-java-function-runtime-provider@2.3.51, amplify-java-function-runtime-provider@workspace:packages/amplify-java-function-runtime-provider": +"amplify-java-function-runtime-provider@2.3.52-next-11.0, amplify-java-function-runtime-provider@workspace:packages/amplify-java-function-runtime-provider": version: 0.0.0-use.local resolution: "amplify-java-function-runtime-provider@workspace:packages/amplify-java-function-runtime-provider" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@types/node": ^12.12.6 "@types/semver": ^7.1.0 @@ -11851,11 +14523,11 @@ __metadata: languageName: unknown linkType: soft -"amplify-nodejs-function-runtime-provider@2.5.29, amplify-nodejs-function-runtime-provider@workspace:packages/amplify-nodejs-function-runtime-provider": +"amplify-nodejs-function-runtime-provider@2.5.30-next-11.0, amplify-nodejs-function-runtime-provider@workspace:packages/amplify-nodejs-function-runtime-provider": version: 0.0.0-use.local resolution: "amplify-nodejs-function-runtime-provider@workspace:packages/amplify-nodejs-function-runtime-provider" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@types/exit": ^0.1.31 "@types/node": ^12.12.6 @@ -11868,11 +14540,11 @@ __metadata: languageName: unknown linkType: soft -"amplify-python-function-runtime-provider@2.4.51, amplify-python-function-runtime-provider@workspace:packages/amplify-python-function-runtime-provider": +"amplify-python-function-runtime-provider@2.4.52-next-11.0, amplify-python-function-runtime-provider@workspace:packages/amplify-python-function-runtime-provider": version: 0.0.0-use.local resolution: "amplify-python-function-runtime-provider@workspace:packages/amplify-python-function-runtime-provider" dependencies: - "@aws-amplify/amplify-cli-core": 4.4.1 + "@aws-amplify/amplify-cli-core": 4.4.2-next-11.0 "@aws-amplify/amplify-function-plugin-interface": 1.12.1 "@types/fs-extra": ^8.0.1 "@types/node": ^12.12.6 @@ -11885,7 +14557,7 @@ __metadata: languageName: unknown linkType: soft -"amplify-storage-simulator@1.11.6, amplify-storage-simulator@workspace:packages/amplify-storage-simulator": +"amplify-storage-simulator@1.11.7-next-11.0, amplify-storage-simulator@workspace:packages/amplify-storage-simulator": version: 0.0.0-use.local resolution: "amplify-storage-simulator@workspace:packages/amplify-storage-simulator" dependencies: @@ -11923,7 +14595,7 @@ __metadata: languageName: unknown linkType: soft -"amplify-velocity-template@1.4.15, amplify-velocity-template@workspace:packages/amplify-velocity-template": +"amplify-velocity-template@1.4.16-next-11.0, amplify-velocity-template@workspace:packages/amplify-velocity-template": version: 0.0.0-use.local resolution: "amplify-velocity-template@workspace:packages/amplify-velocity-template" dependencies: @@ -12234,6 +14906,21 @@ __metadata: languageName: node linkType: hard +"archiver-utils@npm:^5.0.0, archiver-utils@npm:^5.0.2": + version: 5.0.2 + resolution: "archiver-utils@npm:5.0.2" + dependencies: + glob: ^10.0.0 + graceful-fs: ^4.2.0 + is-stream: ^2.0.1 + lazystream: ^1.0.0 + lodash: ^4.17.15 + normalize-path: ^3.0.0 + readable-stream: ^4.0.0 + checksum: 3782c5fa9922186aa1a8e41ed0c2867569faa5f15c8e5e6418ea4c1b730b476e21bd68270b3ea457daf459ae23aaea070b2b9f90cf90a59def8dc79b9e4ef538 + languageName: node + linkType: hard + "archiver@npm:^5.3.0": version: 5.3.0 resolution: "archiver@npm:5.3.0" @@ -12249,6 +14936,21 @@ __metadata: languageName: node linkType: hard +"archiver@npm:^7.0.1": + version: 7.0.1 + resolution: "archiver@npm:7.0.1" + dependencies: + archiver-utils: ^5.0.2 + async: ^3.2.4 + buffer-crc32: ^1.0.0 + readable-stream: ^4.0.0 + readdir-glob: ^1.1.2 + tar-stream: ^3.0.0 + zip-stream: ^6.0.1 + checksum: 02afd87ca16f6184f752db8e26884e6eff911c476812a0e7f7b26c4beb09f06119807f388a8e26ed2558aa8ba9db28646ebd147a4f99e46813b8b43158e1438e + languageName: node + linkType: hard + "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -12498,10 +15200,10 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.1.0, async@npm:^3.2.0": - version: 3.2.3 - resolution: "async@npm:3.2.3" - checksum: 109780c846f05109dde14412d916ae4ed6daf6f9aad0c4aa1dcf0d4da775a3a9e35e0e06e4e06ad9fed66f99ca15549da16f2f243c56103b346e9d3bcd9c943f +"async@npm:^3.1.0, async@npm:^3.2.0, async@npm:^3.2.4": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 languageName: node linkType: hard @@ -12642,6 +15344,30 @@ __metadata: languageName: node linkType: hard +"aws-cdk-lib@npm:~2.187.0": + version: 2.187.0 + resolution: "aws-cdk-lib@npm:2.187.0" + dependencies: + "@aws-cdk/asset-awscli-v1": ^2.2.229 + "@aws-cdk/asset-node-proxy-agent-v6": ^2.1.0 + "@aws-cdk/cloud-assembly-schema": ^41.0.0 + "@balena/dockerignore": ^1.0.2 + case: 1.6.3 + fs-extra: ^11.3.0 + ignore: ^5.3.2 + jsonschema: ^1.5.0 + mime-types: ^2.1.35 + minimatch: ^3.1.2 + punycode: ^2.3.1 + semver: ^7.7.1 + table: ^6.9.0 + yaml: 1.10.2 + peerDependencies: + constructs: ^10.0.0 + checksum: 9b2d97e3e21d296ccf73d099512d380bbc9fd464acd5c02e521b869af82a2c74eef7dd70695012759911bf02eb0e77267b4283b2b7b10643b740c59dea727398 + languageName: node + linkType: hard + "aws-cdk-lib@npm:~2.189.1": version: 2.189.1 resolution: "aws-cdk-lib@npm:2.189.1" @@ -12734,6 +15460,13 @@ __metadata: languageName: node linkType: hard +"b4a@npm:^1.6.4": + version: 1.6.7 + resolution: "b4a@npm:1.6.7" + checksum: ec2f004d1daae04be8c5a1f8aeb7fea213c34025e279db4958eb0b82c1729ee25f7c6e89f92a5f65c8a9cf2d017ce27e3dda912403341d1781bd74528a4849d4 + languageName: node + linkType: hard + "babel-eslint@npm:^10.1.0": version: 10.1.0 resolution: "babel-eslint@npm:10.1.0" @@ -12784,20 +15517,20 @@ __metadata: languageName: node linkType: hard -"babel-jest@npm:^29.5.0": - version: 29.5.0 - resolution: "babel-jest@npm:29.5.0" +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" dependencies: - "@jest/transform": ^29.5.0 + "@jest/transform": ^29.7.0 "@types/babel__core": ^7.1.14 babel-plugin-istanbul: ^6.1.1 - babel-preset-jest: ^29.5.0 + babel-preset-jest: ^29.6.3 chalk: ^4.0.0 graceful-fs: ^4.2.9 slash: ^3.0.0 peerDependencies: "@babel/core": ^7.8.0 - checksum: 1114d3935e0f62b72e155ac79916214c078e798561be3b03d12ddd862f2849becc8516f89046719161ec457bded35d2e1fd7ddfb207a6169dd18bbb2a67ee987 + checksum: 2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 languageName: node linkType: hard @@ -12859,15 +15592,15 @@ __metadata: languageName: node linkType: hard -"babel-plugin-jest-hoist@npm:^29.5.0": - version: 29.5.0 - resolution: "babel-plugin-jest-hoist@npm:29.5.0" +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" dependencies: "@babel/template": ^7.3.3 "@babel/types": ^7.3.3 "@types/babel__core": ^7.1.14 "@types/babel__traverse": ^7.0.6 - checksum: 385547c4d81647848dc3e86fecf4381032be99ed97d87aee78d422631f651042600371ee31e37ec9bb6f4a0a4f296b3b5798d69c410626ea94eae76d9c64da63 + checksum: 7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e languageName: node linkType: hard @@ -13024,15 +15757,15 @@ __metadata: languageName: node linkType: hard -"babel-preset-jest@npm:^29.5.0": - version: 29.5.0 - resolution: "babel-preset-jest@npm:29.5.0" +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" dependencies: - babel-plugin-jest-hoist: ^29.5.0 + babel-plugin-jest-hoist: ^29.6.3 babel-preset-current-node-syntax: ^1.0.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 752b8682c8cf55bca46d870003f4ce43a4ba0fcaa1138ff7f0e02340628e221810b0c2c3e77a7d5070168dc163eb11907f6c9256f187242abe0f14219d1f6b12 + checksum: ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 languageName: node linkType: hard @@ -13089,6 +15822,13 @@ __metadata: languageName: node linkType: hard +"bare-events@npm:^2.2.0": + version: 2.5.4 + resolution: "bare-events@npm:2.5.4" + checksum: 877a9cea73d545e2588cdbd6fd01653e27dac48ad6b44985cdbae73e1f57f292d4ba52e25d1fba53674c1053c463d159f3d5c7bc36a2e6e192e389b499ddd627 + languageName: node + linkType: hard + "base-64@npm:1.0.0": version: 1.0.0 resolution: "base-64@npm:1.0.0" @@ -13483,6 +16223,13 @@ __metadata: languageName: node linkType: hard +"buffer-crc32@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-crc32@npm:1.0.0" + checksum: 8b86e161cee4bb48d5fa622cbae4c18f25e4857e5203b89e23de59e627ab26beb82d9d7999f2b8de02580165f61f83f997beaf02980cdf06affd175b651921ab + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -13724,7 +16471,7 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.0.0, camelcase@npm:^6.2.0, camelcase@npm:^6.2.1": +"camelcase@npm:^6, camelcase@npm:^6.0.0, camelcase@npm:^6.2.0, camelcase@npm:^6.2.1": version: 6.3.0 resolution: "camelcase@npm:6.3.0" checksum: 0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 @@ -13789,6 +16536,38 @@ __metadata: languageName: node linkType: hard +"cdk-assets@npm:^3.2.0": + version: 3.2.1 + resolution: "cdk-assets@npm:3.2.1" + dependencies: + "@aws-cdk/cloud-assembly-schema": ">=43.5.0" + "@aws-cdk/cx-api": ^2.190.0 + "@aws-sdk/client-ecr": ^3 + "@aws-sdk/client-s3": ^3 + "@aws-sdk/client-secrets-manager": ^3 + "@aws-sdk/client-sts": ^3 + "@aws-sdk/credential-providers": ^3 + "@aws-sdk/lib-storage": ^3 + "@smithy/config-resolver": ^4.1.0 + "@smithy/node-config-provider": ^4.0.2 + archiver: ^7.0.1 + glob: ^11.0.1 + mime: ^2 + yargs: ^17.7.2 + bin: + cdk-assets: bin/cdk-assets + docker-credential-cdk-assets: bin/docker-credential-cdk-assets + checksum: 94dc0817c5eeea0ff9da31d70c25c86c104ab121957fb52216e8fad28b74fb76ebe480f46ee5f109c29b52789695a492b7b5e45c9e80b0c22472362f83a16932 + languageName: node + linkType: hard + +"cdk-from-cfn@npm:^0.205.0": + version: 0.205.0 + resolution: "cdk-from-cfn@npm:0.205.0" + checksum: 99eab5277f0f034b4224ec231de60baef9ff942f018e892105f284ac94a12ebfef4b80ec3baeaaa7324a60228788046021fe5ef05565163a77f32ef96b22af43 + languageName: node + linkType: hard + "chalk@npm:4.1.0": version: 4.1.0 resolution: "chalk@npm:4.1.0" @@ -13928,9 +16707,9 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.4.0, chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": - version: 3.5.3 - resolution: "chokidar@npm:3.5.3" +"chokidar@npm:^3, chokidar@npm:^3.4.0, chokidar@npm:^3.4.2, chokidar@npm:^3.5.3": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" dependencies: anymatch: ~3.1.2 braces: ~3.0.2 @@ -13943,7 +16722,7 @@ __metadata: dependenciesMeta: fsevents: optional: true - checksum: 1076953093e0707c882a92c66c0f56ba6187831aa51bb4de878c1fec59ae611a3bf02898f190efec8e77a086b8df61c2b2a3ea324642a0558bdf8ee6c5dc9ca1 + checksum: 8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 languageName: node linkType: hard @@ -13982,7 +16761,7 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.0.0": +"ci-info@npm:^4.0.0, ci-info@npm:^4.1.0": version: 4.2.0 resolution: "ci-info@npm:4.2.0" checksum: 37a2f4b6a213a5cf835890eb0241f0d5b022f6cfefde58a69e9af8e3a0e71e06d6ad7754b0d4efb9cd2613e58a7a33996d71b56b0d04242722e86666f3f3d058 @@ -14342,7 +17121,7 @@ __metadata: languageName: node linkType: hard -"colors@npm:1.4.0, colors@npm:^1.1.2, colors@npm:^1.2.1": +"colors@npm:1.4.0, colors@npm:^1.1.2, colors@npm:^1.2.1, colors@npm:^1.4.0": version: 1.4.0 resolution: "colors@npm:1.4.0" checksum: 9af357c019da3c5a098a301cf64e3799d27549d8f185d86f79af23069e4f4303110d115da98483519331f6fb71c8568d5688fa1c6523600044fd4a54e97c4efb @@ -14497,6 +17276,19 @@ __metadata: languageName: node linkType: hard +"compress-commons@npm:^6.0.2": + version: 6.0.2 + resolution: "compress-commons@npm:6.0.2" + dependencies: + crc-32: ^1.2.0 + crc32-stream: ^6.0.0 + is-stream: ^2.0.1 + normalize-path: ^3.0.0 + readable-stream: ^4.0.0 + checksum: 2347031b7c92c8ed5011b07b93ec53b298fa2cd1800897532ac4d4d1aeae06567883f481b6e35f13b65fc31b190c751df6635434d525562f0203fde76f1f0814 + languageName: node + linkType: hard + "compressible@npm:~2.0.16": version: 2.0.18 resolution: "compressible@npm:2.0.18" @@ -14983,6 +17775,16 @@ __metadata: languageName: node linkType: hard +"crc32-stream@npm:^6.0.0": + version: 6.0.0 + resolution: "crc32-stream@npm:6.0.0" + dependencies: + crc-32: ^1.2.0 + readable-stream: ^4.0.0 + checksum: bf9c84571ede2d119c2b4f3a9ef5eeb9ff94b588493c0d3862259af86d3679dcce1c8569dd2b0a6eff2f35f5e2081cc1263b846d2538d4054da78cf34f262a3d + languageName: node + linkType: hard + "create-ecdh@npm:^4.0.0": version: 4.0.4 resolution: "create-ecdh@npm:4.0.4" @@ -15020,6 +17822,23 @@ __metadata: languageName: node linkType: hard +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": ^29.6.3 + chalk: ^4.0.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + jest-config: ^29.7.0 + jest-util: ^29.7.0 + prompts: ^2.0.1 + bin: + create-jest: bin/create-jest.js + checksum: e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + languageName: node + linkType: hard + "create-require@npm:^1.1.0": version: 1.1.1 resolution: "create-require@npm:1.1.1" @@ -15585,6 +18404,13 @@ __metadata: languageName: node linkType: hard +"decamelize@npm:^5": + version: 5.0.1 + resolution: "decamelize@npm:5.0.1" + checksum: 3da71022bc1e85487810fa0833138effb599fa331ca21e179650e93a765d0c4dabeb1ecdd6ad1474fa0bacd2457953c63ea335afb6e53b35f2b4bf779514e2a3 + languageName: node + linkType: hard + "decode-uri-component@npm:^0.2.0": version: 0.2.2 resolution: "decode-uri-component@npm:0.2.2" @@ -15601,7 +18427,7 @@ __metadata: languageName: node linkType: hard -"dedent@npm:1.5.3": +"dedent@npm:1.5.3, dedent@npm:^1.0.0": version: 1.5.3 resolution: "dedent@npm:1.5.3" peerDependencies: @@ -15904,6 +18730,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^7.0.0": + version: 7.0.0 + resolution: "diff@npm:7.0.0" + checksum: 251fd15f85ffdf814cfc35a728d526b8d2ad3de338dcbd011ac6e57c461417090766b28995f8ff733135b5fbc3699c392db1d5e27711ac4e00244768cd1d577b + languageName: node + linkType: hard + "diffie-hellman@npm:^5.0.0": version: 5.0.3 resolution: "diffie-hellman@npm:5.0.3" @@ -16142,6 +18975,15 @@ __metadata: languageName: node linkType: hard +"dreamopt@npm:~0.8.0": + version: 0.8.0 + resolution: "dreamopt@npm:0.8.0" + dependencies: + wordwrap: ">=0.0.2" + checksum: 8e5953c19519c9ec9427eff91b618c3ded7d2b50fcb89c051ca9c4f49e712460103c12dea09eb3feec5a63f21950488a19481798425aaba815b1c5016b3d58b9 + languageName: node + linkType: hard + "dset@npm:^3.1.2": version: 3.1.4 resolution: "dset@npm:3.1.4" @@ -17170,6 +20012,13 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.4": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -17184,7 +20033,7 @@ __metadata: languageName: node linkType: hard -"events@npm:3.3.0, events@npm:^3.1.0, events@npm:^3.2.0": +"events@npm:3.3.0, events@npm:^3.1.0, events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 @@ -17276,16 +20125,16 @@ __metadata: languageName: node linkType: hard -"expect@npm:^29.0.0, expect@npm:^29.5.0": - version: 29.5.0 - resolution: "expect@npm:29.5.0" +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" dependencies: - "@jest/expect-utils": ^29.5.0 - jest-get-type: ^29.4.3 - jest-matcher-utils: ^29.5.0 - jest-message-util: ^29.5.0 - jest-util: ^29.5.0 - checksum: 3c9382967217ad1453e9271e0da3f83c4aeb12272968007b90fc5873340e7fb64bf4852e1522bdf27556623d031ce62f82aaac09e485a15c6d0589d50999422d + "@jest/expect-utils": ^29.7.0 + jest-get-type: ^29.6.3 + jest-matcher-utils: ^29.7.0 + jest-message-util: ^29.7.0 + jest-util: ^29.7.0 + checksum: 2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 languageName: node linkType: hard @@ -17388,6 +20237,13 @@ __metadata: languageName: node linkType: hard +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: d53f6f786875e8b0529f784b59b4b05d4b5c31c651710496440006a398389a579c8dbcd2081311478b5bf77f4b0b21de69109c5a4eabea9d8e8783d1eb864e4c + languageName: node + linkType: hard + "fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" @@ -17907,7 +20763,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^9.0.0, fs-extra@npm:^9.0.1, fs-extra@npm:^9.1.0": +"fs-extra@npm:^9, fs-extra@npm:^9.0.0, fs-extra@npm:^9.0.1, fs-extra@npm:^9.1.0": version: 9.1.0 resolution: "fs-extra@npm:9.1.0" dependencies: @@ -18299,7 +21155,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2, glob@npm:^10.3.0, glob@npm:^10.3.10, glob@npm:^10.4.5": +"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.0, glob@npm:^10.3.10, glob@npm:^10.4.5": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -18315,6 +21171,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^11.0.1": + version: 11.0.2 + resolution: "glob@npm:11.0.2" + dependencies: + foreground-child: ^3.1.0 + jackspeak: ^4.0.1 + minimatch: ^10.0.0 + minipass: ^7.1.2 + package-json-from-dist: ^1.0.0 + path-scurry: ^2.0.0 + bin: + glob: dist/esm/bin.mjs + checksum: 49f91c64ca882d5e3a72397bd45a146ca91fd3ca53dafb5254daf6c0e83fc510d39ea66f136f9ac7ca075cdd11fbe9aaa235b28f743bd477622e472f4fdc0240 + languageName: node + linkType: hard + "glob@npm:^5.0.15": version: 5.0.15 resolution: "glob@npm:5.0.15" @@ -19059,6 +21931,13 @@ __metadata: languageName: node linkType: hard +"heap@npm:>= 0.2.0": + version: 0.2.7 + resolution: "heap@npm:0.2.7" + checksum: 341c5d51ae13dc8346c371a8a69c57c972fcb9a3233090d3dd5ba29d483d6b5b4e75492443cbfeacd46608bb30e6680f646ffb7a6205900221735587d07a79b6 + languageName: node + linkType: hard + "hidefile@npm:^3.0.0": version: 3.0.0 resolution: "hidefile@npm:3.0.0" @@ -19276,13 +22155,13 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^7.0.0": - version: 7.0.0 - resolution: "http-proxy-agent@npm:7.0.0" +"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" dependencies: agent-base: ^7.1.0 debug: ^4.3.4 - checksum: a11574ff39436cee3c7bc67f259444097b09474605846ddd8edf0bf4ad8644be8533db1aa463426e376865047d05dc22755e638632819317c0c2f1b2196657c8 + checksum: 4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 languageName: node linkType: hard @@ -19346,7 +22225,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:^7.0.0, https-proxy-agent@npm:^7.0.1": +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.6": version: 7.0.6 resolution: "https-proxy-agent@npm:7.0.6" dependencies: @@ -19799,13 +22678,6 @@ __metadata: languageName: node linkType: hard -"ip@npm:^1.1.8": - version: 1.1.9 - resolution: "ip@npm:1.1.9" - checksum: 5af58bfe2110c9978acfd77a2ffcdf9d33a6ce1c72f49edbaf16958f7a8eb979b5163e43bb18938caf3aaa55cdacde4e470874c58ca3b4b112ea7a30461a0c27 - languageName: node - linkType: hard - "ipaddr.js@npm:1.9.1": version: 1.9.1 resolution: "ipaddr.js@npm:1.9.1" @@ -19928,6 +22800,17 @@ __metadata: languageName: node linkType: hard +"is-ci@npm:^4.1.0": + version: 4.1.0 + resolution: "is-ci@npm:4.1.0" + dependencies: + ci-info: ^4.1.0 + bin: + is-ci: bin.js + checksum: c58e733d21f8d7e6ba3d70686124004576b369b4a02a37c4783159ed5a668b3a9e4b359efa955e4a675bd021eca7305010c45a1e646bf77cce14b08808eda88d + languageName: node + linkType: hard + "is-core-module@npm:^2.1.0, is-core-module@npm:^2.11.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.2.0, is-core-module@npm:^2.5.0, is-core-module@npm:^2.8.1": version: 2.16.1 resolution: "is-core-module@npm:2.16.1" @@ -20231,7 +23114,7 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^2.0.0": +"is-stream@npm:^2.0.0, is-stream@npm:^2.0.1": version: 2.0.1 resolution: "is-stream@npm:2.0.1" checksum: 7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 @@ -20421,7 +23304,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-instrument@npm:^5.0.4, istanbul-lib-instrument@npm:^5.1.0": +"istanbul-lib-instrument@npm:^5.0.4": version: 5.2.1 resolution: "istanbul-lib-instrument@npm:5.2.1" dependencies: @@ -20434,6 +23317,19 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": ^7.23.9 + "@babel/parser": ^7.23.9 + "@istanbuljs/schema": ^0.1.3 + istanbul-lib-coverage: ^3.2.0 + semver: ^7.5.4 + checksum: a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + "istanbul-lib-report@npm:^3.0.0": version: 3.0.0 resolution: "istanbul-lib-report@npm:3.0.0" @@ -20510,6 +23406,15 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.0.1": + version: 4.1.0 + resolution: "jackspeak@npm:4.1.0" + dependencies: + "@isaacs/cliui": ^8.0.2 + checksum: 08a6a24a366c90b83aef3ad6ec41dcaaa65428ffab8d80bc7172add0fbb8b134a34f415ad288b2a6fbd406526e9a62abdb40ed4f399fbe00cb45c44056d4dce0 + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.8.5 resolution: "jake@npm:10.8.5" @@ -20524,13 +23429,14 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-changed-files@npm:29.5.0" +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" dependencies: execa: ^5.0.0 + jest-util: ^29.7.0 p-limit: ^3.1.0 - checksum: 96334c78507a13c0f11f1360d893ade78fba7fd169825ca4acf7565156ceddd89b952be81c00378fa87ab642d3f44902c34a20f21b561e985e79f6e81fa7e9a8 + checksum: e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b languageName: node linkType: hard @@ -20561,49 +23467,48 @@ __metadata: languageName: node linkType: hard -"jest-circus@npm:^29.0.0, jest-circus@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-circus@npm:29.5.0" +"jest-circus@npm:^29.0.0, jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" dependencies: - "@jest/environment": ^29.5.0 - "@jest/expect": ^29.5.0 - "@jest/test-result": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/environment": ^29.7.0 + "@jest/expect": ^29.7.0 + "@jest/test-result": ^29.7.0 + "@jest/types": ^29.6.3 "@types/node": "*" chalk: ^4.0.0 co: ^4.6.0 - dedent: ^0.7.0 + dedent: ^1.0.0 is-generator-fn: ^2.0.0 - jest-each: ^29.5.0 - jest-matcher-utils: ^29.5.0 - jest-message-util: ^29.5.0 - jest-runtime: ^29.5.0 - jest-snapshot: ^29.5.0 - jest-util: ^29.5.0 + jest-each: ^29.7.0 + jest-matcher-utils: ^29.7.0 + jest-message-util: ^29.7.0 + jest-runtime: ^29.7.0 + jest-snapshot: ^29.7.0 + jest-util: ^29.7.0 p-limit: ^3.1.0 - pretty-format: ^29.5.0 + pretty-format: ^29.7.0 pure-rand: ^6.0.0 slash: ^3.0.0 stack-utils: ^2.0.3 - checksum: 77f77b826941f67e9794e185072ee612cbddf53a1cfbf736de86176b7dc54e54aef151cf31b492adaef221f550924fd60dbaa01c9b939c3a4bfb46d8392c60a8 + checksum: 8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e languageName: node linkType: hard -"jest-cli@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-cli@npm:29.5.0" +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" dependencies: - "@jest/core": ^29.5.0 - "@jest/test-result": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/core": ^29.7.0 + "@jest/test-result": ^29.7.0 + "@jest/types": ^29.6.3 chalk: ^4.0.0 + create-jest: ^29.7.0 exit: ^0.1.2 - graceful-fs: ^4.2.9 import-local: ^3.0.2 - jest-config: ^29.5.0 - jest-util: ^29.5.0 - jest-validate: ^29.5.0 - prompts: ^2.0.1 + jest-config: ^29.7.0 + jest-util: ^29.7.0 + jest-validate: ^29.7.0 yargs: ^17.3.1 peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -20612,34 +23517,34 @@ __metadata: optional: true bin: jest: bin/jest.js - checksum: d63df7e329760bc036d11980883399de86b41a7fa93bbc2e79feef28284b096dec40afc21796504555ccbf32806bfc78cf64a63eac9093bb4f036b282b409863 + checksum: a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a languageName: node linkType: hard -"jest-config@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-config@npm:29.5.0" +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" dependencies: "@babel/core": ^7.11.6 - "@jest/test-sequencer": ^29.5.0 - "@jest/types": ^29.5.0 - babel-jest: ^29.5.0 + "@jest/test-sequencer": ^29.7.0 + "@jest/types": ^29.6.3 + babel-jest: ^29.7.0 chalk: ^4.0.0 ci-info: ^3.2.0 deepmerge: ^4.2.2 glob: ^7.1.3 graceful-fs: ^4.2.9 - jest-circus: ^29.5.0 - jest-environment-node: ^29.5.0 - jest-get-type: ^29.4.3 - jest-regex-util: ^29.4.3 - jest-resolve: ^29.5.0 - jest-runner: ^29.5.0 - jest-util: ^29.5.0 - jest-validate: ^29.5.0 + jest-circus: ^29.7.0 + jest-environment-node: ^29.7.0 + jest-get-type: ^29.6.3 + jest-regex-util: ^29.6.3 + jest-resolve: ^29.7.0 + jest-runner: ^29.7.0 + jest-util: ^29.7.0 + jest-validate: ^29.7.0 micromatch: ^4.0.4 parse-json: ^5.2.0 - pretty-format: ^29.5.0 + pretty-format: ^29.7.0 slash: ^3.0.0 strip-json-comments: ^3.1.1 peerDependencies: @@ -20650,11 +23555,11 @@ __metadata: optional: true ts-node: optional: true - checksum: 01780eb66815e3d31d237aab5d7611ea59e0cdf159cbab2a7c682cb08bde6d053c17a528547440fb1b0294c26ebfd5b54ad35d8c9439f6fae76960ee0bc90197 + checksum: bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 languageName: node linkType: hard -"jest-diff@npm:>=29.4.3 < 30, jest-diff@npm:^29.4.1, jest-diff@npm:^29.5.0": +"jest-diff@npm:>=29.4.3 < 30, jest-diff@npm:^29.4.1, jest-diff@npm:^29.7.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" dependencies: @@ -20678,12 +23583,12 @@ __metadata: languageName: node linkType: hard -"jest-docblock@npm:^29.4.3": - version: 29.4.3 - resolution: "jest-docblock@npm:29.4.3" +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" dependencies: detect-newline: ^3.0.0 - checksum: 25cdea8fe77ff09d958abd347e26dcd8766ca69d9935bc626a89d694c91d33be06d4c088b02e4b3f143f532f726a10dff0bfe1e2387a0972a95addf5d64ed407 + checksum: d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 languageName: node linkType: hard @@ -20700,16 +23605,16 @@ __metadata: languageName: node linkType: hard -"jest-each@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-each@npm:29.5.0" +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" dependencies: - "@jest/types": ^29.5.0 + "@jest/types": ^29.6.3 chalk: ^4.0.0 - jest-get-type: ^29.4.3 - jest-util: ^29.5.0 - pretty-format: ^29.5.0 - checksum: 214f6b5adfc0d6a3e837769018b7a7b69f41e99aac939fe4730bcca23f69e3566ed23706f95a396b20e63e6b9f90990053fc3c1662808036d4f41e4d6d32641d + jest-get-type: ^29.6.3 + jest-util: ^29.7.0 + pretty-format: ^29.7.0 + checksum: f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 languageName: node linkType: hard @@ -20727,17 +23632,17 @@ __metadata: languageName: node linkType: hard -"jest-environment-node@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-environment-node@npm:29.5.0" +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" dependencies: - "@jest/environment": ^29.5.0 - "@jest/fake-timers": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/environment": ^29.7.0 + "@jest/fake-timers": ^29.7.0 + "@jest/types": ^29.6.3 "@types/node": "*" - jest-mock: ^29.5.0 - jest-util: ^29.5.0 - checksum: 2e636a095ff9a9e0aa20fda5b4c06eebed8f3ba2411062bdf724b114eedafd49b880167998af9f77aa8aa68231621aebe3998389d73433e9553ea5735cad1e14 + jest-mock: ^29.7.0 + jest-util: ^29.7.0 + checksum: 61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b languageName: node linkType: hard @@ -20748,7 +23653,7 @@ __metadata: languageName: node linkType: hard -"jest-get-type@npm:^29.4.3, jest-get-type@npm:^29.6.3": +"jest-get-type@npm:^29.6.3": version: 29.6.3 resolution: "jest-get-type@npm:29.6.3" checksum: 552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b @@ -20779,26 +23684,26 @@ __metadata: languageName: node linkType: hard -"jest-haste-map@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-haste-map@npm:29.5.0" +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" dependencies: - "@jest/types": ^29.5.0 + "@jest/types": ^29.6.3 "@types/graceful-fs": ^4.1.3 "@types/node": "*" anymatch: ^3.0.3 fb-watchman: ^2.0.0 fsevents: ^2.3.2 graceful-fs: ^4.2.9 - jest-regex-util: ^29.4.3 - jest-util: ^29.5.0 - jest-worker: ^29.5.0 + jest-regex-util: ^29.6.3 + jest-util: ^29.7.0 + jest-worker: ^29.7.0 micromatch: ^4.0.4 walker: ^1.0.8 dependenciesMeta: fsevents: optional: true - checksum: 162edfa185478db9ebe7dff73f3475ef2c205d94fa2b0fc3b41aba4fc29bab274d4a76ca41ca20ea7d9d6ed2b0d8519e298cfffbf5cad6631412d8961c190612 + checksum: 2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c languageName: node linkType: hard @@ -20814,13 +23719,13 @@ __metadata: languageName: node linkType: hard -"jest-leak-detector@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-leak-detector@npm:29.5.0" +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" dependencies: - jest-get-type: ^29.4.3 - pretty-format: ^29.5.0 - checksum: d7db5d4a7cb676fc151f533d6887f3d6bbb4e35346346cbed0b5583c296b13af2d3c8434b30f62b0eb9c711718c7f4bd48496c47af3a20320ee162e33d64aaf2 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: 71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 languageName: node linkType: hard @@ -20836,15 +23741,15 @@ __metadata: languageName: node linkType: hard -"jest-matcher-utils@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-matcher-utils@npm:29.5.0" +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" dependencies: chalk: ^4.0.0 - jest-diff: ^29.5.0 - jest-get-type: ^29.4.3 - pretty-format: ^29.5.0 - checksum: 0a3ae95ef5c5c4ac2b2c503c2f57e173fa82725722e1fadcd902fd801afe17d9d36e9366820959465f553627bf1e481a0e4a540125f3b4371eec674b3557f7f3 + jest-diff: ^29.7.0 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: 0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e languageName: node linkType: hard @@ -20882,20 +23787,20 @@ __metadata: languageName: node linkType: hard -"jest-message-util@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-message-util@npm:29.5.0" +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" dependencies: "@babel/code-frame": ^7.12.13 - "@jest/types": ^29.5.0 + "@jest/types": ^29.6.3 "@types/stack-utils": ^2.0.0 chalk: ^4.0.0 graceful-fs: ^4.2.9 micromatch: ^4.0.4 - pretty-format: ^29.5.0 + pretty-format: ^29.7.0 slash: ^3.0.0 stack-utils: ^2.0.3 - checksum: 706e89cacc89c090af584f4687c4e7f0616706481e468ec7c88270e07ae7458a829e477b7b3dff56b75d801f799d65eb2c28d6453c25dd02bea0fd98f0809dbb + checksum: 850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 languageName: node linkType: hard @@ -20919,14 +23824,14 @@ __metadata: languageName: node linkType: hard -"jest-mock@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-mock@npm:29.5.0" +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" dependencies: - "@jest/types": ^29.5.0 + "@jest/types": ^29.6.3 "@types/node": "*" - jest-util: ^29.5.0 - checksum: c5b71d397d6acd44d99cd48dad8ca76334fc5a27e120da72d264d7527a9efc7c6fc431d79de64d0b73aa0ab26a2d0712498e323d42b9e03bee05e983b0d2035c + jest-util: ^29.7.0 + checksum: 7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac languageName: node linkType: hard @@ -20949,20 +23854,20 @@ __metadata: languageName: node linkType: hard -"jest-regex-util@npm:^29.4.3": - version: 29.4.3 - resolution: "jest-regex-util@npm:29.4.3" - checksum: a7a4508bda47c5177e7337fb6fb22e9adab414ba141f224c9992c86973da1ccf5c69040e63636090ad26ef3a123d28bec950fa99496c157444b4f847e5e5a670 +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b languageName: node linkType: hard -"jest-resolve-dependencies@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-resolve-dependencies@npm:29.5.0" +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" dependencies: - jest-regex-util: ^29.4.3 - jest-snapshot: ^29.5.0 - checksum: fbe513b7d905c4a70be17fd1cb4bd83da1e82cceb47ed7ceababbe11c75f1d0c18eadeb3f4ebb6997ba979f35fa18dfd02e1d57eb556675e47b35675fde0aac7 + jest-regex-util: ^29.6.3 + jest-snapshot: ^29.7.0 + checksum: b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d languageName: node linkType: hard @@ -21000,49 +23905,49 @@ __metadata: languageName: node linkType: hard -"jest-resolve@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-resolve@npm:29.5.0" +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" dependencies: chalk: ^4.0.0 graceful-fs: ^4.2.9 - jest-haste-map: ^29.5.0 + jest-haste-map: ^29.7.0 jest-pnp-resolver: ^1.2.2 - jest-util: ^29.5.0 - jest-validate: ^29.5.0 + jest-util: ^29.7.0 + jest-validate: ^29.7.0 resolve: ^1.20.0 resolve.exports: ^2.0.0 slash: ^3.0.0 - checksum: e7ea3b1cf865a7e63ad297d0f43a093dde145f9ca72dc8e75b6c7eb3af60fe78e4f7d024fd92fa280419a4ca038d42a9268d4d5d512958d11347e680daca1f12 + checksum: 59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 languageName: node linkType: hard -"jest-runner@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-runner@npm:29.5.0" +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" dependencies: - "@jest/console": ^29.5.0 - "@jest/environment": ^29.5.0 - "@jest/test-result": ^29.5.0 - "@jest/transform": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/console": ^29.7.0 + "@jest/environment": ^29.7.0 + "@jest/test-result": ^29.7.0 + "@jest/transform": ^29.7.0 + "@jest/types": ^29.6.3 "@types/node": "*" chalk: ^4.0.0 emittery: ^0.13.1 graceful-fs: ^4.2.9 - jest-docblock: ^29.4.3 - jest-environment-node: ^29.5.0 - jest-haste-map: ^29.5.0 - jest-leak-detector: ^29.5.0 - jest-message-util: ^29.5.0 - jest-resolve: ^29.5.0 - jest-runtime: ^29.5.0 - jest-util: ^29.5.0 - jest-watcher: ^29.5.0 - jest-worker: ^29.5.0 + jest-docblock: ^29.7.0 + jest-environment-node: ^29.7.0 + jest-haste-map: ^29.7.0 + jest-leak-detector: ^29.7.0 + jest-message-util: ^29.7.0 + jest-resolve: ^29.7.0 + jest-runtime: ^29.7.0 + jest-util: ^29.7.0 + jest-watcher: ^29.7.0 + jest-worker: ^29.7.0 p-limit: ^3.1.0 source-map-support: 0.5.13 - checksum: 96f47976b9bcc0554455c200d02ebc1547b9a7749b05353c0d55aff535509032c0c12ea25ccc294350f62c14665dbc1e00b15e0d1c52207edfb807e4fec4a36a + checksum: 2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 languageName: node linkType: hard @@ -21076,33 +23981,33 @@ __metadata: languageName: node linkType: hard -"jest-runtime@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-runtime@npm:29.5.0" - dependencies: - "@jest/environment": ^29.5.0 - "@jest/fake-timers": ^29.5.0 - "@jest/globals": ^29.5.0 - "@jest/source-map": ^29.4.3 - "@jest/test-result": ^29.5.0 - "@jest/transform": ^29.5.0 - "@jest/types": ^29.5.0 +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": ^29.7.0 + "@jest/fake-timers": ^29.7.0 + "@jest/globals": ^29.7.0 + "@jest/source-map": ^29.6.3 + "@jest/test-result": ^29.7.0 + "@jest/transform": ^29.7.0 + "@jest/types": ^29.6.3 "@types/node": "*" chalk: ^4.0.0 cjs-module-lexer: ^1.0.0 collect-v8-coverage: ^1.0.0 glob: ^7.1.3 graceful-fs: ^4.2.9 - jest-haste-map: ^29.5.0 - jest-message-util: ^29.5.0 - jest-mock: ^29.5.0 - jest-regex-util: ^29.4.3 - jest-resolve: ^29.5.0 - jest-snapshot: ^29.5.0 - jest-util: ^29.5.0 + jest-haste-map: ^29.7.0 + jest-message-util: ^29.7.0 + jest-mock: ^29.7.0 + jest-regex-util: ^29.6.3 + jest-resolve: ^29.7.0 + jest-snapshot: ^29.7.0 + jest-util: ^29.7.0 slash: ^3.0.0 strip-bom: ^4.0.0 - checksum: 9b5c0a97e1f24945059695e056188041730a3f1dc5924153e323eb7429244e10e7cc877b13d057869d6621c460deae11b77a2a2e9ab56e22b56864a3e44c4448 + checksum: 7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 languageName: node linkType: hard @@ -21146,34 +24051,31 @@ __metadata: languageName: node linkType: hard -"jest-snapshot@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-snapshot@npm:29.5.0" +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" dependencies: "@babel/core": ^7.11.6 "@babel/generator": ^7.7.2 "@babel/plugin-syntax-jsx": ^7.7.2 "@babel/plugin-syntax-typescript": ^7.7.2 - "@babel/traverse": ^7.7.2 "@babel/types": ^7.3.3 - "@jest/expect-utils": ^29.5.0 - "@jest/transform": ^29.5.0 - "@jest/types": ^29.5.0 - "@types/babel__traverse": ^7.0.6 - "@types/prettier": ^2.1.5 + "@jest/expect-utils": ^29.7.0 + "@jest/transform": ^29.7.0 + "@jest/types": ^29.6.3 babel-preset-current-node-syntax: ^1.0.0 chalk: ^4.0.0 - expect: ^29.5.0 + expect: ^29.7.0 graceful-fs: ^4.2.9 - jest-diff: ^29.5.0 - jest-get-type: ^29.4.3 - jest-matcher-utils: ^29.5.0 - jest-message-util: ^29.5.0 - jest-util: ^29.5.0 + jest-diff: ^29.7.0 + jest-get-type: ^29.6.3 + jest-matcher-utils: ^29.7.0 + jest-message-util: ^29.7.0 + jest-util: ^29.7.0 natural-compare: ^1.4.0 - pretty-format: ^29.5.0 - semver: ^7.3.5 - checksum: db9957d9c8607d75bb08302605331b5d90fa738fafeed820ab8ebcb2c90f9e62fb4fec0b4c826c04a37557cbb7a9ed26a10b0c74d46ffedce2d6ae8a9c891b00 + pretty-format: ^29.7.0 + semver: ^7.5.3 + checksum: 6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 languageName: node linkType: hard @@ -21205,7 +24107,7 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^29.0.0, jest-util@npm:^29.5.0, jest-util@npm:^29.7.0": +"jest-util@npm:^29.0.0, jest-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-util@npm:29.7.0" dependencies: @@ -21233,17 +24135,17 @@ __metadata: languageName: node linkType: hard -"jest-validate@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-validate@npm:29.5.0" +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" dependencies: - "@jest/types": ^29.5.0 + "@jest/types": ^29.6.3 camelcase: ^6.2.0 chalk: ^4.0.0 - jest-get-type: ^29.4.3 + jest-get-type: ^29.6.3 leven: ^3.1.0 - pretty-format: ^29.5.0 - checksum: 7aabde27a9b736df65902a1bb4ec63af518d4c95e12a910e7658140784168f08c662d5babe67dfa70d843dd2096bc08aa7090fef83c7a9d6bb0893793c3a599a + pretty-format: ^29.7.0 + checksum: a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 languageName: node linkType: hard @@ -21279,19 +24181,19 @@ __metadata: languageName: node linkType: hard -"jest-watcher@npm:^29.5.0": - version: 29.5.0 - resolution: "jest-watcher@npm:29.5.0" +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" dependencies: - "@jest/test-result": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/test-result": ^29.7.0 + "@jest/types": ^29.6.3 "@types/node": "*" ansi-escapes: ^4.2.1 chalk: ^4.0.0 emittery: ^0.13.1 - jest-util: ^29.5.0 + jest-util: ^29.7.0 string-length: ^4.0.1 - checksum: 6a2e71e720183303913fc34fc24a3f87fca7fcfa638bc6c9109a4808b36251a1cb7fe98b956eb0d9c9ead1ad47c3dc3745289ee89e62c6c615168e92282069ca + checksum: ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 languageName: node linkType: hard @@ -21306,7 +24208,7 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^29.5.0, jest-worker@npm:^29.7.0": +"jest-worker@npm:^29.7.0": version: 29.7.0 resolution: "jest-worker@npm:29.7.0" dependencies: @@ -21318,14 +24220,14 @@ __metadata: languageName: node linkType: hard -"jest@npm:^29.0.0, jest@npm:^29.5.0": - version: 29.5.0 - resolution: "jest@npm:29.5.0" +"jest@npm:^29.0.0, jest@npm:^29.5.0, jest@npm:^29.7.0": + version: 29.7.0 + resolution: "jest@npm:29.7.0" dependencies: - "@jest/core": ^29.5.0 - "@jest/types": ^29.5.0 + "@jest/core": ^29.7.0 + "@jest/types": ^29.6.3 import-local: ^3.0.2 - jest-cli: ^29.5.0 + jest-cli: ^29.7.0 peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: @@ -21333,7 +24235,7 @@ __metadata: optional: true bin: jest: bin/jest.js - checksum: 32e29cfa2373530ed323ea65dfb4fd5172026349be48ebb7a2dc5660adadd1c68f6b0fe2b67cc3ee723cc34e2d4552a852730ac787251b406cf58e37a90f6dac + checksum: f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b languageName: node linkType: hard @@ -21473,6 +24375,19 @@ __metadata: languageName: node linkType: hard +"json-diff@npm:^1.0.6": + version: 1.0.6 + resolution: "json-diff@npm:1.0.6" + dependencies: + "@ewoudenberg/difflib": 0.1.0 + colors: ^1.4.0 + dreamopt: ~0.8.0 + bin: + json-diff: bin/json-diff.js + checksum: 21d0477a703ceaf1cbb8bcbb6ff38c16d16285a1ce6e358a47732632ece0c1d29d169cc5c099b64e65d8820784a04a53bc848699446ff3c546897426b0d3e443 + languageName: node + linkType: hard + "json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" @@ -21708,7 +24623,7 @@ __metadata: languageName: node linkType: hard -"kleur@npm:^4.0.1": +"kleur@npm:^4.0.1, kleur@npm:^4.1.5": version: 4.1.5 resolution: "kleur@npm:4.1.5" checksum: e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a @@ -22382,6 +25297,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.1.0 + resolution: "lru-cache@npm:11.1.0" + checksum: 85c312f7113f65fae6a62de7985348649937eb34fb3d212811acbf6704dc322a421788aca253b62838f1f07049a84cc513d88f494e373d3756514ad263670a64 + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -22735,6 +25657,15 @@ __metadata: languageName: node linkType: hard +"mime@npm:^2": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: a7f2589900d9c16e3bdf7672d16a6274df903da958c1643c9c45771f0478f3846dcb1097f31eb9178452570271361e2149310931ec705c037210fc69639c8e6c + languageName: node + linkType: hard + "mimic-fn@npm:^1.0.0": version: 1.2.0 resolution: "mimic-fn@npm:1.2.0" @@ -22822,6 +25753,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.0, minimatch@npm:^10.0.1": + version: 10.0.1 + resolution: "minimatch@npm:10.0.1" + dependencies: + brace-expansion: ^2.0.1 + checksum: e6c29a81fe83e1877ad51348306be2e8aeca18c88fdee7a99df44322314279e15799e41d7cb274e4e8bb0b451a3bc622d6182e157dfa1717d6cda75e9cd8cd5d + languageName: node + linkType: hard + "minimatch@npm:^5.0.1, minimatch@npm:^5.1.0, minimatch@npm:^5.1.6": version: 5.1.6 resolution: "minimatch@npm:5.1.6" @@ -23168,7 +26108,7 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:0.0.8": +"mute-stream@npm:0.0.8, mute-stream@npm:~0.0.4": version: 0.0.8 resolution: "mute-stream@npm:0.0.8" checksum: 18d06d92e5d6d45e2b63c0e1b8f25376af71748ac36f53c059baa8b76ffac31c5ab225480494e7d35d30215ecdb18fed26ec23cafcd2f7733f2f14406bcd19e2 @@ -24216,7 +27156,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": +"p-limit@npm:^3, p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -24343,30 +27283,29 @@ __metadata: languageName: node linkType: hard -"pac-proxy-agent@npm:^7.0.0": - version: 7.0.0 - resolution: "pac-proxy-agent@npm:7.0.0" +"pac-proxy-agent@npm:^7.1.0": + version: 7.2.0 + resolution: "pac-proxy-agent@npm:7.2.0" dependencies: "@tootallnate/quickjs-emscripten": ^0.23.0 - agent-base: ^7.0.2 + agent-base: ^7.1.2 debug: ^4.3.4 get-uri: ^6.0.1 http-proxy-agent: ^7.0.0 - https-proxy-agent: ^7.0.0 - pac-resolver: ^7.0.0 - socks-proxy-agent: ^8.0.1 - checksum: de7f26fbf970a7a8050df2331ebd3fef42a84a63c7c907c7f2601863737fc8dc1b7de616a5c9401b462966bfb1a94ca49efb75ec7cae0cbdc6bcb3d77aa9e8a6 + https-proxy-agent: ^7.0.6 + pac-resolver: ^7.0.1 + socks-proxy-agent: ^8.0.5 + checksum: 0265c17c9401c2ea735697931a6553a0c6d8b20c4d7d4e3b3a0506080ba69a8d5ad656e2a6be875411212e2b6ed7a4d9526dd3997e08581fdfb1cbcad454c296 languageName: node linkType: hard -"pac-resolver@npm:^7.0.0": - version: 7.0.0 - resolution: "pac-resolver@npm:7.0.0" +"pac-resolver@npm:^7.0.1": + version: 7.0.1 + resolution: "pac-resolver@npm:7.0.1" dependencies: degenerator: ^5.0.0 - ip: ^1.1.8 netmask: ^2.0.2 - checksum: a5ac1bf1f33f667a1c85fd61744672d9364534a1bb68a676ef920091b735ed8a10fc2b57385909e34822a2147b10a898dd79139b07dae0dbd568561d5c40a81b + checksum: 5f3edd1dd10fded31e7d1f95776442c3ee51aa098c28b74ede4927d9677ebe7cebb2636750c24e945f5b84445e41ae39093d3a1014a994e5ceb9f0b1b88ebff5 languageName: node linkType: hard @@ -24671,6 +27610,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: ^11.0.0 + minipass: ^7.1.2 + checksum: 3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c + languageName: node + linkType: hard + "path-to-regexp@npm:0.1.12": version: 0.1.12 resolution: "path-to-regexp@npm:0.1.12" @@ -25877,7 +28826,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.0.0, pretty-format@npm:^29.5.0, pretty-format@npm:^29.7.0": +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" dependencies: @@ -26004,6 +28953,15 @@ __metadata: languageName: node linkType: hard +"promptly@npm:^3.2.0": + version: 3.2.0 + resolution: "promptly@npm:3.2.0" + dependencies: + read: ^1.0.4 + checksum: 2d52204e9bbabd654a6ec61e3c64a6426df6eeeec0ade94d136de5c77af51d3d6a94a7408f2198b79396142b9afe10e34fdb8065867b4bc503b46dec6cf9eedf + languageName: node + linkType: hard + "prompts-ncu@npm:^2.5.1": version: 2.5.1 resolution: "prompts-ncu@npm:2.5.1" @@ -26082,19 +29040,19 @@ __metadata: languageName: node linkType: hard -"proxy-agent@npm:^6.3.0": - version: 6.3.0 - resolution: "proxy-agent@npm:6.3.0" +"proxy-agent@npm:^6.3.0, proxy-agent@npm:^6.5.0": + version: 6.5.0 + resolution: "proxy-agent@npm:6.5.0" dependencies: - agent-base: ^7.0.2 + agent-base: ^7.1.2 debug: ^4.3.4 - http-proxy-agent: ^7.0.0 - https-proxy-agent: ^7.0.0 + http-proxy-agent: ^7.0.1 + https-proxy-agent: ^7.0.6 lru-cache: ^7.14.1 - pac-proxy-agent: ^7.0.0 + pac-proxy-agent: ^7.1.0 proxy-from-env: ^1.1.0 - socks-proxy-agent: ^8.0.1 - checksum: 40a0df2c9af5da8e6fcb95268f3e93181d8dd5c5ee9493517793fe75f847641f44a962d25a49d7208ec3b68cf1998fcd0d976bae773796e2023c71cddd76b642 + socks-proxy-agent: ^8.0.5 + checksum: 7fd4e6f36bf17098a686d4aee3b8394abfc0b0537c2174ce96b0a4223198b9fafb16576c90108a3fcfc2af0168bd7747152bfa1f58e8fee91d3780e79aab7fd8 languageName: node linkType: hard @@ -26564,6 +29522,15 @@ __metadata: languageName: node linkType: hard +"read@npm:^1.0.4": + version: 1.0.7 + resolution: "read@npm:1.0.7" + dependencies: + mute-stream: ~0.0.4 + checksum: 443533f05d5bb11b36ef1c6d625aae4e2ced8967e93cf546f35aa77b4eb6bd157f4256619e446bae43467f8f6619c7bc5c76983348dffaf36afedf4224f46216 + languageName: node + linkType: hard + "read@npm:^3.0.1": version: 3.0.1 resolution: "read@npm:3.0.1" @@ -26599,6 +29566,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.7.0 + resolution: "readable-stream@npm:4.7.0" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + string_decoder: ^1.3.0 + checksum: fd86d068da21cfdb10f7a4479f2e47d9c0a9b0c862fc0c840a7e5360201580a55ac399c764b12a4f6fa291f8cee74d9c4b7562e0d53b3c4b2769f2c98155d957 + languageName: node + linkType: hard + "readable-stream@npm:~1.0.31": version: 1.0.34 resolution: "readable-stream@npm:1.0.34" @@ -26611,12 +29591,12 @@ __metadata: languageName: node linkType: hard -"readdir-glob@npm:^1.0.0": - version: 1.1.1 - resolution: "readdir-glob@npm:1.1.1" +"readdir-glob@npm:^1.0.0, readdir-glob@npm:^1.1.2": + version: 1.1.3 + resolution: "readdir-glob@npm:1.1.3" dependencies: - minimatch: ^3.0.4 - checksum: 90936ece396c1e85534acc1f41a4904a5a8c063cdd405a1f1781b72207f100c79059435d6b98215336a07df6f577e50bc3a1568a0544b1aefbb4aef8d5c5acfb + minimatch: ^5.1.0 + checksum: a37e0716726650845d761f1041387acd93aa91b28dd5381950733f994b6c349ddc1e21e266ec7cc1f9b92e205a7a972232f9b89d5424d07361c2c3753d5dbace languageName: node linkType: hard @@ -27908,7 +30888,7 @@ __metadata: languageName: node linkType: hard -"socks-proxy-agent@npm:^8.0.1, socks-proxy-agent@npm:^8.0.3": +"socks-proxy-agent@npm:^8.0.3, socks-proxy-agent@npm:^8.0.5": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" dependencies: @@ -28127,7 +31107,7 @@ __metadata: languageName: node linkType: hard -"split2@npm:^4.1.0": +"split2@npm:^4.1.0, split2@npm:^4.2.0": version: 4.2.0 resolution: "split2@npm:4.2.0" checksum: b292beb8ce9215f8c642bb68be6249c5a4c7f332fc8ecadae7be5cbdf1ea95addc95f0459ef2e7ad9d45fd1064698a097e4eb211c83e772b49bc0ee423e91534 @@ -28305,6 +31285,20 @@ __metadata: languageName: node linkType: hard +"streamx@npm:^2.15.0": + version: 2.22.0 + resolution: "streamx@npm:2.22.0" + dependencies: + bare-events: ^2.2.0 + fast-fifo: ^1.3.2 + text-decoder: ^1.1.0 + dependenciesMeta: + bare-events: + optional: true + checksum: f5017998a5b6360ba652599d20ef308c8c8ab0e26c8e5f624f0706f0ea12624e94fdf1ec18318124498529a1b106a1ab1c94a1b1e1ad6c2eec7cb9c8ac1b9198 + languageName: node + linkType: hard + "string-argv@npm:~0.3.1": version: 0.3.2 resolution: "string-argv@npm:0.3.2" @@ -28339,7 +31333,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -28421,7 +31415,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -28457,7 +31451,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" dependencies: @@ -28708,7 +31702,7 @@ __metadata: languageName: node linkType: hard -"table@npm:^6.0.9, table@npm:^6.8.1, table@npm:^6.9.0": +"table@npm:^6, table@npm:^6.0.9, table@npm:^6.8.1, table@npm:^6.9.0": version: 6.9.0 resolution: "table@npm:6.9.0" dependencies: @@ -28794,6 +31788,17 @@ __metadata: languageName: node linkType: hard +"tar-stream@npm:^3.0.0": + version: 3.1.7 + resolution: "tar-stream@npm:3.1.7" + dependencies: + b4a: ^1.6.4 + fast-fifo: ^1.2.0 + streamx: ^2.15.0 + checksum: a09199d21f8714bd729993ac49b6c8efcb808b544b89f23378ad6ffff6d1cb540878614ba9d4cfec11a64ef39e1a6f009a5398371491eb1fda606ffc7f70f718 + languageName: node + linkType: hard + "tar@npm:^6.2.1": version: 6.2.1 resolution: "tar@npm:6.2.1" @@ -28908,6 +31913,15 @@ __metadata: languageName: node linkType: hard +"text-decoder@npm:^1.1.0": + version: 1.2.3 + resolution: "text-decoder@npm:1.2.3" + dependencies: + b4a: ^1.6.4 + checksum: 569d776b9250158681c83656ef2c3e0a5d5c660c27ca69f87eedef921749a4fbf02095e5f9a0f862a25cf35258379b06e31dee9c125c9f72e273b7ca1a6d1977 + languageName: node + linkType: hard + "text-extensions@npm:^1.0.0": version: 1.9.0 resolution: "text-extensions@npm:1.9.0" @@ -29565,13 +32579,13 @@ __metadata: languageName: node linkType: hard -"typescript@npm:>=3 < 6, typescript@npm:^4.6.4 || ^5.0.0": - version: 5.8.2 - resolution: "typescript@npm:5.8.2" +"typescript@npm:>=3 < 6, typescript@npm:^4.6.4 || ^5.0.0, typescript@npm:^5.4.5": + version: 5.8.3 + resolution: "typescript@npm:5.8.3" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 5c4f6fbf1c6389b6928fe7b8fcd5dc73bb2d58cd4e3883f1d774ed5bd83b151cbac6b7ecf11723de56d4676daeba8713894b1e9af56174f2f9780ae7848ec3c6 + checksum: 5f8bb01196e542e64d44db3d16ee0e4063ce4f3e3966df6005f2588e86d91c03e1fb131c2581baf0fb65ee79669eea6e161cd448178986587e9f6844446dbb48 languageName: node linkType: hard @@ -29605,13 +32619,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@>=3 < 6#~builtin, typescript@patch:typescript@^4.6.4 || ^5.0.0#~builtin": - version: 5.8.2 - resolution: "typescript@patch:typescript@npm%3A5.8.2#~builtin::version=5.8.2&hash=85af82" +"typescript@patch:typescript@>=3 < 6#~builtin, typescript@patch:typescript@^4.6.4 || ^5.0.0#~builtin, typescript@patch:typescript@^5.4.5#~builtin": + version: 5.8.3 + resolution: "typescript@patch:typescript@npm%3A5.8.3#~builtin::version=5.8.3&hash=85af82" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 8a6cd29dfb59bd5a978407b93ae0edb530ee9376a5b95a42ad057a6f80ffb0c410489ccd6fe48d1d0dfad6e8adf5d62d3874bbd251f488ae30e11a1ce6dabd28 + checksum: 92ea03509e06598948559ddcdd8a4ae5a7ab475766d5589f1b796f5731b3d631a4c7ddfb86a3bd44d58d10102b132cd4b4994dda9b63e6273c66d77d6a271dbd languageName: node linkType: hard @@ -29693,6 +32707,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: 078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344 + languageName: node + linkType: hard + "unfetch@npm:^4.2.0": version: 4.2.0 resolution: "unfetch@npm:4.2.0" @@ -29844,7 +32865,7 @@ __metadata: languageName: node linkType: hard -"unzipper@npm:^0.12.3": +"unzipper@npm:^0.12.1, unzipper@npm:^0.12.3": version: 0.12.3 resolution: "unzipper@npm:0.12.3" dependencies: @@ -30076,6 +33097,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.1.0": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + languageName: node + linkType: hard + "uuid@npm:^8.0.0, uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -30655,7 +33685,7 @@ __metadata: languageName: node linkType: hard -"wordwrap@npm:^1.0.0": +"wordwrap@npm:>=0.0.2, wordwrap@npm:^1.0.0": version: 1.0.0 resolution: "wordwrap@npm:1.0.0" checksum: 7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 @@ -30874,7 +33904,7 @@ __metadata: languageName: node linkType: hard -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0" dependencies: @@ -31134,7 +34164,7 @@ __metadata: languageName: node linkType: hard -"yaml@npm:1.10.2, yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": +"yaml@npm:1.10.2, yaml@npm:^1, yaml@npm:^1.10.0, yaml@npm:^1.10.2, yaml@npm:^1.7.2": version: 1.10.2 resolution: "yaml@npm:1.10.2" checksum: 5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f @@ -31201,7 +34231,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^15.1.0, yargs@npm:^15.3.1": +"yargs@npm:^15, yargs@npm:^15.1.0, yargs@npm:^15.3.1": version: 15.4.1 resolution: "yargs@npm:15.4.1" dependencies: @@ -31344,3 +34374,21 @@ __metadata: checksum: ed9eb9387953576c43bdf7678705e8b0ff4e9149cf92b39fa845ddd5413b08daf68655b1ee8311e2dd7c88ddeb95908a785e8e48473016b2595870b0adf588d4 languageName: node linkType: hard + +"zip-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "zip-stream@npm:6.0.1" + dependencies: + archiver-utils: ^5.0.0 + compress-commons: ^6.0.2 + readable-stream: ^4.0.0 + checksum: 50f2fb30327fb9d09879abf7ae2493705313adf403e794b030151aaae00009162419d60d0519e807673ec04d442e140c8879ca14314df0a0192de3b233e8f28b + languageName: node + linkType: hard + +"zod@npm:^3.22.2": + version: 3.24.3 + resolution: "zod@npm:3.24.3" + checksum: ab0369810968d0329a1a141e9418e01e5c9c2a4905cbb7cb7f5a131d6e9487596e1400e21eeff24c4a8ee28dacfa5bd6103893765c055b7a98c2006a5a4fc68d + languageName: node + linkType: hard