diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 717b645bce..92a0f4ac68 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,2 +1 @@
-# Amplify Data and Amplify CLI teams are modeled as codeowners during this migration process. We'll remove CLI once complete.
-* @aws-amplify/amplify-data @aws-amplify/amplify-cli
+* @aws-amplify/amplify-data
diff --git a/packages/amplify-category-api/.npmignore b/packages/amplify-category-api/.npmignore
new file mode 100644
index 0000000000..a1f3bf2a32
--- /dev/null
+++ b/packages/amplify-category-api/.npmignore
@@ -0,0 +1,7 @@
+**/__mocks__/**
+**/__tests__/**
+./src
+tsconfig.json
+tsconfig.tsbuildinfo
+!resources/awscloudformation/overrides-resource/AppSync/tsconfig.json
+!resources/awscloudformation/overrides-resource/APIGW/tsconfig.json
diff --git a/packages/amplify-category-api/CHANGELOG.md b/packages/amplify-category-api/CHANGELOG.md
new file mode 100644
index 0000000000..cf4cebe478
--- /dev/null
+++ b/packages/amplify-category-api/CHANGELOG.md
@@ -0,0 +1,2261 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
+# [2.2.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.1.2...@aws-amplify/amplify-category-api@2.2.0) (2022-06-03)
+
+
+### Features
+
+* allow 3rd-party plugins to CDK override ([#9601](https://github.com/aws-amplify/amplify-cli/issues/9601)) ([60498c5](https://github.com/aws-amplify/amplify-cli/commit/60498c5fb54dcf15e2fb5b87528540fdcffc0cd1))
+
+
+
+
+
+## [2.1.2](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.1.1...@aws-amplify/amplify-category-api@2.1.2) (2022-05-27)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [2.1.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.1.0...@aws-amplify/amplify-category-api@2.1.1) (2022-05-24)
+
+
+### Bug Fixes
+
+* opensearch instance type check during push ([#10389](https://github.com/aws-amplify/amplify-cli/issues/10389)) ([bbb6586](https://github.com/aws-amplify/amplify-cli/commit/bbb658617ba5ea5186bdc68b81961342505b4430))
+
+
+
+
+
+# [2.1.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.0.4...@aws-amplify/amplify-category-api@2.1.0) (2022-05-10)
+
+
+### Bug Fixes
+
+* root path handling for REST APIs ([#9842](https://github.com/aws-amplify/amplify-cli/issues/9842)) ([08fb69f](https://github.com/aws-amplify/amplify-cli/commit/08fb69f6237a8e0a98ffdf6d73cb0b030ace583e))
+
+
+### Features
+
+* usage flow logging for interactive and non-interactive cli commands ([#10288](https://github.com/aws-amplify/amplify-cli/issues/10288)) ([da391b1](https://github.com/aws-amplify/amplify-cli/commit/da391b146612d8914f72e558e5503d075456c820)), closes [#10293](https://github.com/aws-amplify/amplify-cli/issues/10293)
+
+
+
+
+
+## [2.0.4](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.0.3...@aws-amplify/amplify-category-api@2.0.4) (2022-04-29)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [2.0.3](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.0.2...@aws-amplify/amplify-category-api@2.0.3) (2022-04-27)
+
+
+### Bug Fixes
+
+* remove prompt from rds headless pull ([#10239](https://github.com/aws-amplify/amplify-cli/issues/10239)) ([3e276d1](https://github.com/aws-amplify/amplify-cli/commit/3e276d1cc154995281ef9baeb7775b3f71d5948b))
+
+
+
+
+
+## [2.0.2](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.0.1...@aws-amplify/amplify-category-api@2.0.2) (2022-04-18)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [2.0.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@2.0.0...@aws-amplify/amplify-category-api@2.0.1) (2022-04-11)
+
+
+### Bug Fixes
+
+* api sets auth dependency correctly in meta files ([#10072](https://github.com/aws-amplify/amplify-cli/issues/10072)) ([42edc57](https://github.com/aws-amplify/amplify-cli/commit/42edc572ab21064da16ede94d16884fda5a9a54f))
+
+
+
+
+
+# [2.0.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.17...@aws-amplify/amplify-category-api@2.0.0) (2022-04-07)
+
+
+### Bug Fixes
+
+* **container-api:** upgrade flask version for the python api ([#10077](https://github.com/aws-amplify/amplify-cli/issues/10077)) ([64ba2f1](https://github.com/aws-amplify/amplify-cli/commit/64ba2f1b0231ba3d237bfdbd2d36db303bedb9d3))
+* use prompter and handle deletions ([#10122](https://github.com/aws-amplify/amplify-cli/issues/10122)) ([5c0e290](https://github.com/aws-amplify/amplify-cli/commit/5c0e2904e5ac65824642281e732aae4f02904fd0))
+
+
+### BREAKING CHANGES
+
+* package name update requires version bump in order to keep in sync with lerna.
+
+
+
+
+
+## [1.2.17](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.16...@aws-amplify/amplify-category-api@1.2.17) (2022-03-23)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.16](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.15...@aws-amplify/amplify-category-api@1.2.16) (2022-03-17)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.15](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.14...@aws-amplify/amplify-category-api@1.2.15) (2022-03-14)
+
+
+### Bug Fixes
+
+* allow api updates without migration ([#9864](https://github.com/aws-amplify/amplify-cli/issues/9864)) ([389f551](https://github.com/aws-amplify/amplify-cli/commit/389f551ea800afc9563b6e546f3ea0073bc13c68))
+* **amplify-category-api:** remove stack trace from printout for api ([#9877](https://github.com/aws-amplify/amplify-cli/issues/9877)) ([55be9c3](https://github.com/aws-amplify/amplify-cli/commit/55be9c3bac002fb1cc2cc4c1eae3df1b6972c4cb))
+* split policies for both legacy and migrated REST APIs ([#9572](https://github.com/aws-amplify/amplify-cli/issues/9572)) ([436d53f](https://github.com/aws-amplify/amplify-cli/commit/436d53f348954dab02364d1bed528c3b4121ede3))
+
+
+
+
+
+## [1.2.14](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.13...@aws-amplify/amplify-category-api@1.2.14) (2022-03-07)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** surface override build errors for REST APIs ([#9804](https://github.com/aws-amplify/amplify-cli/issues/9804)) ([b22b67d](https://github.com/aws-amplify/amplify-cli/commit/b22b67de1cdcafbb9475374e7fdf989431b5ce9c))
+
+
+
+
+
+## [1.2.13](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.12...@aws-amplify/amplify-category-api@1.2.13) (2022-02-25)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.12](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.11...@aws-amplify/amplify-category-api@1.2.12) (2022-02-18)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.11](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.10...@aws-amplify/amplify-category-api@1.2.11) (2022-02-15)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.10](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.6...@aws-amplify/amplify-category-api@1.2.10) (2022-02-10)
+
+
+
+## 7.6.19 (2022-02-08)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.6](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.5...@aws-amplify/amplify-category-api@1.2.6) (2022-02-03)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.5](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.4...@aws-amplify/amplify-category-api@1.2.5) (2022-01-31)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.4](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.3...@aws-amplify/amplify-category-api@1.2.4) (2022-01-27)
+
+
+### Bug Fixes
+
+* handle auth and api dependency in state files correctly ([#9528](https://github.com/aws-amplify/amplify-cli/issues/9528)) ([edced3e](https://github.com/aws-amplify/amplify-cli/commit/edced3ef76da02bd269cf85c7287bfb74875d967)), closes [#9341](https://github.com/aws-amplify/amplify-cli/issues/9341)
+* rest api override CloudFormation parameters ([#9325](https://github.com/aws-amplify/amplify-cli/issues/9325)) ([3338cfa](https://github.com/aws-amplify/amplify-cli/commit/3338cfaee199f83d2e270f12bb41983c067f42fe)), closes [#9221](https://github.com/aws-amplify/amplify-cli/issues/9221)
+
+
+### Reverts
+
+* Revert "fix: handle auth and api dependency in state files correctly (#9528)" (#9602) ([b99b08e](https://github.com/aws-amplify/amplify-cli/commit/b99b08e187be8f2f77761ccdd1092a0cba86a051)), closes [#9528](https://github.com/aws-amplify/amplify-cli/issues/9528) [#9602](https://github.com/aws-amplify/amplify-cli/issues/9602)
+
+
+
+
+
+## [1.2.3](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.2...@aws-amplify/amplify-category-api@1.2.3) (2022-01-23)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.2.2](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.1...@aws-amplify/amplify-category-api@1.2.2) (2022-01-20)
+
+
+### Bug Fixes
+
+* **api:** container rest api push treats incorrect initial push behavior ([#9556](https://github.com/aws-amplify/amplify-cli/issues/9556)) ([e1fadef](https://github.com/aws-amplify/amplify-cli/commit/e1fadef8152608fc1a9a088b065af95846fe0efc))
+
+
+
+
+
+## [1.2.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.2.0...@aws-amplify/amplify-category-api@1.2.1) (2022-01-20)
+
+
+### Bug Fixes
+
+* **api:** container secrets pickup correct environment values ([#9513](https://github.com/aws-amplify/amplify-cli/issues/9513)) ([9986bd6](https://github.com/aws-amplify/amplify-cli/commit/9986bd6e0885609a04080f617db6c7331fb76f6a))
+
+
+
+
+
+# [1.2.0](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.10...@aws-amplify/amplify-category-api@1.2.0) (2022-01-13)
+
+
+### Features
+
+* **amplify-category-api:** enable default 4xx and 5xx api gateway cors response ([#9158](https://github.com/aws-amplify/amplify-cli/issues/9158)) ([b35cbda](https://github.com/aws-amplify/amplify-cli/commit/b35cbda1a567142b72dc068081abd8fb65860074)), closes [#5183](https://github.com/aws-amplify/amplify-cli/issues/5183)
+
+
+
+
+
+## [1.1.10](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.8...@aws-amplify/amplify-category-api@1.1.10) (2022-01-10)
+
+
+
+## 7.6.7 (2022-01-10)
+
+
+### Bug Fixes
+
+* update dep and use node test environment ([#9434](https://github.com/aws-amplify/amplify-cli/issues/9434)) ([1691327](https://github.com/aws-amplify/amplify-cli/commit/1691327740ea40d0ebb974e6aeabc64c62b288ef))
+
+
+
+
+
+## [1.1.8](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.7...@aws-amplify/amplify-category-api@1.1.8) (2021-12-21)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+## [1.1.7](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.6...@aws-amplify/amplify-category-api@1.1.7) (2021-12-17)
+
+
+### Bug Fixes
+
+* add missing salt to deployment logicalId ([#9234](https://github.com/aws-amplify/amplify-cli/issues/9234)) ([d4109e2](https://github.com/aws-amplify/amplify-cli/commit/d4109e2e8a18e2e9b787a97b1508efb1bf97fac9))
+* add-graphql-datasource command ([#9288](https://github.com/aws-amplify/amplify-cli/issues/9288)) ([f4cb8cb](https://github.com/aws-amplify/amplify-cli/commit/f4cb8cb2acdbe3024ff26385395860127fa78b5c))
+* improve api migration logic, update migration prompt ([#9267](https://github.com/aws-amplify/amplify-cli/issues/9267)) ([beed4f9](https://github.com/aws-amplify/amplify-cli/commit/beed4f9aa77bfbbb92ff0cb504e8019ce01e48f6))
+
+
+
+
+
+## [1.1.6](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.5...@aws-amplify/amplify-category-api@1.1.6) (2021-12-03)
+
+
+
+## 7.6.2 (2021-12-02)
+
+
+### Bug Fixes
+
+* add safety check ([#9193](https://github.com/aws-amplify/amplify-cli/issues/9193)) ([4c12eb1](https://github.com/aws-amplify/amplify-cli/commit/4c12eb16805e5866dc99d74c36fd1c97130bcd70))
+
+
+
+
+
+## [1.1.5](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.4...@aws-amplify/amplify-category-api@1.1.5) (2021-12-02)
+
+
+
+## 7.5.6 (2021-12-01)
+
+
+### Bug Fixes
+
+* call the correct migration function for Admin Queries ([#9174](https://github.com/aws-amplify/amplify-cli/issues/9174)) ([1ab2e66](https://github.com/aws-amplify/amplify-cli/commit/1ab2e66e1b54d09d68def7186b85644cb6d91653))
+
+
+
+
+
+## [1.1.4](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.3...@aws-amplify/amplify-category-api@1.1.4) (2021-12-01)
+
+
+### Bug Fixes
+
+* migrate rest apis with protected routes on push ([#9068](https://github.com/aws-amplify/amplify-cli/issues/9068)) ([62b4436](https://github.com/aws-amplify/amplify-cli/commit/62b44365108ba3410c9023623394aa98a52db84e))
+
+
+
+
+
+## [1.1.3](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.2...@aws-amplify/amplify-category-api@1.1.3) (2021-11-29)
+
+
+### Bug Fixes
+
+* use __dirname instead of '.' in import path ([#9105](https://github.com/aws-amplify/amplify-cli/issues/9105)) ([8b6c6e4](https://github.com/aws-amplify/amplify-cli/commit/8b6c6e43e86bdc067c2607df129a0aff64a7588d))
+
+
+
+
+
+## [1.1.2](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.1...@aws-amplify/amplify-category-api@1.1.2) (2021-11-26)
+
+
+
+## 7.5.3 (2021-11-26)
+
+
+### Bug Fixes
+
+* console override build issue ([#9078](https://github.com/aws-amplify/amplify-cli/issues/9078)) ([5c9bc5c](https://github.com/aws-amplify/amplify-cli/commit/5c9bc5c4003dd21c2897dc3c4faef9a9c19c1d99))
+* transformer version ([#9092](https://github.com/aws-amplify/amplify-cli/issues/9092)) ([acfa82c](https://github.com/aws-amplify/amplify-cli/commit/acfa82c9b275df0a7347ae0700a919dd8c03a4de))
+
+
+
+
+
+## [1.1.1](https://github.com/aws-amplify/amplify-cli/compare/@aws-amplify/amplify-category-api@1.1.0...@aws-amplify/amplify-category-api@1.1.1) (2021-11-24)
+
+**Note:** Version bump only for package @aws-amplify/amplify-category-api
+
+
+
+
+
+# 1.1.0 (2021-11-23)
+
+
+### Bug Fixes
+
+* asana bug fixes ([#8692](https://github.com/aws-amplify/amplify-cli/issues/8692)) ([c41d8b6](https://github.com/aws-amplify/amplify-cli/commit/c41d8b6442c48266f3fa074c9a9d33ce086ce1b9))
+* broken path on build-override ([798fd79](https://github.com/aws-amplify/amplify-cli/commit/798fd7988880f3c6617549e99b035e147e6d2137))
+* capitalization for filter, e2e test ([#8667](https://github.com/aws-amplify/amplify-cli/issues/8667)) ([4c5c869](https://github.com/aws-amplify/amplify-cli/commit/4c5c8699ca9261194ba0e6a5bfb958a0d95f412c))
+* **graphql:** detect resource update on graphql api auth mode change ([#8782](https://github.com/aws-amplify/amplify-cli/issues/8782)) ([714a122](https://github.com/aws-amplify/amplify-cli/commit/714a1221ec1ce72c88ba732172be6b8feab56a09))
+* **graphql:** refactor lambda authorizer code to use function category to create authorizer function ([#8784](https://github.com/aws-amplify/amplify-cli/issues/8784)) ([f529b54](https://github.com/aws-amplify/amplify-cli/commit/f529b541e2607eb4d2dd9e27810621fca141d6e2))
+* pushing multiple APIs at a time ([#8663](https://github.com/aws-amplify/amplify-cli/issues/8663)) ([23c2c4b](https://github.com/aws-amplify/amplify-cli/commit/23c2c4bb8e970e86285b72c224c9a51b112bfc0f))
+* remove await from sync read cfn calls ([#8977](https://github.com/aws-amplify/amplify-cli/issues/8977)) ([7ef6fb7](https://github.com/aws-amplify/amplify-cli/commit/7ef6fb72739d4618d02dba689a927831b53cb098))
+* stack generation logic when multiple paths ref same Lambda ([#8673](https://github.com/aws-amplify/amplify-cli/issues/8673)) ([d4a04e6](https://github.com/aws-amplify/amplify-cli/commit/d4a04e61db325b65737a94f33f7dba77d8a07529))
+
+
+### Features
+
+* extensibility for REST APIs ([#8598](https://github.com/aws-amplify/amplify-cli/issues/8598)) ([de19d23](https://github.com/aws-amplify/amplify-cli/commit/de19d231465c1f16bf7d1c7ccb8dba2f36d039d8))
+* override support for api category ([#9013](https://github.com/aws-amplify/amplify-cli/issues/9013)) ([ae7b001](https://github.com/aws-amplify/amplify-cli/commit/ae7b001f274f327a29c99c67fe851272c6208e84)), closes [#9001](https://github.com/aws-amplify/amplify-cli/issues/9001) [#8954](https://github.com/aws-amplify/amplify-cli/issues/8954) [#8958](https://github.com/aws-amplify/amplify-cli/issues/8958) [#8960](https://github.com/aws-amplify/amplify-cli/issues/8960) [#8967](https://github.com/aws-amplify/amplify-cli/issues/8967) [#8971](https://github.com/aws-amplify/amplify-cli/issues/8971) [#8976](https://github.com/aws-amplify/amplify-cli/issues/8976) [#8975](https://github.com/aws-amplify/amplify-cli/issues/8975) [#8981](https://github.com/aws-amplify/amplify-cli/issues/8981) [#8983](https://github.com/aws-amplify/amplify-cli/issues/8983) [#8992](https://github.com/aws-amplify/amplify-cli/issues/8992) [#9000](https://github.com/aws-amplify/amplify-cli/issues/9000) [#9002](https://github.com/aws-amplify/amplify-cli/issues/9002) [#9005](https://github.com/aws-amplify/amplify-cli/issues/9005) [#9006](https://github.com/aws-amplify/amplify-cli/issues/9006) [#9007](https://github.com/aws-amplify/amplify-cli/issues/9007) [#9008](https://github.com/aws-amplify/amplify-cli/issues/9008) [#9010](https://github.com/aws-amplify/amplify-cli/issues/9010) [#9011](https://github.com/aws-amplify/amplify-cli/issues/9011) [#9012](https://github.com/aws-amplify/amplify-cli/issues/9012) [#9014](https://github.com/aws-amplify/amplify-cli/issues/9014) [#9015](https://github.com/aws-amplify/amplify-cli/issues/9015) [#9017](https://github.com/aws-amplify/amplify-cli/issues/9017) [#9020](https://github.com/aws-amplify/amplify-cli/issues/9020) [#9024](https://github.com/aws-amplify/amplify-cli/issues/9024) [#9027](https://github.com/aws-amplify/amplify-cli/issues/9027) [#9028](https://github.com/aws-amplify/amplify-cli/issues/9028) [#9029](https://github.com/aws-amplify/amplify-cli/issues/9029) [#9032](https://github.com/aws-amplify/amplify-cli/issues/9032) [#9031](https://github.com/aws-amplify/amplify-cli/issues/9031) [#9035](https://github.com/aws-amplify/amplify-cli/issues/9035) [#9038](https://github.com/aws-amplify/amplify-cli/issues/9038) [#9039](https://github.com/aws-amplify/amplify-cli/issues/9039)
+
+
+
+# 6.4.0 (2021-11-10)
+
+
+### Bug Fixes
+
+* [#8223](https://github.com/aws-amplify/amplify-cli/issues/8223), conversion to typescript ([#8245](https://github.com/aws-amplify/amplify-cli/issues/8245)) ([096e6ca](https://github.com/aws-amplify/amplify-cli/commit/096e6ca19b94aa40ef249ea98d008380395afa16))
+* **amplify-category-api:** change auth directive type and fix codegen bug ([#8639](https://github.com/aws-amplify/amplify-cli/issues/8639)) ([b8d838d](https://github.com/aws-amplify/amplify-cli/commit/b8d838ddfd332c0f6fb36ef52ab76da24b5d26ca))
+* **amplify-category-api:** fixed api to reference stack name and deployment bucket ([#8145](https://github.com/aws-amplify/amplify-cli/issues/8145)) ([4c7493a](https://github.com/aws-amplify/amplify-cli/commit/4c7493ac34fa89cab0c80e5c674bbeb102891a64))
+* api containers on repushing does not fail ([#8416](https://github.com/aws-amplify/amplify-cli/issues/8416)) ([eb64172](https://github.com/aws-amplify/amplify-cli/commit/eb641725febba88ebb7349c0e662d4ffc6cf7e97))
+* expand region support for aurora serverless ([#8577](https://github.com/aws-amplify/amplify-cli/issues/8577)) ([ad0cd2b](https://github.com/aws-amplify/amplify-cli/commit/ad0cd2b7e0644986276aa295dd424976f5c3ab68))
+* **graphql-model-transformer:** fixed schema template options check for transformer version ([#8449](https://github.com/aws-amplify/amplify-cli/issues/8449)) ([aedcae3](https://github.com/aws-amplify/amplify-cli/commit/aedcae36f445c6e990bd94fd29d1b012e1b13787))
+* **graphql:** lambda auth label fix ([#8623](https://github.com/aws-amplify/amplify-cli/issues/8623)) ([6b4994d](https://github.com/aws-amplify/amplify-cli/commit/6b4994dd860015dd7f72b0f162314ffd580c727e))
+* **graphql:** minor api prompt fixes ([#8603](https://github.com/aws-amplify/amplify-cli/issues/8603)) ([b9aabe2](https://github.com/aws-amplify/amplify-cli/commit/b9aabe22705cc5d418e83fc8a957f2aac59e0693))
+* remove duplicate error messages ([#8651](https://github.com/aws-amplify/amplify-cli/issues/8651)) ([aad5de7](https://github.com/aws-amplify/amplify-cli/commit/aad5de7b56b9b077b6b689c5b37d51dbfd4b262d))
+* schema migrator utility as separate command ([#8720](https://github.com/aws-amplify/amplify-cli/issues/8720)) ([46e1ee6](https://github.com/aws-amplify/amplify-cli/commit/46e1ee6a49dd86bb682b182a37626bc3f2f966ea))
+
+
+### Features
+
+* **amplify-provider-awscloudformation:** change global_auth_rule to globalAuthRule for global auth ([#8674](https://github.com/aws-amplify/amplify-cli/issues/8674)) ([7a06216](https://github.com/aws-amplify/amplify-cli/commit/7a06216c0a56d9ab886ebb16b2179394fc5e76d2))
+* **amplify-provider-awscloudformation:** change sandbox mode syntax in schema ([#8592](https://github.com/aws-amplify/amplify-cli/issues/8592)) ([a3bdd44](https://github.com/aws-amplify/amplify-cli/commit/a3bdd44fddd3414a39d561510092084a1b8e6e61))
+* Custom policies IAM Policies for Lambda and Containers ([#8068](https://github.com/aws-amplify/amplify-cli/issues/8068)) ([3e1ce0d](https://github.com/aws-amplify/amplify-cli/commit/3e1ce0de4d25ab239adcdcef778cc82f30b17a94))
+* flag to allow destructive schema changes ([#8273](https://github.com/aws-amplify/amplify-cli/issues/8273)) ([18de856](https://github.com/aws-amplify/amplify-cli/commit/18de856fb61bf2df8f73375e4e55a58c6159a232))
+* Flag to allow schema changes that require table replacement ([#8144](https://github.com/aws-amplify/amplify-cli/issues/8144)) ([2d4e65a](https://github.com/aws-amplify/amplify-cli/commit/2d4e65acfd034d33c6fa8ac1f5f8582e7e3bc399))
+
+
+### Reverts
+
+* Revert "Lambda auth minor fixes (#8741)" (#8762) ([aa1096c](https://github.com/aws-amplify/amplify-cli/commit/aa1096ca504bdb7e6a2dca2963c546f957116f9d)), closes [#8741](https://github.com/aws-amplify/amplify-cli/issues/8741) [#8762](https://github.com/aws-amplify/amplify-cli/issues/8762)
+* Revert "feat: Flag to allow schema changes that require table replacement (#8144)" (#8268) ([422dd04](https://github.com/aws-amplify/amplify-cli/commit/422dd04425c72aa7276e086d38ce4d5f4681f9f3)), closes [#8144](https://github.com/aws-amplify/amplify-cli/issues/8144) [#8268](https://github.com/aws-amplify/amplify-cli/issues/8268)
+
+
+
+# 5.3.0 (2021-08-04)
+
+
+### Bug Fixes
+
+* api resource name case sensitivity failure ([#7452](https://github.com/aws-amplify/amplify-cli/issues/7452)) ([7bd5524](https://github.com/aws-amplify/amplify-cli/commit/7bd5524703a8ac963cf4d9ef6a1fbb031e42e3c4))
+* carry existing container secret config over ([#7224](https://github.com/aws-amplify/amplify-cli/issues/7224)) ([b2f3bf7](https://github.com/aws-amplify/amplify-cli/commit/b2f3bf7059ce3ca1e72cf6c451edd3e61699828a))
+* conditionally rebuild container apis on push ([#7175](https://github.com/aws-amplify/amplify-cli/issues/7175)) ([a27a033](https://github.com/aws-amplify/amplify-cli/commit/a27a033af0fe6a9db8becd15b713113c64e70eb3))
+* **container-hosting:** ignore test cases ([#7895](https://github.com/aws-amplify/amplify-cli/issues/7895)) ([f051445](https://github.com/aws-amplify/amplify-cli/commit/f05144510311fd38f188fd2d86a9fb0c74219269))
+* **graphql-model-transformer:** model input fields transform ([#7857](https://github.com/aws-amplify/amplify-cli/issues/7857)) ([12ff663](https://github.com/aws-amplify/amplify-cli/commit/12ff663a94a4896bd9eacef3847be15b7631d8df))
+* **graphql-transformer-common:** improve generated graphql pluralization ([#7258](https://github.com/aws-amplify/amplify-cli/issues/7258)) ([fc3ad0d](https://github.com/aws-amplify/amplify-cli/commit/fc3ad0dd5a12a7912c59ae12024f593b4cdf7f2d)), closes [#4224](https://github.com/aws-amplify/amplify-cli/issues/4224)
+* lambda timeout should be an integer type ([#7699](https://github.com/aws-amplify/amplify-cli/issues/7699)) ([cbacf4d](https://github.com/aws-amplify/amplify-cli/commit/cbacf4d3e497421855c09825970e025550aacfd7))
+* make ECR repository name validation more strict ([#7337](https://github.com/aws-amplify/amplify-cli/issues/7337)) ([188efdd](https://github.com/aws-amplify/amplify-cli/commit/188efdde6ded25a06c6fb52e0b2abe04981b0993))
+* multi-env container hosting ([#7009](https://github.com/aws-amplify/amplify-cli/issues/7009)) ([#7346](https://github.com/aws-amplify/amplify-cli/issues/7346)) ([6c33215](https://github.com/aws-amplify/amplify-cli/commit/6c33215d064029add6b93bb10cad96bb63f40101))
+* support adding REST API paths in 'add api' ([#7229](https://github.com/aws-amplify/amplify-cli/issues/7229)) ([fa9404a](https://github.com/aws-amplify/amplify-cli/commit/fa9404afd1eedd342ea6ff2033fcbd143b33748a))
+* unique api name check works when no existing apis ([#7615](https://github.com/aws-amplify/amplify-cli/issues/7615)) ([8cef10f](https://github.com/aws-amplify/amplify-cli/commit/8cef10f051bea8428f3b16095137e14346218bb3))
+* update overlapping REST path warning ([#7276](https://github.com/aws-amplify/amplify-cli/issues/7276)) ([3fc7534](https://github.com/aws-amplify/amplify-cli/commit/3fc75343ba228307080f3ef6a6cae4cf3387a007))
+
+
+
+## 4.50.2 (2021-05-03)
+
+
+
+## 4.50.1 (2021-05-03)
+
+
+### Bug Fixes
+
+* [#6123](https://github.com/aws-amplify/amplify-cli/issues/6123) - add missing amplify-cli-core dependencies to packages ([#6124](https://github.com/aws-amplify/amplify-cli/issues/6124)) ([e6519f2](https://github.com/aws-amplify/amplify-cli/commit/e6519f2dd81d2983b797f226d723a73a25967d25))
+* **amplify-category-api:** mantain ff in iam api policy ([#6723](https://github.com/aws-amplify/amplify-cli/issues/6723)) ([51e5e1b](https://github.com/aws-amplify/amplify-cli/commit/51e5e1b53514a05788dd824a48991c0db0b9705d)), closes [#6675](https://github.com/aws-amplify/amplify-cli/issues/6675)
+* **cli:** use more inclusive language ([#6919](https://github.com/aws-amplify/amplify-cli/issues/6919)) ([bb70464](https://github.com/aws-amplify/amplify-cli/commit/bb70464d6c24fa931c0eb80d234a496d936913f5))
+* consolidate REST API IAM policies ([#6904](https://github.com/aws-amplify/amplify-cli/issues/6904)) (ref [#2084](https://github.com/aws-amplify/amplify-cli/issues/2084)) ([5cfff17](https://github.com/aws-amplify/amplify-cli/commit/5cfff173d57ec9ab68984faf2d0f6474eccdcaae))
+* fix appsync permission assignment from functions ([#5342](https://github.com/aws-amplify/amplify-cli/issues/5342)) ([b2e2dd0](https://github.com/aws-amplify/amplify-cli/commit/b2e2dd0071c1a451ba032cf7f8cfe7cf6381a96e))
+* handle errors and provide better error message when adding data source ([#7117](https://github.com/aws-amplify/amplify-cli/issues/7117)) (ref [#4384](https://github.com/aws-amplify/amplify-cli/issues/4384)) ([888829b](https://github.com/aws-amplify/amplify-cli/commit/888829ba6f53209ca12d215ed510d5e201d025ee))
+* remove process on next and await ([#6239](https://github.com/aws-amplify/amplify-cli/issues/6239)) ([59d4a0e](https://github.com/aws-amplify/amplify-cli/commit/59d4a0eb318d2b3ad97be34bda9dee756cf82d74))
+* sets policy resource name in api update flow ([328abac](https://github.com/aws-amplify/amplify-cli/commit/328abacd81d03dbae8b0c0246536c465a7954c1e))
+* wording: Enable, instead of Configure, conflict detection ([#6708](https://github.com/aws-amplify/amplify-cli/issues/6708)) ([dac6ae9](https://github.com/aws-amplify/amplify-cli/commit/dac6ae94af47dd01da25ea4f61efd5442cb4c06b))
+
+
+### Features
+
+* allows adding graphql datasource with an empty graphql schema file ([#4464](https://github.com/aws-amplify/amplify-cli/issues/4464)) ([2b71a2d](https://github.com/aws-amplify/amplify-cli/commit/2b71a2df31585ad06674417b7003dfb70d5b785d))
+* dont open urls when CLI is running in CI ([#6503](https://github.com/aws-amplify/amplify-cli/issues/6503)) ([27546a7](https://github.com/aws-amplify/amplify-cli/commit/27546a78159ea95c636dbbd094fe6a4f7fb8f8f4)), closes [#5973](https://github.com/aws-amplify/amplify-cli/issues/5973)
+
+
+### Reverts
+
+* Revert "docs: add readme to vtl resolvers directory (#6718)" (#6845) ([1b67327](https://github.com/aws-amplify/amplify-cli/commit/1b67327f4885c611708c73256094456ab95b67ef)), closes [#6718](https://github.com/aws-amplify/amplify-cli/issues/6718) [#6845](https://github.com/aws-amplify/amplify-cli/issues/6845)
+
+
+
+# 4.39.0 (2020-12-10)
+
+
+### Bug Fixes
+
+* containers - don't wait for pipeline on hosting ([#6137](https://github.com/aws-amplify/amplify-cli/issues/6137)) ([c75d694](https://github.com/aws-amplify/amplify-cli/commit/c75d69436104cb974684b0ed48c743294f70d556))
+* only ignore root src folder ([#6136](https://github.com/aws-amplify/amplify-cli/issues/6136)) ([6289f5d](https://github.com/aws-amplify/amplify-cli/commit/6289f5ddc803eecf4d048075d769153c978523ac))
+
+
+### Features
+
+* container-based deployments([#5727](https://github.com/aws-amplify/amplify-cli/issues/5727)) ([fad6377](https://github.com/aws-amplify/amplify-cli/commit/fad6377bd384862ca4429cb1a83eee90efd62b58))
+* pre-deploy pull, new login mechanism and pkg cli updates ([#5941](https://github.com/aws-amplify/amplify-cli/issues/5941)) ([7274251](https://github.com/aws-amplify/amplify-cli/commit/7274251faadc1035acce5f44699b172e10e2e67d))
+
+
+
+# 4.32.0-alpha.0 (2020-10-27)
+
+
+### Bug Fixes
+
+* [#429](https://github.com/aws-amplify/amplify-cli/issues/429) - Editor hanging bug ([#2086](https://github.com/aws-amplify/amplify-cli/issues/2086)) ([6767445](https://github.com/aws-amplify/amplify-cli/commit/676744549f903fa3a4804d814eb325301ed462ba))
+* add support for mobile hub migrated resources ([#5407](https://github.com/aws-amplify/amplify-cli/issues/5407)) ([5dfe287](https://github.com/aws-amplify/amplify-cli/commit/5dfe2872c153047ebdc56bc4f671fd57c12379d9))
+* added exit code on remove ([#5427](https://github.com/aws-amplify/amplify-cli/issues/5427)) ([33132f7](https://github.com/aws-amplify/amplify-cli/commit/33132f764b290cafd345720409a5db8ea6088069))
+* **amplify-category-api:** add check for provider during migration ([3207e41](https://github.com/aws-amplify/amplify-cli/commit/3207e4153e5a9f8a41dad5757d1ec83b7fc8185a)), closes [#918](https://github.com/aws-amplify/amplify-cli/issues/918)
+* **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+* **amplify-category-api:** added check to read schema in schema dir ([#3871](https://github.com/aws-amplify/amplify-cli/issues/3871)) ([21bd229](https://github.com/aws-amplify/amplify-cli/commit/21bd229b5c15e4ce837da604ec73e7f40076170f)), closes [fixes#3082](https://github.com/fixes/issues/3082)
+* **amplify-category-api:** edit auth workflow if cognito is not used ([#3232](https://github.com/aws-amplify/amplify-cli/issues/3232)) ([f9473cf](https://github.com/aws-amplify/amplify-cli/commit/f9473cf50bbcf43a701f1f44b6f4d451dc2be237)), closes [#2967](https://github.com/aws-amplify/amplify-cli/issues/2967)
+* **amplify-category-api:** Fix [#2498](https://github.com/aws-amplify/amplify-cli/issues/2498) ([#2503](https://github.com/aws-amplify/amplify-cli/issues/2503)) ([35aab06](https://github.com/aws-amplify/amplify-cli/commit/35aab06c1ac9d3081f4f2e06ae18c14ef212aa43))
+* **amplify-category-api:** fix api add-graphql-datasource command ([#2320](https://github.com/aws-amplify/amplify-cli/issues/2320)) ([a9c829d](https://github.com/aws-amplify/amplify-cli/commit/a9c829d79e91246d2bb9a707ccfe886502ceebe2))
+* **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+* **amplify-category-api:** fix init env bug ([#1715](https://github.com/aws-amplify/amplify-cli/issues/1715)) ([1e21371](https://github.com/aws-amplify/amplify-cli/commit/1e21371900c315ca9fcbb9bcb1f4c8ec9800ee86)), closes [#1713](https://github.com/aws-amplify/amplify-cli/issues/1713)
+* **amplify-category-api:** include userpool id in parameter.json ([#2238](https://github.com/aws-amplify/amplify-cli/issues/2238)) ([143b847](https://github.com/aws-amplify/amplify-cli/commit/143b84739d754f09f29f73678fd5a60674fd9304))
+* **amplify-category-api:** plumb api id to resources that require it ([#3464](https://github.com/aws-amplify/amplify-cli/issues/3464)) ([2b2d52f](https://github.com/aws-amplify/amplify-cli/commit/2b2d52f05edc1190953965ca0f3ecd880ec66a63)), closes [#3431](https://github.com/aws-amplify/amplify-cli/issues/3431) [#3386](https://github.com/aws-amplify/amplify-cli/issues/3386)
+* **amplify-category-api:** safeguard prompt with empty options ([#2430](https://github.com/aws-amplify/amplify-cli/issues/2430)) ([cb8f6dd](https://github.com/aws-amplify/amplify-cli/commit/cb8f6dddefb7f7e7f8159988563fc076f470ee79)), closes [#2423](https://github.com/aws-amplify/amplify-cli/issues/2423)
+* **amplify-category-api:** toggle datastore in update ([#4276](https://github.com/aws-amplify/amplify-cli/issues/4276)) ([4f02a62](https://github.com/aws-amplify/amplify-cli/commit/4f02a62f5c8929cabe914e2e38fb28dc535d2d61)), closes [#4058](https://github.com/aws-amplify/amplify-cli/issues/4058)
+* **amplify-category-api:** use standard json read ([#2581](https://github.com/aws-amplify/amplify-cli/issues/2581)) ([3adc395](https://github.com/aws-amplify/amplify-cli/commit/3adc395a5e4ccf3673735f8091db63923a46c501))
+* **amplify-provider-awscloudformation:** apigw unauth access ([#1906](https://github.com/aws-amplify/amplify-cli/issues/1906)) ([bcd0d02](https://github.com/aws-amplify/amplify-cli/commit/bcd0d02a229d3dab2e5babc40b68ac9090aa5f15))
+* change default length for api key back to 7 days ([#2507](https://github.com/aws-amplify/amplify-cli/issues/2507)) ([6a7e61f](https://github.com/aws-amplify/amplify-cli/commit/6a7e61fc7315f5e732ad7b36b5c0ae88ea36b628))
+* checking undefined auth config ([#5313](https://github.com/aws-amplify/amplify-cli/issues/5313)) ([42810c9](https://github.com/aws-amplify/amplify-cli/commit/42810c98b0015f12119f3387749889a6bf6abe9b))
+* **cli:** add console command in the help message ([#2494](https://github.com/aws-amplify/amplify-cli/issues/2494)) ([cf0eddd](https://github.com/aws-amplify/amplify-cli/commit/cf0eddd1ba27b1126b0745cc068f205b2c2c8343)), closes [#1607](https://github.com/aws-amplify/amplify-cli/issues/1607)
+* **cli:** deleting the amplify app on delete ([#3568](https://github.com/aws-amplify/amplify-cli/issues/3568)) ([f39bbcb](https://github.com/aws-amplify/amplify-cli/commit/f39bbcb715875eeeb612bcbc40b275b33f85eaf6)), closes [#3239](https://github.com/aws-amplify/amplify-cli/issues/3239)
+* **cli:** fix inquirer version ([#1690](https://github.com/aws-amplify/amplify-cli/issues/1690)) ([9246032](https://github.com/aws-amplify/amplify-cli/commit/9246032603db49022c444e41faa5881592ce5dc9)), closes [#1688](https://github.com/aws-amplify/amplify-cli/issues/1688)
+* **cli:** remove calls to gluegun's prompt.confirm ([#546](https://github.com/aws-amplify/amplify-cli/issues/546)) ([0080ddb](https://github.com/aws-amplify/amplify-cli/commit/0080ddbf5bc19bbbff7d4187167a748b5b578fce))
+* **cli:** remove unnecessary stack trace log when adding services ([#4610](https://github.com/aws-amplify/amplify-cli/issues/4610)) ([56efb32](https://github.com/aws-amplify/amplify-cli/commit/56efb32b79c47839cb9506a9300d40a01875a9fc))
+* data inconsitency ([#5344](https://github.com/aws-amplify/amplify-cli/issues/5344)) ([bfe1903](https://github.com/aws-amplify/amplify-cli/commit/bfe19038b5b676056f45d7ffcc4c2460057936d8))
+* fixing the IAM policies for AppSync API ([#1634](https://github.com/aws-amplify/amplify-cli/issues/1634)) ([9fb2fa9](https://github.com/aws-amplify/amplify-cli/commit/9fb2fa956d9d86b07c837a547766000fe88d3011))
+* **graphql-auth-transformer:** add a time delay when creating apiKey ([#4493](https://github.com/aws-amplify/amplify-cli/issues/4493)) ([3f544e7](https://github.com/aws-amplify/amplify-cli/commit/3f544e7f421f66f3d4e920cdd89ddb926c412241))
+* **graphql-elasticsearch-transformer:** fix duplicate records in es lambda ([#3712](https://github.com/aws-amplify/amplify-cli/issues/3712)) ([dd9f7e0](https://github.com/aws-amplify/amplify-cli/commit/dd9f7e0031a0dc68a9027de02f60bbe69d315c3d)), closes [#3602](https://github.com/aws-amplify/amplify-cli/issues/3602) [#3705](https://github.com/aws-amplify/amplify-cli/issues/3705)
+* incorrect type on graphql boilerplate schema ([#4070](https://github.com/aws-amplify/amplify-cli/issues/4070)) ([d96171a](https://github.com/aws-amplify/amplify-cli/commit/d96171a7461ecbb610c3cbcbcb05cdf5492dc8e5))
+* move test package dependencies to devDependencies ([#2034](https://github.com/aws-amplify/amplify-cli/issues/2034)) ([f5623d0](https://github.com/aws-amplify/amplify-cli/commit/f5623d04a43e685901f4f1cd96e2a227164c71ee))
+* populate API_KEY env var when present ([#4923](https://github.com/aws-amplify/amplify-cli/issues/4923)) ([81231f9](https://github.com/aws-amplify/amplify-cli/commit/81231f98305dd9e37bb64eb30a9c7307bb471ad9))
+* refactor mobile hub migration checks ([#5632](https://github.com/aws-amplify/amplify-cli/issues/5632)) ([b796eb8](https://github.com/aws-amplify/amplify-cli/commit/b796eb8303bb903f5f531506254441a63eba2962))
+* remove mutableParametersState from stored function-params ([#4897](https://github.com/aws-amplify/amplify-cli/issues/4897)) ([c608166](https://github.com/aws-amplify/amplify-cli/commit/c6081668798e94165ede40bb06439075946e3e86))
+* removes duplicate auth types ([#5272](https://github.com/aws-amplify/amplify-cli/issues/5272)) ([8747911](https://github.com/aws-amplify/amplify-cli/commit/8747911ff5a515e86971af7d5ad15681c64eb532))
+* return undefined for empty conflict resolution ([#4982](https://github.com/aws-amplify/amplify-cli/issues/4982)) ([7c5bf1a](https://github.com/aws-amplify/amplify-cli/commit/7c5bf1a36078a345d80ecbf2cea3a067ae1137e1)), closes [#4965](https://github.com/aws-amplify/amplify-cli/issues/4965)
+* sub * for path parms in CFN policy ([#5092](https://github.com/aws-amplify/amplify-cli/issues/5092)) ([2942688](https://github.com/aws-amplify/amplify-cli/commit/29426884968314122b65a24b2f9658a618bf9120))
+* update CLI to handle UTF8 BOM ([#1357](https://github.com/aws-amplify/amplify-cli/issues/1357)) ([b0afa07](https://github.com/aws-amplify/amplify-cli/commit/b0afa07ab22d50409ff93c41350995cd7d2a1084)), closes [#1355](https://github.com/aws-amplify/amplify-cli/issues/1355) [#1122](https://github.com/aws-amplify/amplify-cli/issues/1122)
+* update graphql schema to match docs ([#3652](https://github.com/aws-amplify/amplify-cli/issues/3652)) ([dc3c866](https://github.com/aws-amplify/amplify-cli/commit/dc3c8661066be6bfdbb404b81a73bfed1fcf0095)), closes [#3513](https://github.com/aws-amplify/amplify-cli/issues/3513)
+* validatePathName_validPath matcher ([#4559](https://github.com/aws-amplify/amplify-cli/issues/4559)) ([94c00c4](https://github.com/aws-amplify/amplify-cli/commit/94c00c43e912e94a03ab10acbd93ad3dc5d2c18c))
+
+
+### Features
+
+* add a warning on migration and force compile gql schema ([77fb557](https://github.com/aws-amplify/amplify-cli/commit/77fb5573be5ca006a5cdcbc1226d834549a74732))
+* add graphQLEndpoint as an env var to lambda functions ([#1641](https://github.com/aws-amplify/amplify-cli/issues/1641)) ([ae825a6](https://github.com/aws-amplify/amplify-cli/commit/ae825a61514f7e173da012326a2f5de0de0626e4)), closes [#1620](https://github.com/aws-amplify/amplify-cli/issues/1620)
+* add option to open AppSync console using the CLI ([#386](https://github.com/aws-amplify/amplify-cli/issues/386)) ([3874a57](https://github.com/aws-amplify/amplify-cli/commit/3874a571d9ee9699f5b73ca985ca80e92909133a))
+* adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c1927da10f8c54f38a523021187361131c))
+* allow creation of REST API endpoint at root path (/) ([#4649](https://github.com/aws-amplify/amplify-cli/issues/4649)) ([49d8121](https://github.com/aws-amplify/amplify-cli/commit/49d8121ade1f06bf23d511523b88e9dd6c289073)), closes [#3868](https://github.com/aws-amplify/amplify-cli/issues/3868) [#4834](https://github.com/aws-amplify/amplify-cli/issues/4834)
+* **amplify-category-api:** add rds support for new regions ([#5360](https://github.com/aws-amplify/amplify-cli/issues/5360)) ([4f65ed1](https://github.com/aws-amplify/amplify-cli/commit/4f65ed1ba6ab76f7d018b998525b73aa1e47fbcd)), closes [#4739](https://github.com/aws-amplify/amplify-cli/issues/4739)
+* **amplify-category-api:** allow minified CF stack templates ([#3520](https://github.com/aws-amplify/amplify-cli/issues/3520)) ([6da2a63](https://github.com/aws-amplify/amplify-cli/commit/6da2a634548fdf48deb4b1144c67d1e1515abb80)), closes [#2914](https://github.com/aws-amplify/amplify-cli/issues/2914)
+* **amplify-category-api:** support path parameters in REST APIs ([#3394](https://github.com/aws-amplify/amplify-cli/issues/3394)) ([fa7d07e](https://github.com/aws-amplify/amplify-cli/commit/fa7d07e1f6f54185a37851ea9d4c840b092501cc))
+* **amplify-category-function:** refactor to support runtime and template plugins ([#3517](https://github.com/aws-amplify/amplify-cli/issues/3517)) ([607ae21](https://github.com/aws-amplify/amplify-cli/commit/607ae21287941805f44ea8a9b78dd12d16d71f85))
+* **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+* **cli:** new plugin platform ([#2254](https://github.com/aws-amplify/amplify-cli/issues/2254)) ([7ec29dd](https://github.com/aws-amplify/amplify-cli/commit/7ec29dd4f2da8c90727b36469eca646d289877b6))
+* **cli:** usage measurement ([#3641](https://github.com/aws-amplify/amplify-cli/issues/3641)) ([a755863](https://github.com/aws-amplify/amplify-cli/commit/a7558637fbb791dc22e0a91ae16f1b96fe4e99df))
+* conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+* Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+* flow to add policies to access amplify resources from Lambda ([#1462](https://github.com/aws-amplify/amplify-cli/issues/1462)) ([fee247c](https://github.com/aws-amplify/amplify-cli/commit/fee247c74f54b050f7b7a6ea0733fbd08976f232))
+* headless mode for API category ([#4834](https://github.com/aws-amplify/amplify-cli/issues/4834)) ([c2e09d7](https://github.com/aws-amplify/amplify-cli/commit/c2e09d73fd1bb461eeace8f4a7addd70a63047ad))
+* implement multi-auth functionality ([#1916](https://github.com/aws-amplify/amplify-cli/issues/1916)) ([b99f58e](https://github.com/aws-amplify/amplify-cli/commit/b99f58e4a2b85cbe9f430838554ae3c277440132))
+* Lambda layers ([#4697](https://github.com/aws-amplify/amplify-cli/issues/4697)) ([4e97400](https://github.com/aws-amplify/amplify-cli/commit/4e974007d95c894ab4108a2dff8d5996e7e3ce25))
+* migration of API GW and Interactions ([a91ba9a](https://github.com/aws-amplify/amplify-cli/commit/a91ba9ae4de8a49c7ce8b8912e2962fd1a59824b))
+* migration of categories - s3,dynamo,lambda,appsync ([#495](https://github.com/aws-amplify/amplify-cli/issues/495)) ([1ef1d21](https://github.com/aws-amplify/amplify-cli/commit/1ef1d210b9accf8ba2571a42e3529ec24aa29bb3))
+* multi-environment support for API Gateway ([#418](https://github.com/aws-amplify/amplify-cli/issues/418)) ([24ddf83](https://github.com/aws-amplify/amplify-cli/commit/24ddf83066dc2c8e531e5f5e48e5145e2b6acf90))
+* multi-environment support for interactions category ([4ca2617](https://github.com/aws-amplify/amplify-cli/commit/4ca26177aef907f911c1f961f962b35ba07f4810))
+* sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe8925a4e73358b03ba927267a2df328b78))
+* support for provisioning Cognito Hosted UI and support CRUD operations in Storage and API categories ([729b0de](https://github.com/aws-amplify/amplify-cli/commit/729b0de411e5a576271f270d765cc31e4ee1424d))
+* support importing of auth resources ([#5591](https://github.com/aws-amplify/amplify-cli/issues/5591)) ([7903246](https://github.com/aws-amplify/amplify-cli/commit/790324680544fe18481f91390001f9f07a144203))
+* uplevel enabling of datastore and update of auth configs to top ([#3495](https://github.com/aws-amplify/amplify-cli/issues/3495)) ([f406bb2](https://github.com/aws-amplify/amplify-cli/commit/f406bb29957c98caf427a3cb46e2126f6dcf212f))
+* User Pool Groups, Admin Auth Support, Custom Group Role Policies ([#2443](https://github.com/aws-amplify/amplify-cli/issues/2443)) ([09aecfd](https://github.com/aws-amplify/amplify-cli/commit/09aecfd0cb3dae2c17d1c512946cc733c4fe3d4c))
+
+
+### Reverts
+
+* Revert problematic PRs (#4803) ([f21a0f4](https://github.com/aws-amplify/amplify-cli/commit/f21a0f449a23c0c80a6f3280eef76bcbf3e9cb7c)), closes [#4803](https://github.com/aws-amplify/amplify-cli/issues/4803) [#4796](https://github.com/aws-amplify/amplify-cli/issues/4796) [#4576](https://github.com/aws-amplify/amplify-cli/issues/4576) [#4575](https://github.com/aws-amplify/amplify-cli/issues/4575) [#4610](https://github.com/aws-amplify/amplify-cli/issues/4610)
+* Revert "Changing behavior so that the switch to PAY_PER_REQUEST billing is explicit. Users now set a parameter UsePayPerRequestBilling. This makes the migration steps occur much faster." ([e278fe1](https://github.com/aws-amplify/amplify-cli/commit/e278fe1f8edc85054a9684534c00225e4a79b242))
+
+
+
+
+
+## [3.3.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@3.3.4...amplify-category-api@3.3.5) (2021-11-21)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [3.3.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@3.3.3...amplify-category-api@3.3.4) (2021-11-20)
+
+
+### Bug Fixes
+
+* remove await from sync read cfn calls ([#8977](https://github.com/aws-amplify/amplify-cli/issues/8977)) ([7ef6fb7](https://github.com/aws-amplify/amplify-cli/commit/7ef6fb72739d4618d02dba689a927831b53cb098))
+
+
+
+
+
+## [3.3.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@3.3.1...amplify-category-api@3.3.3) (2021-11-19)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [3.3.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@3.3.1...amplify-category-api@3.3.2) (2021-11-19)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [3.3.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@3.3.0...amplify-category-api@3.3.1) (2021-11-17)
+
+
+### Bug Fixes
+
+* **graphql:** detect resource update on graphql api auth mode change ([#8782](https://github.com/aws-amplify/amplify-cli/issues/8782)) ([714a122](https://github.com/aws-amplify/amplify-cli/commit/714a1221ec1ce72c88ba732172be6b8feab56a09))
+* **graphql:** refactor lambda authorizer code to use function category to create authorizer function ([#8784](https://github.com/aws-amplify/amplify-cli/issues/8784)) ([f529b54](https://github.com/aws-amplify/amplify-cli/commit/f529b541e2607eb4d2dd9e27810621fca141d6e2))
+
+
+
+
+
+# [3.3.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.34.0...amplify-category-api@3.3.0) (2021-11-15)
+
+
+### Bug Fixes
+
+* asana bug fixes ([#8692](https://github.com/aws-amplify/amplify-cli/issues/8692)) ([c41d8b6](https://github.com/aws-amplify/amplify-cli/commit/c41d8b6442c48266f3fa074c9a9d33ce086ce1b9))
+* broken path on build-override ([798fd79](https://github.com/aws-amplify/amplify-cli/commit/798fd7988880f3c6617549e99b035e147e6d2137))
+* capitalization for filter, e2e test ([#8667](https://github.com/aws-amplify/amplify-cli/issues/8667)) ([4c5c869](https://github.com/aws-amplify/amplify-cli/commit/4c5c8699ca9261194ba0e6a5bfb958a0d95f412c))
+* pushing multiple APIs at a time ([#8663](https://github.com/aws-amplify/amplify-cli/issues/8663)) ([23c2c4b](https://github.com/aws-amplify/amplify-cli/commit/23c2c4bb8e970e86285b72c224c9a51b112bfc0f))
+* stack generation logic when multiple paths ref same Lambda ([#8673](https://github.com/aws-amplify/amplify-cli/issues/8673)) ([d4a04e6](https://github.com/aws-amplify/amplify-cli/commit/d4a04e61db325b65737a94f33f7dba77d8a07529))
+
+
+### Features
+
+* extensibility for REST APIs ([#8598](https://github.com/aws-amplify/amplify-cli/issues/8598)) ([de19d23](https://github.com/aws-amplify/amplify-cli/commit/de19d231465c1f16bf7d1c7ccb8dba2f36d039d8))
+
+
+
+
+
+# [3.0.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.33.2...amplify-category-api@3.0.0) (2021-11-13)
+
+
+### Bug Fixes
+
+* asana bug fixes ([#8692](https://github.com/aws-amplify/amplify-cli/issues/8692)) ([c41d8b6](https://github.com/aws-amplify/amplify-cli/commit/c41d8b6442c48266f3fa074c9a9d33ce086ce1b9))
+* broken path on build-override ([798fd79](https://github.com/aws-amplify/amplify-cli/commit/798fd7988880f3c6617549e99b035e147e6d2137))
+* capitalization for filter, e2e test ([#8667](https://github.com/aws-amplify/amplify-cli/issues/8667)) ([4c5c869](https://github.com/aws-amplify/amplify-cli/commit/4c5c8699ca9261194ba0e6a5bfb958a0d95f412c))
+* pushing multiple APIs at a time ([#8663](https://github.com/aws-amplify/amplify-cli/issues/8663)) ([23c2c4b](https://github.com/aws-amplify/amplify-cli/commit/23c2c4bb8e970e86285b72c224c9a51b112bfc0f))
+* stack generation logic when multiple paths ref same Lambda ([#8673](https://github.com/aws-amplify/amplify-cli/issues/8673)) ([d4a04e6](https://github.com/aws-amplify/amplify-cli/commit/d4a04e61db325b65737a94f33f7dba77d8a07529))
+
+
+### Features
+
+* extensibility for REST APIs ([#8598](https://github.com/aws-amplify/amplify-cli/issues/8598)) ([de19d23](https://github.com/aws-amplify/amplify-cli/commit/de19d231465c1f16bf7d1c7ccb8dba2f36d039d8))
+
+
+
+# 6.4.0 (2021-11-10)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** change auth directive type and fix codegen bug ([#8639](https://github.com/aws-amplify/amplify-cli/issues/8639)) ([b8d838d](https://github.com/aws-amplify/amplify-cli/commit/b8d838ddfd332c0f6fb36ef52ab76da24b5d26ca))
+* expand region support for aurora serverless ([#8577](https://github.com/aws-amplify/amplify-cli/issues/8577)) ([ad0cd2b](https://github.com/aws-amplify/amplify-cli/commit/ad0cd2b7e0644986276aa295dd424976f5c3ab68))
+* **graphql-model-transformer:** fixed schema template options check for transformer version ([#8449](https://github.com/aws-amplify/amplify-cli/issues/8449)) ([aedcae3](https://github.com/aws-amplify/amplify-cli/commit/aedcae36f445c6e990bd94fd29d1b012e1b13787))
+* **graphql:** lambda auth label fix ([#8623](https://github.com/aws-amplify/amplify-cli/issues/8623)) ([6b4994d](https://github.com/aws-amplify/amplify-cli/commit/6b4994dd860015dd7f72b0f162314ffd580c727e))
+* **graphql:** minor api prompt fixes ([#8603](https://github.com/aws-amplify/amplify-cli/issues/8603)) ([b9aabe2](https://github.com/aws-amplify/amplify-cli/commit/b9aabe22705cc5d418e83fc8a957f2aac59e0693))
+* remove duplicate error messages ([#8651](https://github.com/aws-amplify/amplify-cli/issues/8651)) ([aad5de7](https://github.com/aws-amplify/amplify-cli/commit/aad5de7b56b9b077b6b689c5b37d51dbfd4b262d))
+* schema migrator utility as separate command ([#8720](https://github.com/aws-amplify/amplify-cli/issues/8720)) ([46e1ee6](https://github.com/aws-amplify/amplify-cli/commit/46e1ee6a49dd86bb682b182a37626bc3f2f966ea))
+
+
+### Features
+
+* **amplify-provider-awscloudformation:** change global_auth_rule to globalAuthRule for global auth ([#8674](https://github.com/aws-amplify/amplify-cli/issues/8674)) ([7a06216](https://github.com/aws-amplify/amplify-cli/commit/7a06216c0a56d9ab886ebb16b2179394fc5e76d2))
+* **amplify-provider-awscloudformation:** change sandbox mode syntax in schema ([#8592](https://github.com/aws-amplify/amplify-cli/issues/8592)) ([a3bdd44](https://github.com/aws-amplify/amplify-cli/commit/a3bdd44fddd3414a39d561510092084a1b8e6e61))
+* flag to allow destructive schema changes ([#8273](https://github.com/aws-amplify/amplify-cli/issues/8273)) ([18de856](https://github.com/aws-amplify/amplify-cli/commit/18de856fb61bf2df8f73375e4e55a58c6159a232))
+
+
+### Reverts
+
+* Revert "Lambda auth minor fixes (#8741)" (#8762) ([aa1096c](https://github.com/aws-amplify/amplify-cli/commit/aa1096ca504bdb7e6a2dca2963c546f957116f9d)), closes [#8741](https://github.com/aws-amplify/amplify-cli/issues/8741) [#8762](https://github.com/aws-amplify/amplify-cli/issues/8762)
+
+
+
+
+
+# [2.34.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.33.2...amplify-category-api@2.34.0) (2021-11-11)
+
+
+
+# 6.4.0 (2021-11-10)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** change auth directive type and fix codegen bug ([#8639](https://github.com/aws-amplify/amplify-cli/issues/8639)) ([b8d838d](https://github.com/aws-amplify/amplify-cli/commit/b8d838ddfd332c0f6fb36ef52ab76da24b5d26ca))
+* expand region support for aurora serverless ([#8577](https://github.com/aws-amplify/amplify-cli/issues/8577)) ([ad0cd2b](https://github.com/aws-amplify/amplify-cli/commit/ad0cd2b7e0644986276aa295dd424976f5c3ab68))
+* **graphql-model-transformer:** fixed schema template options check for transformer version ([#8449](https://github.com/aws-amplify/amplify-cli/issues/8449)) ([aedcae3](https://github.com/aws-amplify/amplify-cli/commit/aedcae36f445c6e990bd94fd29d1b012e1b13787))
+* **graphql:** lambda auth label fix ([#8623](https://github.com/aws-amplify/amplify-cli/issues/8623)) ([6b4994d](https://github.com/aws-amplify/amplify-cli/commit/6b4994dd860015dd7f72b0f162314ffd580c727e))
+* **graphql:** minor api prompt fixes ([#8603](https://github.com/aws-amplify/amplify-cli/issues/8603)) ([b9aabe2](https://github.com/aws-amplify/amplify-cli/commit/b9aabe22705cc5d418e83fc8a957f2aac59e0693))
+* remove duplicate error messages ([#8651](https://github.com/aws-amplify/amplify-cli/issues/8651)) ([aad5de7](https://github.com/aws-amplify/amplify-cli/commit/aad5de7b56b9b077b6b689c5b37d51dbfd4b262d))
+* schema migrator utility as separate command ([#8720](https://github.com/aws-amplify/amplify-cli/issues/8720)) ([46e1ee6](https://github.com/aws-amplify/amplify-cli/commit/46e1ee6a49dd86bb682b182a37626bc3f2f966ea))
+
+
+### Features
+
+* **amplify-provider-awscloudformation:** change global_auth_rule to globalAuthRule for global auth ([#8674](https://github.com/aws-amplify/amplify-cli/issues/8674)) ([7a06216](https://github.com/aws-amplify/amplify-cli/commit/7a06216c0a56d9ab886ebb16b2179394fc5e76d2))
+* **amplify-provider-awscloudformation:** change sandbox mode syntax in schema ([#8592](https://github.com/aws-amplify/amplify-cli/issues/8592)) ([a3bdd44](https://github.com/aws-amplify/amplify-cli/commit/a3bdd44fddd3414a39d561510092084a1b8e6e61))
+* flag to allow destructive schema changes ([#8273](https://github.com/aws-amplify/amplify-cli/issues/8273)) ([18de856](https://github.com/aws-amplify/amplify-cli/commit/18de856fb61bf2df8f73375e4e55a58c6159a232))
+
+
+### Reverts
+
+* Revert "Lambda auth minor fixes (#8741)" (#8762) ([aa1096c](https://github.com/aws-amplify/amplify-cli/commit/aa1096ca504bdb7e6a2dca2963c546f957116f9d)), closes [#8741](https://github.com/aws-amplify/amplify-cli/issues/8741) [#8762](https://github.com/aws-amplify/amplify-cli/issues/8762)
+
+
+
+
+
+## [2.33.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.33.1...amplify-category-api@2.33.2) (2021-10-13)
+
+
+### Bug Fixes
+
+* api containers on repushing does not fail ([#8416](https://github.com/aws-amplify/amplify-cli/issues/8416)) ([eb64172](https://github.com/aws-amplify/amplify-cli/commit/eb641725febba88ebb7349c0e662d4ffc6cf7e97))
+
+
+
+
+
+## [2.33.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.33.0...amplify-category-api@2.33.1) (2021-10-10)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** fixed api to reference stack name and deployment bucket ([#8145](https://github.com/aws-amplify/amplify-cli/issues/8145)) ([4c7493a](https://github.com/aws-amplify/amplify-cli/commit/4c7493ac34fa89cab0c80e5c674bbeb102891a64))
+
+
+
+
+
+# [2.33.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.32.0...amplify-category-api@2.33.0) (2021-10-06)
+
+
+### Features
+
+* Custom policies IAM Policies for Lambda and Containers ([#8068](https://github.com/aws-amplify/amplify-cli/issues/8068)) ([3e1ce0d](https://github.com/aws-amplify/amplify-cli/commit/3e1ce0de4d25ab239adcdcef778cc82f30b17a94))
+
+
+
+
+
+# [2.32.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.23...amplify-category-api@2.32.0) (2021-09-27)
+
+
+### Bug Fixes
+
+* [#8223](https://github.com/aws-amplify/amplify-cli/issues/8223), conversion to typescript ([#8245](https://github.com/aws-amplify/amplify-cli/issues/8245)) ([096e6ca](https://github.com/aws-amplify/amplify-cli/commit/096e6ca19b94aa40ef249ea98d008380395afa16))
+
+
+### Features
+
+* Flag to allow schema changes that require table replacement ([#8144](https://github.com/aws-amplify/amplify-cli/issues/8144)) ([2d4e65a](https://github.com/aws-amplify/amplify-cli/commit/2d4e65acfd034d33c6fa8ac1f5f8582e7e3bc399))
+
+
+### Reverts
+
+* Revert "feat: Flag to allow schema changes that require table replacement (#8144)" (#8268) ([422dd04](https://github.com/aws-amplify/amplify-cli/commit/422dd04425c72aa7276e086d38ce4d5f4681f9f3)), closes [#8144](https://github.com/aws-amplify/amplify-cli/issues/8144) [#8268](https://github.com/aws-amplify/amplify-cli/issues/8268)
+
+
+
+
+
+## [2.31.23](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.22...amplify-category-api@2.31.23) (2021-09-18)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.22](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.21...amplify-category-api@2.31.22) (2021-09-14)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.21](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.20...amplify-category-api@2.31.21) (2021-09-09)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.20](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.19...amplify-category-api@2.31.20) (2021-09-02)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.19](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.18...amplify-category-api@2.31.19) (2021-08-24)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.18](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.17...amplify-category-api@2.31.18) (2021-08-06)
+
+
+
+# 5.3.0 (2021-08-04)
+
+
+### Bug Fixes
+
+* **container-hosting:** ignore test cases ([#7895](https://github.com/aws-amplify/amplify-cli/issues/7895)) ([f051445](https://github.com/aws-amplify/amplify-cli/commit/f05144510311fd38f188fd2d86a9fb0c74219269))
+* **graphql-model-transformer:** model input fields transform ([#7857](https://github.com/aws-amplify/amplify-cli/issues/7857)) ([12ff663](https://github.com/aws-amplify/amplify-cli/commit/12ff663a94a4896bd9eacef3847be15b7631d8df))
+* multi-env container hosting ([#7009](https://github.com/aws-amplify/amplify-cli/issues/7009)) ([#7346](https://github.com/aws-amplify/amplify-cli/issues/7346)) ([6c33215](https://github.com/aws-amplify/amplify-cli/commit/6c33215d064029add6b93bb10cad96bb63f40101))
+
+
+
+
+
+## [2.31.17](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.16...amplify-category-api@2.31.17) (2021-07-30)
+
+
+### Bug Fixes
+
+* lambda timeout should be an integer type ([#7699](https://github.com/aws-amplify/amplify-cli/issues/7699)) ([cbacf4d](https://github.com/aws-amplify/amplify-cli/commit/cbacf4d3e497421855c09825970e025550aacfd7))
+
+
+
+
+
+## [2.31.16](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.15...amplify-category-api@2.31.16) (2021-07-27)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.15](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.14...amplify-category-api@2.31.15) (2021-07-16)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.14](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.13...amplify-category-api@2.31.14) (2021-07-12)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.13](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.12...amplify-category-api@2.31.13) (2021-06-30)
+
+
+### Bug Fixes
+
+* api resource name case sensitivity failure ([#7452](https://github.com/aws-amplify/amplify-cli/issues/7452)) ([7bd5524](https://github.com/aws-amplify/amplify-cli/commit/7bd5524703a8ac963cf4d9ef6a1fbb031e42e3c4))
+* unique api name check works when no existing apis ([#7615](https://github.com/aws-amplify/amplify-cli/issues/7615)) ([8cef10f](https://github.com/aws-amplify/amplify-cli/commit/8cef10f051bea8428f3b16095137e14346218bb3))
+
+
+
+
+
+## [2.31.12](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.11...amplify-category-api@2.31.12) (2021-06-24)
+
+
+### Bug Fixes
+
+* **graphql-transformer-common:** improve generated graphql pluralization ([#7258](https://github.com/aws-amplify/amplify-cli/issues/7258)) ([fc3ad0d](https://github.com/aws-amplify/amplify-cli/commit/fc3ad0dd5a12a7912c59ae12024f593b4cdf7f2d)), closes [#4224](https://github.com/aws-amplify/amplify-cli/issues/4224)
+* support adding REST API paths in 'add api' ([#7229](https://github.com/aws-amplify/amplify-cli/issues/7229)) ([fa9404a](https://github.com/aws-amplify/amplify-cli/commit/fa9404afd1eedd342ea6ff2033fcbd143b33748a))
+
+
+
+
+
+## [2.31.11](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.10...amplify-category-api@2.31.11) (2021-06-15)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.10](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.9...amplify-category-api@2.31.10) (2021-06-02)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.9](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.8...amplify-category-api@2.31.9) (2021-05-26)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.8](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.7...amplify-category-api@2.31.8) (2021-05-18)
+
+
+### Bug Fixes
+
+* make ECR repository name validation more strict ([#7337](https://github.com/aws-amplify/amplify-cli/issues/7337)) ([188efdd](https://github.com/aws-amplify/amplify-cli/commit/188efdde6ded25a06c6fb52e0b2abe04981b0993))
+
+
+
+
+
+## [2.31.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.6...amplify-category-api@2.31.7) (2021-05-14)
+
+
+### Bug Fixes
+
+* carry existing container secret config over ([#7224](https://github.com/aws-amplify/amplify-cli/issues/7224)) ([b2f3bf7](https://github.com/aws-amplify/amplify-cli/commit/b2f3bf7059ce3ca1e72cf6c451edd3e61699828a))
+* conditionally rebuild container apis on push ([#7175](https://github.com/aws-amplify/amplify-cli/issues/7175)) ([a27a033](https://github.com/aws-amplify/amplify-cli/commit/a27a033af0fe6a9db8becd15b713113c64e70eb3))
+* update overlapping REST path warning ([#7276](https://github.com/aws-amplify/amplify-cli/issues/7276)) ([3fc7534](https://github.com/aws-amplify/amplify-cli/commit/3fc75343ba228307080f3ef6a6cae4cf3387a007))
+
+
+
+
+
+## [2.31.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.4...amplify-category-api@2.31.6) (2021-05-03)
+
+
+
+## 4.50.1 (2021-05-03)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.4...amplify-category-api@2.31.5) (2021-05-03)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.3...amplify-category-api@2.31.4) (2021-04-27)
+
+
+### Bug Fixes
+
+* consolidate REST API IAM policies ([#6904](https://github.com/aws-amplify/amplify-cli/issues/6904)) (ref [#2084](https://github.com/aws-amplify/amplify-cli/issues/2084)) ([5cfff17](https://github.com/aws-amplify/amplify-cli/commit/5cfff173d57ec9ab68984faf2d0f6474eccdcaae))
+* handle errors and provide better error message when adding data source ([#7117](https://github.com/aws-amplify/amplify-cli/issues/7117)) (ref [#4384](https://github.com/aws-amplify/amplify-cli/issues/4384)) ([888829b](https://github.com/aws-amplify/amplify-cli/commit/888829ba6f53209ca12d215ed510d5e201d025ee))
+
+
+
+
+
+## [2.31.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.2...amplify-category-api@2.31.3) (2021-04-19)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.31.1...amplify-category-api@2.31.2) (2021-04-14)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.31.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.30.0...amplify-category-api@2.31.1) (2021-04-09)
+
+
+### Bug Fixes
+
+* **cli:** use more inclusive language ([#6919](https://github.com/aws-amplify/amplify-cli/issues/6919)) ([bb70464](https://github.com/aws-amplify/amplify-cli/commit/bb70464d6c24fa931c0eb80d234a496d936913f5))
+
+
+
+
+
+# [2.30.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.29.5...amplify-category-api@2.30.0) (2021-03-23)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** mantain ff in iam api policy ([#6723](https://github.com/aws-amplify/amplify-cli/issues/6723)) ([51e5e1b](https://github.com/aws-amplify/amplify-cli/commit/51e5e1b53514a05788dd824a48991c0db0b9705d)), closes [#6675](https://github.com/aws-amplify/amplify-cli/issues/6675)
+
+
+### Features
+
+* allows adding graphql datasource with an empty graphql schema file ([#4464](https://github.com/aws-amplify/amplify-cli/issues/4464)) ([2b71a2d](https://github.com/aws-amplify/amplify-cli/commit/2b71a2df31585ad06674417b7003dfb70d5b785d))
+
+
+
+
+
+## [2.29.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.29.4...amplify-category-api@2.29.5) (2021-03-11)
+
+
+### Reverts
+
+* Revert "docs: add readme to vtl resolvers directory (#6718)" (#6845) ([1b67327](https://github.com/aws-amplify/amplify-cli/commit/1b67327f4885c611708c73256094456ab95b67ef)), closes [#6718](https://github.com/aws-amplify/amplify-cli/issues/6718) [#6845](https://github.com/aws-amplify/amplify-cli/issues/6845)
+
+
+
+
+
+## [2.29.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.29.3...amplify-category-api@2.29.4) (2021-03-05)
+
+
+### Bug Fixes
+
+* wording: Enable, instead of Configure, conflict detection ([#6708](https://github.com/aws-amplify/amplify-cli/issues/6708)) ([dac6ae9](https://github.com/aws-amplify/amplify-cli/commit/dac6ae94af47dd01da25ea4f61efd5442cb4c06b))
+
+
+
+
+
+## [2.29.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.29.2...amplify-category-api@2.29.3) (2021-02-26)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.29.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.29.1...amplify-category-api@2.29.2) (2021-02-24)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.29.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.29.0...amplify-category-api@2.29.1) (2021-02-17)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+# [2.29.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.28.3...amplify-category-api@2.29.0) (2021-02-11)
+
+
+### Bug Fixes
+
+* sets policy resource name in api update flow ([328abac](https://github.com/aws-amplify/amplify-cli/commit/328abacd81d03dbae8b0c0246536c465a7954c1e))
+
+
+### Features
+
+* dont open urls when CLI is running in CI ([#6503](https://github.com/aws-amplify/amplify-cli/issues/6503)) ([27546a7](https://github.com/aws-amplify/amplify-cli/commit/27546a78159ea95c636dbbd094fe6a4f7fb8f8f4)), closes [#5973](https://github.com/aws-amplify/amplify-cli/issues/5973)
+
+
+
+
+
+## [2.28.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.28.2...amplify-category-api@2.28.3) (2021-02-10)
+
+
+### Bug Fixes
+
+* fix appsync permission assignment from functions ([#5342](https://github.com/aws-amplify/amplify-cli/issues/5342)) ([b2e2dd0](https://github.com/aws-amplify/amplify-cli/commit/b2e2dd0071c1a451ba032cf7f8cfe7cf6381a96e))
+
+
+
+
+
+## [2.28.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.28.1...amplify-category-api@2.28.2) (2021-01-08)
+
+
+### Bug Fixes
+
+* remove process on next and await ([#6239](https://github.com/aws-amplify/amplify-cli/issues/6239)) ([59d4a0e](https://github.com/aws-amplify/amplify-cli/commit/59d4a0eb318d2b3ad97be34bda9dee756cf82d74))
+
+
+
+
+
+## [2.28.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.28.0...amplify-category-api@2.28.1) (2020-12-16)
+
+
+### Bug Fixes
+
+* [#6123](https://github.com/aws-amplify/amplify-cli/issues/6123) - add missing amplify-cli-core dependencies to packages ([#6124](https://github.com/aws-amplify/amplify-cli/issues/6124)) ([e6519f2](https://github.com/aws-amplify/amplify-cli/commit/e6519f2dd81d2983b797f226d723a73a25967d25))
+
+
+
+
+
+# [2.28.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.27.1...amplify-category-api@2.28.0) (2020-12-11)
+
+
+
+# 4.39.0 (2020-12-10)
+
+
+### Bug Fixes
+
+* containers - don't wait for pipeline on hosting ([#6137](https://github.com/aws-amplify/amplify-cli/issues/6137)) ([c75d694](https://github.com/aws-amplify/amplify-cli/commit/c75d69436104cb974684b0ed48c743294f70d556))
+* only ignore root src folder ([#6136](https://github.com/aws-amplify/amplify-cli/issues/6136)) ([6289f5d](https://github.com/aws-amplify/amplify-cli/commit/6289f5ddc803eecf4d048075d769153c978523ac))
+
+
+### Features
+
+* container-based deployments([#5727](https://github.com/aws-amplify/amplify-cli/issues/5727)) ([fad6377](https://github.com/aws-amplify/amplify-cli/commit/fad6377bd384862ca4429cb1a83eee90efd62b58))
+
+
+
+
+
+## [2.27.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.27.0...amplify-category-api@2.27.1) (2020-12-07)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+# [2.27.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.26.2...amplify-category-api@2.27.0) (2020-11-30)
+
+
+### Features
+
+* pre-deploy pull, new login mechanism and pkg cli updates ([#5941](https://github.com/aws-amplify/amplify-cli/issues/5941)) ([7274251](https://github.com/aws-amplify/amplify-cli/commit/7274251faadc1035acce5f44699b172e10e2e67d))
+
+
+
+
+
+## [2.26.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.26.1...amplify-category-api@2.26.2) (2020-11-24)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.26.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.4...amplify-category-api@2.26.1) (2020-11-22)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+# [2.26.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.11...amplify-category-api@2.26.0) (2020-11-22)
+
+
+### Bug Fixes
+
+* add support for mobile hub migrated resources ([#5407](https://github.com/aws-amplify/amplify-cli/issues/5407)) ([5dfe287](https://github.com/aws-amplify/amplify-cli/commit/5dfe2872c153047ebdc56bc4f671fd57c12379d9))
+* added exit code on remove ([#5427](https://github.com/aws-amplify/amplify-cli/issues/5427)) ([33132f7](https://github.com/aws-amplify/amplify-cli/commit/33132f764b290cafd345720409a5db8ea6088069))
+* checking undefined auth config ([#5313](https://github.com/aws-amplify/amplify-cli/issues/5313)) ([42810c9](https://github.com/aws-amplify/amplify-cli/commit/42810c98b0015f12119f3387749889a6bf6abe9b))
+* data inconsitency ([#5344](https://github.com/aws-amplify/amplify-cli/issues/5344)) ([bfe1903](https://github.com/aws-amplify/amplify-cli/commit/bfe19038b5b676056f45d7ffcc4c2460057936d8))
+* incorrect type on graphql boilerplate schema ([#4070](https://github.com/aws-amplify/amplify-cli/issues/4070)) ([d96171a](https://github.com/aws-amplify/amplify-cli/commit/d96171a7461ecbb610c3cbcbcb05cdf5492dc8e5))
+* populate API_KEY env var when present ([#4923](https://github.com/aws-amplify/amplify-cli/issues/4923)) ([81231f9](https://github.com/aws-amplify/amplify-cli/commit/81231f98305dd9e37bb64eb30a9c7307bb471ad9))
+* refactor mobile hub migration checks ([#5632](https://github.com/aws-amplify/amplify-cli/issues/5632)) ([b796eb8](https://github.com/aws-amplify/amplify-cli/commit/b796eb8303bb903f5f531506254441a63eba2962))
+* remove mutableParametersState from stored function-params ([#4897](https://github.com/aws-amplify/amplify-cli/issues/4897)) ([c608166](https://github.com/aws-amplify/amplify-cli/commit/c6081668798e94165ede40bb06439075946e3e86))
+* removes duplicate auth types ([#5272](https://github.com/aws-amplify/amplify-cli/issues/5272)) ([8747911](https://github.com/aws-amplify/amplify-cli/commit/8747911ff5a515e86971af7d5ad15681c64eb532))
+* return undefined for empty conflict resolution ([#4982](https://github.com/aws-amplify/amplify-cli/issues/4982)) ([7c5bf1a](https://github.com/aws-amplify/amplify-cli/commit/7c5bf1a36078a345d80ecbf2cea3a067ae1137e1)), closes [#4965](https://github.com/aws-amplify/amplify-cli/issues/4965)
+* sub * for path parms in CFN policy ([#5092](https://github.com/aws-amplify/amplify-cli/issues/5092)) ([2942688](https://github.com/aws-amplify/amplify-cli/commit/29426884968314122b65a24b2f9658a618bf9120))
+* **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+* **amplify-category-api:** added check to read schema in schema dir ([#3871](https://github.com/aws-amplify/amplify-cli/issues/3871)) ([21bd229](https://github.com/aws-amplify/amplify-cli/commit/21bd229b5c15e4ce837da604ec73e7f40076170f)), closes [fixes#3082](https://github.com/fixes/issues/3082)
+* **amplify-category-api:** edit auth workflow if cognito is not used ([#3232](https://github.com/aws-amplify/amplify-cli/issues/3232)) ([f9473cf](https://github.com/aws-amplify/amplify-cli/commit/f9473cf50bbcf43a701f1f44b6f4d451dc2be237)), closes [#2967](https://github.com/aws-amplify/amplify-cli/issues/2967)
+* **amplify-category-api:** Fix [#2498](https://github.com/aws-amplify/amplify-cli/issues/2498) ([#2503](https://github.com/aws-amplify/amplify-cli/issues/2503)) ([35aab06](https://github.com/aws-amplify/amplify-cli/commit/35aab06c1ac9d3081f4f2e06ae18c14ef212aa43))
+* **amplify-category-api:** fix api add-graphql-datasource command ([#2320](https://github.com/aws-amplify/amplify-cli/issues/2320)) ([a9c829d](https://github.com/aws-amplify/amplify-cli/commit/a9c829d79e91246d2bb9a707ccfe886502ceebe2))
+* **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+* **amplify-category-api:** include userpool id in parameter.json ([#2238](https://github.com/aws-amplify/amplify-cli/issues/2238)) ([143b847](https://github.com/aws-amplify/amplify-cli/commit/143b84739d754f09f29f73678fd5a60674fd9304))
+* **amplify-category-api:** plumb api id to resources that require it ([#3464](https://github.com/aws-amplify/amplify-cli/issues/3464)) ([2b2d52f](https://github.com/aws-amplify/amplify-cli/commit/2b2d52f05edc1190953965ca0f3ecd880ec66a63)), closes [#3431](https://github.com/aws-amplify/amplify-cli/issues/3431) [#3386](https://github.com/aws-amplify/amplify-cli/issues/3386)
+* **amplify-category-api:** safeguard prompt with empty options ([#2430](https://github.com/aws-amplify/amplify-cli/issues/2430)) ([cb8f6dd](https://github.com/aws-amplify/amplify-cli/commit/cb8f6dddefb7f7e7f8159988563fc076f470ee79)), closes [#2423](https://github.com/aws-amplify/amplify-cli/issues/2423)
+* **amplify-category-api:** toggle datastore in update ([#4276](https://github.com/aws-amplify/amplify-cli/issues/4276)) ([4f02a62](https://github.com/aws-amplify/amplify-cli/commit/4f02a62f5c8929cabe914e2e38fb28dc535d2d61)), closes [#4058](https://github.com/aws-amplify/amplify-cli/issues/4058)
+* **amplify-category-api:** use standard json read ([#2581](https://github.com/aws-amplify/amplify-cli/issues/2581)) ([3adc395](https://github.com/aws-amplify/amplify-cli/commit/3adc395a5e4ccf3673735f8091db63923a46c501))
+* **amplify-provider-awscloudformation:** apigw unauth access ([#1906](https://github.com/aws-amplify/amplify-cli/issues/1906)) ([bcd0d02](https://github.com/aws-amplify/amplify-cli/commit/bcd0d02a229d3dab2e5babc40b68ac9090aa5f15))
+* **cli:** add console command in the help message ([#2494](https://github.com/aws-amplify/amplify-cli/issues/2494)) ([cf0eddd](https://github.com/aws-amplify/amplify-cli/commit/cf0eddd1ba27b1126b0745cc068f205b2c2c8343)), closes [#1607](https://github.com/aws-amplify/amplify-cli/issues/1607)
+* **cli:** deleting the amplify app on delete ([#3568](https://github.com/aws-amplify/amplify-cli/issues/3568)) ([f39bbcb](https://github.com/aws-amplify/amplify-cli/commit/f39bbcb715875eeeb612bcbc40b275b33f85eaf6)), closes [#3239](https://github.com/aws-amplify/amplify-cli/issues/3239)
+* **cli:** remove unnecessary stack trace log when adding services ([#4610](https://github.com/aws-amplify/amplify-cli/issues/4610)) ([56efb32](https://github.com/aws-amplify/amplify-cli/commit/56efb32b79c47839cb9506a9300d40a01875a9fc))
+* **graphql-auth-transformer:** add a time delay when creating apiKey ([#4493](https://github.com/aws-amplify/amplify-cli/issues/4493)) ([3f544e7](https://github.com/aws-amplify/amplify-cli/commit/3f544e7f421f66f3d4e920cdd89ddb926c412241))
+* [#429](https://github.com/aws-amplify/amplify-cli/issues/429) - Editor hanging bug ([#2086](https://github.com/aws-amplify/amplify-cli/issues/2086)) ([6767445](https://github.com/aws-amplify/amplify-cli/commit/676744549f903fa3a4804d814eb325301ed462ba))
+* change default length for api key back to 7 days ([#2507](https://github.com/aws-amplify/amplify-cli/issues/2507)) ([6a7e61f](https://github.com/aws-amplify/amplify-cli/commit/6a7e61fc7315f5e732ad7b36b5c0ae88ea36b628))
+* move test package dependencies to devDependencies ([#2034](https://github.com/aws-amplify/amplify-cli/issues/2034)) ([f5623d0](https://github.com/aws-amplify/amplify-cli/commit/f5623d04a43e685901f4f1cd96e2a227164c71ee))
+* update graphql schema to match docs ([#3652](https://github.com/aws-amplify/amplify-cli/issues/3652)) ([dc3c866](https://github.com/aws-amplify/amplify-cli/commit/dc3c8661066be6bfdbb404b81a73bfed1fcf0095)), closes [#3513](https://github.com/aws-amplify/amplify-cli/issues/3513)
+* validatePathName_validPath matcher ([#4559](https://github.com/aws-amplify/amplify-cli/issues/4559)) ([94c00c4](https://github.com/aws-amplify/amplify-cli/commit/94c00c43e912e94a03ab10acbd93ad3dc5d2c18c))
+* **graphql-elasticsearch-transformer:** fix duplicate records in es lambda ([#3712](https://github.com/aws-amplify/amplify-cli/issues/3712)) ([dd9f7e0](https://github.com/aws-amplify/amplify-cli/commit/dd9f7e0031a0dc68a9027de02f60bbe69d315c3d)), closes [#3602](https://github.com/aws-amplify/amplify-cli/issues/3602) [#3705](https://github.com/aws-amplify/amplify-cli/issues/3705)
+
+
+### Features
+
+* support importing of auth resources ([#5591](https://github.com/aws-amplify/amplify-cli/issues/5591)) ([7903246](https://github.com/aws-amplify/amplify-cli/commit/790324680544fe18481f91390001f9f07a144203))
+* **amplify-category-api:** add rds support for new regions ([#5360](https://github.com/aws-amplify/amplify-cli/issues/5360)) ([4f65ed1](https://github.com/aws-amplify/amplify-cli/commit/4f65ed1ba6ab76f7d018b998525b73aa1e47fbcd)), closes [#4739](https://github.com/aws-amplify/amplify-cli/issues/4739)
+* allow creation of REST API endpoint at root path (/) ([#4649](https://github.com/aws-amplify/amplify-cli/issues/4649)) ([49d8121](https://github.com/aws-amplify/amplify-cli/commit/49d8121ade1f06bf23d511523b88e9dd6c289073)), closes [#3868](https://github.com/aws-amplify/amplify-cli/issues/3868) [#4834](https://github.com/aws-amplify/amplify-cli/issues/4834)
+* headless mode for API category ([#4834](https://github.com/aws-amplify/amplify-cli/issues/4834)) ([c2e09d7](https://github.com/aws-amplify/amplify-cli/commit/c2e09d73fd1bb461eeace8f4a7addd70a63047ad))
+* Lambda layers ([#4697](https://github.com/aws-amplify/amplify-cli/issues/4697)) ([4e97400](https://github.com/aws-amplify/amplify-cli/commit/4e974007d95c894ab4108a2dff8d5996e7e3ce25))
+* **amplify-category-api:** allow minified CF stack templates ([#3520](https://github.com/aws-amplify/amplify-cli/issues/3520)) ([6da2a63](https://github.com/aws-amplify/amplify-cli/commit/6da2a634548fdf48deb4b1144c67d1e1515abb80)), closes [#2914](https://github.com/aws-amplify/amplify-cli/issues/2914)
+* **amplify-category-api:** support path parameters in REST APIs ([#3394](https://github.com/aws-amplify/amplify-cli/issues/3394)) ([fa7d07e](https://github.com/aws-amplify/amplify-cli/commit/fa7d07e1f6f54185a37851ea9d4c840b092501cc))
+* **amplify-category-function:** refactor to support runtime and template plugins ([#3517](https://github.com/aws-amplify/amplify-cli/issues/3517)) ([607ae21](https://github.com/aws-amplify/amplify-cli/commit/607ae21287941805f44ea8a9b78dd12d16d71f85))
+* **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+* **cli:** usage measurement ([#3641](https://github.com/aws-amplify/amplify-cli/issues/3641)) ([a755863](https://github.com/aws-amplify/amplify-cli/commit/a7558637fbb791dc22e0a91ae16f1b96fe4e99df))
+* adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c1927da10f8c54f38a523021187361131c))
+* conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+* Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+* implement multi-auth functionality ([#1916](https://github.com/aws-amplify/amplify-cli/issues/1916)) ([b99f58e](https://github.com/aws-amplify/amplify-cli/commit/b99f58e4a2b85cbe9f430838554ae3c277440132))
+* sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe8925a4e73358b03ba927267a2df328b78))
+* uplevel enabling of datastore and update of auth configs to top ([#3495](https://github.com/aws-amplify/amplify-cli/issues/3495)) ([f406bb2](https://github.com/aws-amplify/amplify-cli/commit/f406bb29957c98caf427a3cb46e2126f6dcf212f))
+* User Pool Groups, Admin Auth Support, Custom Group Role Policies ([#2443](https://github.com/aws-amplify/amplify-cli/issues/2443)) ([09aecfd](https://github.com/aws-amplify/amplify-cli/commit/09aecfd0cb3dae2c17d1c512946cc733c4fe3d4c))
+* **cli:** new plugin platform ([#2254](https://github.com/aws-amplify/amplify-cli/issues/2254)) ([7ec29dd](https://github.com/aws-amplify/amplify-cli/commit/7ec29dd4f2da8c90727b36469eca646d289877b6))
+
+
+### Reverts
+
+* Revert problematic PRs (#4803) ([f21a0f4](https://github.com/aws-amplify/amplify-cli/commit/f21a0f449a23c0c80a6f3280eef76bcbf3e9cb7c)), closes [#4803](https://github.com/aws-amplify/amplify-cli/issues/4803) [#4796](https://github.com/aws-amplify/amplify-cli/issues/4796) [#4576](https://github.com/aws-amplify/amplify-cli/issues/4576) [#4575](https://github.com/aws-amplify/amplify-cli/issues/4575) [#4610](https://github.com/aws-amplify/amplify-cli/issues/4610)
+
+
+
+
+
+## [2.25.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.4...amplify-category-api@2.25.7) (2020-11-20)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.25.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.4...amplify-category-api@2.25.6) (2020-11-20)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.25.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.4...amplify-category-api@2.25.5) (2020-11-19)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.25.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.3...amplify-category-api@2.25.4) (2020-11-08)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.25.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.2...amplify-category-api@2.25.3) (2020-10-30)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.25.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.1...amplify-category-api@2.25.2) (2020-10-27)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.25.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.25.0...amplify-category-api@2.25.1) (2020-10-22)
+
+
+### Bug Fixes
+
+* refactor mobile hub migration checks ([#5632](https://github.com/aws-amplify/amplify-cli/issues/5632)) ([b796eb8](https://github.com/aws-amplify/amplify-cli/commit/b796eb8303bb903f5f531506254441a63eba2962))
+
+
+
+
+
+# [2.25.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.24.1...amplify-category-api@2.25.0) (2020-10-17)
+
+
+### Features
+
+* support importing of auth resources ([#5591](https://github.com/aws-amplify/amplify-cli/issues/5591)) ([7903246](https://github.com/aws-amplify/amplify-cli/commit/790324680544fe18481f91390001f9f07a144203))
+
+
+
+
+
+## [2.24.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.24.0...amplify-category-api@2.24.1) (2020-10-07)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+# [2.24.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.9...amplify-category-api@2.24.0) (2020-10-01)
+
+
+### Bug Fixes
+
+* add support for mobile hub migrated resources ([#5407](https://github.com/aws-amplify/amplify-cli/issues/5407)) ([5dfe287](https://github.com/aws-amplify/amplify-cli/commit/5dfe2872c153047ebdc56bc4f671fd57c12379d9))
+* added exit code on remove ([#5427](https://github.com/aws-amplify/amplify-cli/issues/5427)) ([33132f7](https://github.com/aws-amplify/amplify-cli/commit/33132f764b290cafd345720409a5db8ea6088069))
+
+
+### Features
+
+* **amplify-category-api:** add rds support for new regions ([#5360](https://github.com/aws-amplify/amplify-cli/issues/5360)) ([4f65ed1](https://github.com/aws-amplify/amplify-cli/commit/4f65ed1ba6ab76f7d018b998525b73aa1e47fbcd)), closes [#4739](https://github.com/aws-amplify/amplify-cli/issues/4739)
+
+
+
+
+
+## [2.23.9](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.8...amplify-category-api@2.23.9) (2020-09-25)
+
+
+### Bug Fixes
+
+* data inconsitency ([#5344](https://github.com/aws-amplify/amplify-cli/issues/5344)) ([bfe1903](https://github.com/aws-amplify/amplify-cli/commit/bfe19038b5b676056f45d7ffcc4c2460057936d8))
+
+
+
+
+
+## [2.23.8](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.7...amplify-category-api@2.23.8) (2020-09-16)
+
+
+### Bug Fixes
+
+* checking undefined auth config ([#5313](https://github.com/aws-amplify/amplify-cli/issues/5313)) ([42810c9](https://github.com/aws-amplify/amplify-cli/commit/42810c98b0015f12119f3387749889a6bf6abe9b))
+* removes duplicate auth types ([#5272](https://github.com/aws-amplify/amplify-cli/issues/5272)) ([8747911](https://github.com/aws-amplify/amplify-cli/commit/8747911ff5a515e86971af7d5ad15681c64eb532))
+
+
+
+
+
+## [2.23.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.6...amplify-category-api@2.23.7) (2020-09-09)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.23.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.5...amplify-category-api@2.23.6) (2020-09-03)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.23.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.4...amplify-category-api@2.23.5) (2020-09-03)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.23.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.3...amplify-category-api@2.23.4) (2020-08-31)
+
+
+### Bug Fixes
+
+* sub * for path parms in CFN policy ([#5092](https://github.com/aws-amplify/amplify-cli/issues/5092)) ([2942688](https://github.com/aws-amplify/amplify-cli/commit/29426884968314122b65a24b2f9658a618bf9120))
+
+
+
+
+
+## [2.23.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.2...amplify-category-api@2.23.3) (2020-08-20)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.23.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.1...amplify-category-api@2.23.2) (2020-08-14)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.23.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.23.0...amplify-category-api@2.23.1) (2020-08-11)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+# [2.23.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.22.0...amplify-category-api@2.23.0) (2020-08-06)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** added check to read schema in schema dir ([#3871](https://github.com/aws-amplify/amplify-cli/issues/3871)) ([21bd229](https://github.com/aws-amplify/amplify-cli/commit/21bd229b5c15e4ce837da604ec73e7f40076170f)), closes [fixes#3082](https://github.com/fixes/issues/3082)
+* return undefined for empty conflict resolution ([#4982](https://github.com/aws-amplify/amplify-cli/issues/4982)) ([7c5bf1a](https://github.com/aws-amplify/amplify-cli/commit/7c5bf1a36078a345d80ecbf2cea3a067ae1137e1)), closes [#4965](https://github.com/aws-amplify/amplify-cli/issues/4965)
+
+
+### Features
+
+* allow creation of REST API endpoint at root path (/) ([#4649](https://github.com/aws-amplify/amplify-cli/issues/4649)) ([49d8121](https://github.com/aws-amplify/amplify-cli/commit/49d8121ade1f06bf23d511523b88e9dd6c289073)), closes [#3868](https://github.com/aws-amplify/amplify-cli/issues/3868) [#4834](https://github.com/aws-amplify/amplify-cli/issues/4834)
+
+
+
+
+
+# [2.22.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.20.5...amplify-category-api@2.22.0) (2020-07-29)
+
+
+### Bug Fixes
+
+* populate API_KEY env var when present ([#4923](https://github.com/aws-amplify/amplify-cli/issues/4923)) ([81231f9](https://github.com/aws-amplify/amplify-cli/commit/81231f98305dd9e37bb64eb30a9c7307bb471ad9))
+* remove mutableParametersState from stored function-params ([#4897](https://github.com/aws-amplify/amplify-cli/issues/4897)) ([c608166](https://github.com/aws-amplify/amplify-cli/commit/c6081668798e94165ede40bb06439075946e3e86))
+
+
+### Features
+
+* headless mode for API category ([#4834](https://github.com/aws-amplify/amplify-cli/issues/4834)) ([c2e09d7](https://github.com/aws-amplify/amplify-cli/commit/c2e09d73fd1bb461eeace8f4a7addd70a63047ad))
+
+
+
+
+
+# [2.21.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.20.5...amplify-category-api@2.21.0) (2020-07-23)
+
+
+### Bug Fixes
+
+* remove mutableParametersState from stored function-params ([#4897](https://github.com/aws-amplify/amplify-cli/issues/4897)) ([6e379fa](https://github.com/aws-amplify/amplify-cli/commit/6e379fabd9f5ea2316ce91f03c3e7cb3aa39fe08))
+
+
+### Features
+
+* headless mode for API category ([#4834](https://github.com/aws-amplify/amplify-cli/issues/4834)) ([b729266](https://github.com/aws-amplify/amplify-cli/commit/b729266b9bb519738ef88125784d72ac428f47e1))
+
+
+
+
+
+## [2.20.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.20.4...amplify-category-api@2.20.5) (2020-07-18)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.20.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.20.3...amplify-category-api@2.20.4) (2020-07-15)
+
+
+### Bug Fixes
+
+* **graphql-auth-transformer:** add a time delay when creating apiKey ([#4493](https://github.com/aws-amplify/amplify-cli/issues/4493)) ([1d56b40](https://github.com/aws-amplify/amplify-cli/commit/1d56b40d673b257e07905d9bc1830e8f9c8495a1))
+
+
+
+
+
+## [2.20.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.20.2...amplify-category-api@2.20.3) (2020-07-14)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.20.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.20.1...amplify-category-api@2.20.2) (2020-07-11)
+
+
+### Bug Fixes
+
+* **cli:** remove unnecessary stack trace log when adding services ([#4610](https://github.com/aws-amplify/amplify-cli/issues/4610)) ([5bee574](https://github.com/aws-amplify/amplify-cli/commit/5bee574bbcd956c032e7714b0813aedd7914a6cb))
+
+
+### Reverts
+
+* Revert problematic PRs (#4803) ([7f38d81](https://github.com/aws-amplify/amplify-cli/commit/7f38d81ef2f890c25d39b02407c5255c8760c511)), closes [#4803](https://github.com/aws-amplify/amplify-cli/issues/4803) [#4796](https://github.com/aws-amplify/amplify-cli/issues/4796) [#4576](https://github.com/aws-amplify/amplify-cli/issues/4576) [#4575](https://github.com/aws-amplify/amplify-cli/issues/4575) [#4610](https://github.com/aws-amplify/amplify-cli/issues/4610)
+
+
+
+
+
+## [2.20.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.20.0...amplify-category-api@2.20.1) (2020-07-09)
+
+
+### Bug Fixes
+
+* validatePathName_validPath matcher ([#4559](https://github.com/aws-amplify/amplify-cli/issues/4559)) ([3cf5f91](https://github.com/aws-amplify/amplify-cli/commit/3cf5f914024e55904da0f782ea71bd62bfca40e3))
+
+
+
+
+
+# [2.20.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.8...amplify-category-api@2.20.0) (2020-07-07)
+
+
+### Features
+
+* **cli:** usage measurement ([#3641](https://github.com/aws-amplify/amplify-cli/issues/3641)) ([30a7fe7](https://github.com/aws-amplify/amplify-cli/commit/30a7fe70f5838a766631befcc720a721e801bc5f))
+* Lambda layers ([#4697](https://github.com/aws-amplify/amplify-cli/issues/4697)) ([c55b2e0](https://github.com/aws-amplify/amplify-cli/commit/c55b2e0c3377127aaf887591d7bc20d7240ef11d))
+
+
+
+
+
+## [2.19.8](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.7...amplify-category-api@2.19.8) (2020-06-25)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.19.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.6...amplify-category-api@2.19.7) (2020-06-18)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.19.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.5...amplify-category-api@2.19.6) (2020-06-11)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.19.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.4...amplify-category-api@2.19.5) (2020-06-10)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.19.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.3...amplify-category-api@2.19.4) (2020-06-02)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.19.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.2...amplify-category-api@2.19.3) (2020-05-26)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** toggle datastore in update ([#4276](https://github.com/aws-amplify/amplify-cli/issues/4276)) ([c522f29](https://github.com/aws-amplify/amplify-cli/commit/c522f295304410aeb1d6f60aaba9b466d3304ee1)), closes [#4058](https://github.com/aws-amplify/amplify-cli/issues/4058)
+
+
+
+
+
+## [2.19.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.1...amplify-category-api@2.19.2) (2020-05-15)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.19.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.19.0...amplify-category-api@2.19.1) (2020-05-08)
+
+
+### Bug Fixes
+
+* incorrect type on graphql boilerplate schema ([#4070](https://github.com/aws-amplify/amplify-cli/issues/4070)) ([d96171a](https://github.com/aws-amplify/amplify-cli/commit/d96171a7461ecbb610c3cbcbcb05cdf5492dc8e5))
+
+
+
+
+
+# [2.19.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.18.0...amplify-category-api@2.19.0) (2020-04-23)
+
+
+### Features
+
+* **amplify-category-api:** allow minified CF stack templates ([#3520](https://github.com/aws-amplify/amplify-cli/issues/3520)) ([6da2a63](https://github.com/aws-amplify/amplify-cli/commit/6da2a634548fdf48deb4b1144c67d1e1515abb80)), closes [#2914](https://github.com/aws-amplify/amplify-cli/issues/2914)
+
+
+
+
+
+# [2.18.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.17.1...amplify-category-api@2.18.0) (2020-04-06)
+
+
+### Features
+
+* uplevel enabling of datastore and update of auth configs to top ([#3495](https://github.com/aws-amplify/amplify-cli/issues/3495)) ([f406bb2](https://github.com/aws-amplify/amplify-cli/commit/f406bb29957c98caf427a3cb46e2126f6dcf212f))
+
+
+
+
+
+## [2.17.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.17.0...amplify-category-api@2.17.1) (2020-03-26)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+# [2.17.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.16.1...amplify-category-api@2.17.0) (2020-03-22)
+
+
+### Bug Fixes
+
+* **cli:** deleting the amplify app on delete ([#3568](https://github.com/aws-amplify/amplify-cli/issues/3568)) ([f39bbcb](https://github.com/aws-amplify/amplify-cli/commit/f39bbcb715875eeeb612bcbc40b275b33f85eaf6)), closes [#3239](https://github.com/aws-amplify/amplify-cli/issues/3239)
+* update graphql schema to match docs ([#3652](https://github.com/aws-amplify/amplify-cli/issues/3652)) ([dc3c866](https://github.com/aws-amplify/amplify-cli/commit/dc3c8661066be6bfdbb404b81a73bfed1fcf0095)), closes [#3513](https://github.com/aws-amplify/amplify-cli/issues/3513)
+* **graphql-elasticsearch-transformer:** fix duplicate records in es lambda ([#3712](https://github.com/aws-amplify/amplify-cli/issues/3712)) ([dd9f7e0](https://github.com/aws-amplify/amplify-cli/commit/dd9f7e0031a0dc68a9027de02f60bbe69d315c3d)), closes [#3602](https://github.com/aws-amplify/amplify-cli/issues/3602) [#3705](https://github.com/aws-amplify/amplify-cli/issues/3705)
+
+
+### Features
+
+* **amplify-category-function:** refactor to support runtime and template plugins ([#3517](https://github.com/aws-amplify/amplify-cli/issues/3517)) ([607ae21](https://github.com/aws-amplify/amplify-cli/commit/607ae21287941805f44ea8a9b78dd12d16d71f85))
+
+
+
+
+
+## [2.16.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.16.0...amplify-category-api@2.16.1) (2020-03-10)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+# [2.16.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.13.3...amplify-category-api@2.16.0) (2020-03-07)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** plumb api id to resources that require it ([#3464](https://github.com/aws-amplify/amplify-cli/issues/3464)) ([2b2d52f](https://github.com/aws-amplify/amplify-cli/commit/2b2d52f05edc1190953965ca0f3ecd880ec66a63)), closes [#3431](https://github.com/aws-amplify/amplify-cli/issues/3431) [#3386](https://github.com/aws-amplify/amplify-cli/issues/3386)
+
+
+### Features
+
+* **amplify-category-api:** support path parameters in REST APIs ([#3394](https://github.com/aws-amplify/amplify-cli/issues/3394)) ([fa7d07e](https://github.com/aws-amplify/amplify-cli/commit/fa7d07e1f6f54185a37851ea9d4c840b092501cc))
+
+
+
+
+
+## [2.14.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.13.5-beta.0...amplify-category-api@2.14.1) (2020-03-05)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.13.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.13.2...amplify-category-api@2.13.3) (2020-02-13)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.13.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.13.1...amplify-category-api@2.13.2) (2020-02-07)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+
+
+## [2.13.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@2.13.0...amplify-category-api@2.13.1) (2020-01-24)
+
+
+### Bug Fixes
+
+* **amplify-category-api:** edit auth workflow if cognito is not used ([#3232](https://github.com/aws-amplify/amplify-cli/issues/3232)) ([f9473cf](https://github.com/aws-amplify/amplify-cli/commit/f9473cf50bbcf43a701f1f44b6f4d451dc2be237)), closes [#2967](https://github.com/aws-amplify/amplify-cli/issues/2967)
+
+
+
+
+
+# [2.13.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.13.0) (2020-01-23)
+
+### Bug Fixes
+
+- **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.12.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.12.0) (2020-01-09)
+
+### Bug Fixes
+
+- **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.11.0) (2019-12-31)
+
+### Bug Fixes
+
+- **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.10.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.10.0) (2019-12-28)
+
+### Bug Fixes
+
+- **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.9.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.9.0) (2019-12-26)
+
+### Bug Fixes
+
+- **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.8.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.8.0) (2019-12-25)
+
+### Bug Fixes
+
+- **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.7.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.7.0) (2019-12-20)
+
+### Bug Fixes
+
+- **amplify-category-api:** add config check in writeResolverConfig ([bed4929](https://github.com/aws-amplify/amplify-cli/commit/bed49295c22f372511abb94f7227ba686cccf214))
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.6.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.6.0) (2019-12-10)
+
+### Bug Fixes
+
+- **amplify-category-api:** fix conflict resolution learn more ([#2954](https://github.com/aws-amplify/amplify-cli/issues/2954)) ([5b0825a](https://github.com/aws-amplify/amplify-cli/commit/5b0825a44ad0b64180eb5cc373944ef82829eb06))
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.4.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.4.0) (2019-12-03)
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.3.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.3.0) (2019-12-01)
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.2.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.2.0) (2019-11-27)
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [2.1.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.30.0...amplify-category-api@2.1.0) (2019-11-27)
+
+### Features
+
+- conditions update ([#2789](https://github.com/aws-amplify/amplify-cli/issues/2789)) ([3fae391](https://github.com/aws-amplify/amplify-cli/commit/3fae391340d5fd151e1c43286c90142b5ab0eab0))
+- Delete all ([#2615](https://github.com/aws-amplify/amplify-cli/issues/2615)) ([5467679](https://github.com/aws-amplify/amplify-cli/commit/54676797b913d4a2c284c62244c8ccf8e55a44d8))
+- **cli:** cLI updates and new features for Amplify Console ([#2742](https://github.com/aws-amplify/amplify-cli/issues/2742)) ([0fd0dd5](https://github.com/aws-amplify/amplify-cli/commit/0fd0dd5102177766c454c8715fa5acac32385048))
+
+# [1.13.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.11...amplify-category-api@1.13.0) (2019-08-30)
+
+### Bug Fixes
+
+- **amplify-provider-awscloudformation:** apigw unauth access ([#1906](https://github.com/aws-amplify/amplify-cli/issues/1906)) ([bcd0d02](https://github.com/aws-amplify/amplify-cli/commit/bcd0d02))
+- [#429](https://github.com/aws-amplify/amplify-cli/issues/429) - Editor hanging bug ([#2086](https://github.com/aws-amplify/amplify-cli/issues/2086)) ([6767445](https://github.com/aws-amplify/amplify-cli/commit/6767445))
+- move test package dependencies to devDependencies ([#2034](https://github.com/aws-amplify/amplify-cli/issues/2034)) ([f5623d0](https://github.com/aws-amplify/amplify-cli/commit/f5623d0))
+
+### Features
+
+- adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c))
+- sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe))
+
+# [1.12.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.11...amplify-category-api@1.12.0) (2019-08-28)
+
+### Bug Fixes
+
+- **amplify-provider-awscloudformation:** apigw unauth access ([#1906](https://github.com/aws-amplify/amplify-cli/issues/1906)) ([bcd0d02](https://github.com/aws-amplify/amplify-cli/commit/bcd0d02))
+- [#429](https://github.com/aws-amplify/amplify-cli/issues/429) - Editor hanging bug ([#2086](https://github.com/aws-amplify/amplify-cli/issues/2086)) ([6767445](https://github.com/aws-amplify/amplify-cli/commit/6767445))
+- move test package dependencies to devDependencies ([#2034](https://github.com/aws-amplify/amplify-cli/issues/2034)) ([f5623d0](https://github.com/aws-amplify/amplify-cli/commit/f5623d0))
+
+### Features
+
+- adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c))
+- sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe))
+
+# [1.11.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.11...amplify-category-api@1.11.0) (2019-08-13)
+
+### Bug Fixes
+
+- **amplify-provider-awscloudformation:** apigw unauth access ([#1906](https://github.com/aws-amplify/amplify-cli/issues/1906)) ([bcd0d02](https://github.com/aws-amplify/amplify-cli/commit/bcd0d02))
+
+### Features
+
+- adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c))
+- sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe))
+
+# [1.10.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.11...amplify-category-api@1.10.0) (2019-08-07)
+
+### Features
+
+- adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c))
+- sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe))
+
+# [1.9.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.11...amplify-category-api@1.9.0) (2019-08-02)
+
+### Features
+
+- adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c))
+- sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe))
+
+# [1.8.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.11...amplify-category-api@1.8.0) (2019-07-31)
+
+### Features
+
+- adding amplify cli predictions category ([#1936](https://github.com/aws-amplify/amplify-cli/issues/1936)) ([b7b7c2c](https://github.com/aws-amplify/amplify-cli/commit/b7b7c2c))
+- sanity check ([#1815](https://github.com/aws-amplify/amplify-cli/issues/1815)) ([54a8dbe](https://github.com/aws-amplify/amplify-cli/commit/54a8dbe))
+
+## [1.7.11](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.10...amplify-category-api@1.7.11) (2019-07-24)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.7.10](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.8...amplify-category-api@1.7.10) (2019-07-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.7.8](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.7...amplify-category-api@1.7.8) (2019-07-10)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.7.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.6...amplify-category-api@1.7.7) (2019-07-09)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.7.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.4...amplify-category-api@1.7.6) (2019-06-30)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.7.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.3...amplify-category-api@1.7.4) (2019-06-26)
+
+### Bug Fixes
+
+- **amplify-category-api:** fix init env bug ([#1715](https://github.com/aws-amplify/amplify-cli/issues/1715)) ([1e21371](https://github.com/aws-amplify/amplify-cli/commit/1e21371)), closes [#1713](https://github.com/aws-amplify/amplify-cli/issues/1713)
+
+## [1.7.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.2...amplify-category-api@1.7.3) (2019-06-20)
+
+### Bug Fixes
+
+- **cli:** fix inquirer version ([#1690](https://github.com/aws-amplify/amplify-cli/issues/1690)) ([9246032](https://github.com/aws-amplify/amplify-cli/commit/9246032)), closes [#1688](https://github.com/aws-amplify/amplify-cli/issues/1688)
+
+## [1.7.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.1...amplify-category-api@1.7.2) (2019-06-18)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.7.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.7.0...amplify-category-api@1.7.1) (2019-06-12)
+
+**Note:** Version bump only for package amplify-category-api
+
+# [1.7.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.6.1...amplify-category-api@1.7.0) (2019-06-11)
+
+### Bug Fixes
+
+- fixing the IAM policies for AppSync API ([#1634](https://github.com/aws-amplify/amplify-cli/issues/1634)) ([9fb2fa9](https://github.com/aws-amplify/amplify-cli/commit/9fb2fa9))
+
+### Features
+
+- add graphQLEndpoint as an env var to lambda functions ([#1641](https://github.com/aws-amplify/amplify-cli/issues/1641)) ([ae825a6](https://github.com/aws-amplify/amplify-cli/commit/ae825a6)), closes [#1620](https://github.com/aws-amplify/amplify-cli/issues/1620)
+
+## [1.6.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.6.0...amplify-category-api@1.6.1) (2019-06-06)
+
+**Note:** Version bump only for package amplify-category-api
+
+# [1.6.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.10...amplify-category-api@1.6.0) (2019-05-29)
+
+### Features
+
+- flow to add policies to access amplify resources from Lambda ([#1462](https://github.com/aws-amplify/amplify-cli/issues/1462)) ([fee247c](https://github.com/aws-amplify/amplify-cli/commit/fee247c))
+
+## [1.5.10](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.9...amplify-category-api@1.5.10) (2019-05-24)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.9](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.8...amplify-category-api@1.5.9) (2019-05-21)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.8](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.7...amplify-category-api@1.5.8) (2019-05-17)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.6...amplify-category-api@1.5.7) (2019-05-07)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.5...amplify-category-api@1.5.6) (2019-04-30)
+
+### Bug Fixes
+
+- update CLI to handle UTF8 BOM ([#1357](https://github.com/aws-amplify/amplify-cli/issues/1357)) ([b0afa07](https://github.com/aws-amplify/amplify-cli/commit/b0afa07)), closes [#1355](https://github.com/aws-amplify/amplify-cli/issues/1355) [#1122](https://github.com/aws-amplify/amplify-cli/issues/1122)
+
+## [1.5.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.4...amplify-category-api@1.5.5) (2019-04-25)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.3...amplify-category-api@1.5.4) (2019-04-24)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.2...amplify-category-api@1.5.3) (2019-04-16)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.5.1...amplify-category-api@1.5.2) (2019-04-16)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.5.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.4.0...amplify-category-api@1.5.1) (2019-04-09)
+
+**Note:** Version bump only for package amplify-category-api
+
+# [1.4.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.0.8...amplify-category-api@1.4.0) (2019-04-03)
+
+### Features
+
+- support for provisioning Cognito Hosted UI and support CRUD operations in Storage and API categories ([729b0de](https://github.com/aws-amplify/amplify-cli/commit/729b0de))
+
+## [1.0.8](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.0.7...amplify-category-api@1.0.8) (2019-03-22)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.0.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.0.6...amplify-category-api@1.0.7) (2019-02-26)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.0.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.0.5...amplify-category-api@1.0.6) (2019-02-22)
+
+### Bug Fixes
+
+- **amplify-category-api:** add check for provider during migration ([3207e41](https://github.com/aws-amplify/amplify-cli/commit/3207e41)), closes [#918](https://github.com/aws-amplify/amplify-cli/issues/918)
+
+## [1.0.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.0.3-beta.0...amplify-category-api@1.0.5) (2019-02-11)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.0.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.0.3-beta.0...amplify-category-api@1.0.3) (2019-02-11)
+
+**Note:** Version bump only for package amplify-category-api
+
+## [1.0.3-beta.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@1.0.2...amplify-category-api@1.0.3-beta.0) (2019-02-11)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.2.1-multienv.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.2.1-multienv.5...amplify-category-api@0.2.1-multienv.7) (2019-01-30)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.2.1-multienv.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.2.1-multienv.5...amplify-category-api@0.2.1-multienv.6) (2019-01-16)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.2.1-multienv.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.2.1-multienv.4...amplify-category-api@0.2.1-multienv.5) (2018-12-28)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.2.1-multienv.4](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.2.1-multienv.3...amplify-category-api@0.2.1-multienv.4) (2018-12-21)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.2.1-multienv.3](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.2.1-multienv.2...amplify-category-api@0.2.1-multienv.3) (2018-12-05)
+
+### Bug Fixes
+
+- **cli:** remove calls to gluegun's prompt.confirm ([#546](https://github.com/aws-amplify/amplify-cli/issues/546)) ([0080ddb](https://github.com/aws-amplify/amplify-cli/commit/0080ddb))
+
+
+
+## [0.2.1-multienv.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.2.1-multienv.1...amplify-category-api@0.2.1-multienv.2) (2018-12-04)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.2.1-multienv.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.2.1-multienv.0...amplify-category-api@0.2.1-multienv.1) (2018-11-28)
+
+### Features
+
+- add a warning on migration and force compile gql schema ([77fb557](https://github.com/aws-amplify/amplify-cli/commit/77fb557))
+- migration of API GW and Interactions ([a91ba9a](https://github.com/aws-amplify/amplify-cli/commit/a91ba9a))
+- migration of categories - s3,dynamo,lambda,appsync ([#495](https://github.com/aws-amplify/amplify-cli/issues/495)) ([1ef1d21](https://github.com/aws-amplify/amplify-cli/commit/1ef1d21))
+
+
+
+## [0.2.1-multienv.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.34-multienv.2...amplify-category-api@0.2.1-multienv.0) (2018-11-21)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.34-multienv.2](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.34-multienv.1...amplify-category-api@0.1.34-multienv.2) (2018-11-19)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.34-multienv.1](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.34-multienv.0...amplify-category-api@0.1.34-multienv.1) (2018-11-19)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.34-multienv.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.33...amplify-category-api@0.1.34-multienv.0) (2018-11-16)
+
+### Features
+
+- multi-environment support for API Gateway ([#418](https://github.com/aws-amplify/amplify-cli/issues/418)) ([24ddf83](https://github.com/aws-amplify/amplify-cli/commit/24ddf83))
+- multi-environment support for interactions category ([4ca2617](https://github.com/aws-amplify/amplify-cli/commit/4ca2617))
+
+
+
+## [0.1.33](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.33-beta.0...amplify-category-api@0.1.33) (2018-11-09)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.33-beta.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.13...amplify-category-api@0.1.33-beta.0) (2018-11-09)
+
+### Features
+
+- add option to open AppSync console using the CLI ([#386](https://github.com/aws-amplify/amplify-cli/issues/386)) ([3874a57](https://github.com/aws-amplify/amplify-cli/commit/3874a57))
+
+
+
+## [0.1.32](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.32-beta.0...amplify-category-api@0.1.32) (2018-11-05)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.32-beta.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.13...amplify-category-api@0.1.32-beta.0) (2018-11-05)
+
+### Features
+
+- add option to open AppSync console using the CLI ([#386](https://github.com/aws-amplify/amplify-cli/issues/386)) ([3874a57](https://github.com/aws-amplify/amplify-cli/commit/3874a57))
+
+
+
+## [0.1.31](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.13...amplify-category-api@0.1.31) (2018-11-02)
+
+### Features
+
+- add option to open AppSync console using the CLI ([#386](https://github.com/aws-amplify/amplify-cli/issues/386)) ([3874a57](https://github.com/aws-amplify/amplify-cli/commit/3874a57))
+
+
+
+## [0.1.30](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.30-beta.0...amplify-category-api@0.1.30) (2018-11-02)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.30-beta.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.13...amplify-category-api@0.1.30-beta.0) (2018-11-02)
+
+### Features
+
+- add option to open AppSync console using the CLI ([#386](https://github.com/aws-amplify/amplify-cli/issues/386)) ([3874a57](https://github.com/aws-amplify/amplify-cli/commit/3874a57))
+
+
+
+## [0.1.29](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.29-beta.0...amplify-category-api@0.1.29) (2018-10-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.29-beta.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.13...amplify-category-api@0.1.29-beta.0) (2018-10-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.28](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.28-beta.0...amplify-category-api@0.1.28) (2018-10-18)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.28-beta.0](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.13...amplify-category-api@0.1.28-beta.0) (2018-10-12)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.13](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.12...amplify-category-api@0.1.13) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.12](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.11...amplify-category-api@0.1.12) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.11](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.9...amplify-category-api@0.1.11) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.10](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.9...amplify-category-api@0.1.10) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.9](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.8...amplify-category-api@0.1.9) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.8](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.7...amplify-category-api@0.1.8) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.7](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.6...amplify-category-api@0.1.7) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.6](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.5...amplify-category-api@0.1.6) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## [0.1.5](https://github.com/aws-amplify/amplify-cli/compare/amplify-category-api@0.1.4...amplify-category-api@0.1.5) (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## 0.1.4 (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## 0.1.3 (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## 0.1.2 (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
+
+
+
+## 0.1.1 (2018-08-23)
+
+**Note:** Version bump only for package amplify-category-api
diff --git a/packages/amplify-category-api/Readme.md b/packages/amplify-category-api/Readme.md
new file mode 100644
index 0000000000..3fa520a15e
--- /dev/null
+++ b/packages/amplify-category-api/Readme.md
@@ -0,0 +1,14 @@
+# Amplify CLI API Plugin
+
+## Commands Summary
+
+The following table lists the current set of commands supported by the Amplify API Category Plugin.
+
+| Command | Description |
+| --- | --- |
+| amplify api add | Takes you through steps in the CLI to add an API resource to your backend. |
+| amplify api add-graphql-datasource | Takes you through the steps in the CLI to import an already existing Aurora Serverless data source to an existing GraphQL API resource.
+| amplify api update | Takes you through steps in the CLI to update an API resource. |
+| amplify api gql-compile | Compiles your GraphQL schema and generates a corresponding cloudformation template. |
+| amplify api push | Provisions only API cloud resources with the latest local developments. |
+| amplify api remove | Removes an API resource from your local backend. The resource is removed from the cloud on the next push command. |
diff --git a/packages/amplify-category-api/amplify-plugin.json b/packages/amplify-category-api/amplify-plugin.json
new file mode 100644
index 0000000000..f9ee43e924
--- /dev/null
+++ b/packages/amplify-category-api/amplify-plugin.json
@@ -0,0 +1,21 @@
+{
+ "name": "api",
+ "type": "category",
+ "commands": [
+ "add-graphql-datasource",
+ "add",
+ "console",
+ "gql-compile",
+ "migrate",
+ "override",
+ "push",
+ "rebuild",
+ "remove",
+ "update",
+ "help"
+ ],
+ "commandAliases": {
+ "configure": "update"
+ },
+ "eventHandlers": []
+}
diff --git a/packages/amplify-category-api/package.json b/packages/amplify-category-api/package.json
new file mode 100644
index 0000000000..369a964053
--- /dev/null
+++ b/packages/amplify-category-api/package.json
@@ -0,0 +1,135 @@
+{
+ "name": "@aws-amplify/amplify-category-api",
+ "version": "2.2.0",
+ "description": "amplify-cli api plugin",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/aws-amplify/amplify-category-api.git",
+ "directory": "packages/amplify-category-api"
+ },
+ "author": "Amazon Web Services",
+ "license": "Apache-2.0",
+ "main": "lib/index.js",
+ "scripts": {
+ "build": "tsc",
+ "watch": "tsc -w",
+ "clean": "rimraf lib tsconfig.tsbuildinfo node_modules",
+ "test": "jest",
+ "generateSchemas": "ts-node ./scripts/generateApiSchemas.ts"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@aws-amplify/graphql-auth-transformer": "0.9.2",
+ "@aws-amplify/graphql-default-value-transformer": "0.5.24",
+ "@aws-amplify/graphql-function-transformer": "0.7.18",
+ "@aws-amplify/graphql-http-transformer": "0.8.18",
+ "@aws-amplify/graphql-index-transformer": "0.11.9",
+ "@aws-amplify/graphql-maps-to-transformer": "1.1.16",
+ "@aws-amplify/graphql-model-transformer": "0.14.2",
+ "@aws-amplify/graphql-predictions-transformer": "0.6.18",
+ "@aws-amplify/graphql-relational-transformer": "0.9.2",
+ "@aws-amplify/graphql-searchable-transformer": "0.14.2",
+ "@aws-amplify/graphql-transformer-core": "0.17.2",
+ "@aws-amplify/graphql-transformer-interfaces": "1.14.2",
+ "@aws-amplify/graphql-transformer-migrator": "1.2.38",
+ "@aws-cdk/assets": "~1.124.0",
+ "@aws-cdk/aws-apigateway": "~1.124.0",
+ "@aws-cdk/aws-apigatewayv2": "~1.124.0",
+ "@aws-cdk/aws-applicationautoscaling": "~1.124.0",
+ "@aws-cdk/aws-autoscaling": "~1.124.0",
+ "@aws-cdk/aws-autoscaling-common": "~1.124.0",
+ "@aws-cdk/aws-autoscaling-hooktargets": "~1.124.0",
+ "@aws-cdk/aws-certificatemanager": "~1.124.0",
+ "@aws-cdk/aws-cloudformation": "~1.124.0",
+ "@aws-cdk/aws-cloudfront": "~1.124.0",
+ "@aws-cdk/aws-cloudwatch": "~1.124.0",
+ "@aws-cdk/aws-codebuild": "~1.124.0",
+ "@aws-cdk/aws-codeguruprofiler": "~1.124.0",
+ "@aws-cdk/aws-codepipeline": "~1.124.0",
+ "@aws-cdk/aws-codepipeline-actions": "~1.124.0",
+ "@aws-cdk/aws-cognito": "~1.124.0",
+ "@aws-cdk/aws-ec2": "~1.124.0",
+ "@aws-cdk/aws-ecr": "~1.124.0",
+ "@aws-cdk/aws-ecr-assets": "~1.124.0",
+ "@aws-cdk/aws-ecs": "~1.124.0",
+ "@aws-cdk/aws-efs": "~1.124.0",
+ "@aws-cdk/aws-elasticloadbalancing": "~1.124.0",
+ "@aws-cdk/aws-elasticloadbalancingv2": "~1.124.0",
+ "@aws-cdk/aws-events": "~1.124.0",
+ "@aws-cdk/aws-iam": "~1.124.0",
+ "@aws-cdk/aws-kms": "~1.124.0",
+ "@aws-cdk/aws-lambda": "~1.124.0",
+ "@aws-cdk/aws-logs": "~1.124.0",
+ "@aws-cdk/aws-route53": "~1.124.0",
+ "@aws-cdk/aws-route53-targets": "~1.124.0",
+ "@aws-cdk/aws-s3": "~1.124.0",
+ "@aws-cdk/aws-s3-assets": "~1.124.0",
+ "@aws-cdk/aws-sam": "~1.124.0",
+ "@aws-cdk/aws-secretsmanager": "~1.124.0",
+ "@aws-cdk/aws-servicediscovery": "~1.124.0",
+ "@aws-cdk/aws-sns": "~1.124.0",
+ "@aws-cdk/aws-sns-subscriptions": "~1.124.0",
+ "@aws-cdk/aws-sqs": "~1.124.0",
+ "@aws-cdk/aws-ssm": "~1.124.0",
+ "@aws-cdk/cloud-assembly-schema": "~1.124.0",
+ "@aws-cdk/core": "~1.124.0",
+ "@aws-cdk/custom-resources": "~1.124.0",
+ "@aws-cdk/cx-api": "~1.124.0",
+ "@aws-cdk/region-info": "~1.124.0",
+ "@graphql-tools/merge": "^6.0.18",
+ "@octokit/rest": "^18.0.9",
+ "amplify-cli-core": "^2.9.0",
+ "amplify-headless-interface": "^1.15.0",
+ "amplify-prompts": "^2.2.0",
+ "amplify-provider-awscloudformation": "^6.3.0",
+ "amplify-util-headless-input": "^1.9.5",
+ "chalk": "^4.1.1",
+ "cloudform": "^4.2.0",
+ "cloudform-types": "^4.2.0",
+ "constructs": "^3.3.125",
+ "fs-extra": "^8.1.0",
+ "graphql": "^14.5.8",
+ "graphql-auth-transformer": "7.2.34",
+ "graphql-connection-transformer": "5.2.34",
+ "graphql-dynamodb-transformer": "7.2.34",
+ "graphql-elasticsearch-transformer": "5.2.34",
+ "graphql-function-transformer": "3.3.25",
+ "graphql-http-transformer": "5.2.34",
+ "graphql-key-transformer": "3.2.34",
+ "graphql-predictions-transformer": "3.2.34",
+ "graphql-relational-schema-transformer": "2.21.7",
+ "graphql-transformer-common": "4.23.1",
+ "graphql-transformer-core": "7.5.2",
+ "graphql-versioned-transformer": "5.2.34",
+ "import-from": "^3.0.0",
+ "import-global": "^0.1.0",
+ "inquirer": "^7.3.3",
+ "js-yaml": "^4.0.0",
+ "lodash": "^4.17.21",
+ "ora": "^4.0.3",
+ "rimraf": "^3.0.0",
+ "uuid": "^8.3.2"
+ },
+ "devDependencies": {
+ "@aws-cdk/assertions": "~1.124.0",
+ "@types/js-yaml": "^4.0.0"
+ },
+ "jest": {
+ "testURL": "http://localhost",
+ "transform": {
+ "^.+\\.tsx?$": "ts-jest"
+ },
+ "testRegex": "((\\.|/)(test|spec))\\.(jsx?|tsx?)$",
+ "moduleFileExtensions": [
+ "ts",
+ "tsx",
+ "js",
+ "jsx",
+ "json",
+ "node"
+ ],
+ "collectCoverage": true
+ }
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/cloudformation-templates/defaultCustomResources.json b/packages/amplify-category-api/resources/awscloudformation/cloudformation-templates/defaultCustomResources.json
new file mode 100644
index 0000000000..f95feea378
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/cloudformation-templates/defaultCustomResources.json
@@ -0,0 +1,58 @@
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Description": "An auto-generated nested stack.",
+ "Metadata": {},
+ "Parameters": {
+ "AppSyncApiId": {
+ "Type": "String",
+ "Description": "The id of the AppSync API associated with this project."
+ },
+ "AppSyncApiName": {
+ "Type": "String",
+ "Description": "The name of the AppSync API",
+ "Default": "AppSyncSimpleTransform"
+ },
+ "env": {
+ "Type": "String",
+ "Description": "The environment name. e.g. Dev, Test, or Production",
+ "Default": "NONE"
+ },
+ "S3DeploymentBucket": {
+ "Type": "String",
+ "Description": "The S3 bucket containing all deployment assets for the project."
+ },
+ "S3DeploymentRootKey": {
+ "Type": "String",
+ "Description": "An S3 key relative to the S3DeploymentBucket that points to the root\nof the deployment directory."
+ }
+ },
+ "Resources": {
+ "EmptyResource": {
+ "Type": "Custom::EmptyResource",
+ "Condition": "AlwaysFalse"
+ }
+ },
+ "Conditions": {
+ "HasEnvironmentParameter": {
+ "Fn::Not": [
+ {
+ "Fn::Equals": [
+ {
+ "Ref": "env"
+ },
+ "NONE"
+ ]
+ }
+ ]
+ },
+ "AlwaysFalse": {
+ "Fn::Equals": ["true", "false"]
+ }
+ },
+ "Outputs": {
+ "EmptyOutput": {
+ "Description": "An empty output. You may delete this if you have at least one resource above.",
+ "Value": ""
+ }
+ }
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/docker-compose.yml b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/docker-compose.yml
new file mode 100644
index 0000000000..ec14c45640
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/docker-compose.yml
@@ -0,0 +1,23 @@
+version: "3.8"
+services:
+ express:
+ build:
+ context: ./express
+ dockerfile: Dockerfile
+ ports:
+ - "8080:8080"
+ networks:
+ - public
+ - private
+ python:
+ build:
+ context: ./python
+ dockerfile: Dockerfile
+ networks:
+ - public
+ - private
+ ports:
+ - "5000:5000"
+networks:
+ public:
+ private:
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/Dockerfile b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/Dockerfile
new file mode 100644
index 0000000000..189a7e546e
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/Dockerfile
@@ -0,0 +1,12 @@
+FROM public.ecr.aws/bitnami/node:14-prod-debian-10
+
+ENV PORT=8080
+EXPOSE 8080
+
+WORKDIR /usr/src/app
+
+COPY package*.json ./
+RUN npm install
+COPY . .
+
+CMD [ "node", "index.js" ]
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/DynamoDBActions.js b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/DynamoDBActions.js
new file mode 100644
index 0000000000..ca4e48cb06
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/DynamoDBActions.js
@@ -0,0 +1,60 @@
+const AWS = require('aws-sdk');
+const docClient = new AWS.DynamoDB.DocumentClient();
+
+const TableName = process.env.STORAGE_POSTS_NAME;
+
+const addPostToDDB = async ({ id, title, author, description, topic }) => {
+
+ var params = {
+ Item: {
+ id: parseInt(id, 10),
+ title: title,
+ author: author,
+ description: description,
+ topic: topic
+ },
+ TableName: TableName
+ }
+ try {
+ const data = await docClient.put(params).promise()
+ return params.Item;
+ } catch (err) {
+ console.log('Error: ' + err);
+ return err
+ }
+}
+
+const scanPostsFromDDB = async () => {
+ var params = {
+ TableName: TableName,
+ }
+
+ try {
+ const data = await docClient.scan(params).promise();
+ return data.Items;
+ } catch (err) {
+ console.log('Error: ' + err);
+ return err;
+ }
+}
+
+const getPostFromDDB = async (id) => {
+ const key = parseInt(id, 10);
+ var params = {
+ TableName: TableName,
+ Key: { id: key },
+ }
+ try {
+ const data = await docClient.get(params).promise()
+ return data.Item;
+ } catch (err) {
+ console.log('Error: ' + err);
+ return err
+ }
+}
+
+module.exports = {
+ addPostToDDB,
+ scanPostsFromDDB,
+ getPostFromDDB
+};
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/index.js b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/index.js
new file mode 100644
index 0000000000..78228b2775
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/index.js
@@ -0,0 +1,124 @@
+const express = require("express");
+const bodyParser = require('body-parser');
+const http = require('http');
+const port = process.env.PORT || 3001;
+
+const {
+ addPostToDDB,
+ scanPostsFromDDB,
+ getPostFromDDB
+} = require('./DynamoDBActions');
+
+const app = express();
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: true }));
+
+// Enable CORS for all methods
+app.use(function (req, res, next) {
+ res.header("Access-Control-Allow-Origin", "*")
+ res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
+ next()
+});
+
+const checkAuthRules = (req, res, next) => {
+ const jwt = req.header("Authorization") || "";
+
+ const [, jwtBody] = jwt.split(".");
+
+ const obj = JSON.parse(
+ jwtBody ? Buffer.from(jwtBody, "base64").toString("utf-8") : "{}"
+ );
+
+ //Customer can perform logic on JWT body
+ //console.log(obj);
+ next();
+
+ //Failure example:
+ // const err = new Error("Access denied");
+ // err.statusCode = 403;
+ // return next(err);
+}
+
+app.use(checkAuthRules);
+
+app.get("/posts", async (req, res, next) => {
+
+ try {
+ const result = await scanPostsFromDDB();
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+app.get("/post", async (req, res, next) => {
+ console.log(req.query.id);
+
+ try {
+ const result = await getPostFromDDB(req.query.id);
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+app.post("/post", async (req, res, next) => {
+
+ try {
+ const result = await addPostToDDB(req.body);
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+app.get("/images", (req, res, next) => {
+ const options = {
+ port: 5000,
+ host: 'localhost',
+ method: 'GET',
+ path: '/images'
+ };
+
+ http.get(options, data => {
+ var body = '';
+ data.on('data', (chunk) => {
+ body += chunk;
+ });
+ data.on('end', () => {
+ console.log(body);
+ try {
+ res.contentType("application/json").send(body);
+ } catch (err) {
+ console.log(err);
+ next(err);
+ }
+ }).on('error', (error) => {
+ console.log(error);
+ });
+ })
+});
+
+app.use((req, res, next) => {
+
+ try {
+ const result = `Please try GET on /posts, /post?id=xyz, GET on /images, or a POST to /post with JSON {\"id\":\"123\",\"title\":\"Fargate test\"}`;
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+// Error middleware must be defined last
+app.use((err, req, res, next) => {
+ console.error(err.message);
+ if (!err.statusCode) err.statusCode = 500; // If err has no specified error code, set error code to 'Internal Server Error (500)'
+ res
+ .status(err.statusCode)
+ .json({ message: err.message })
+ .end();
+});
+
+app.listen(port, () => {
+ console.log('Example app listening at http://localhost:' + port);
+});
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/package.json b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/package.json
new file mode 100644
index 0000000000..84302629ea
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/express/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "express-lasagna",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {},
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "aws-sdk": "^2.1113.0",
+ "axios": "^0.21.4",
+ "body-parser": "^1.19.0",
+ "express": "^4.17.1",
+ "redis": "^3.0.2"
+ }
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/Dockerfile b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/Dockerfile
new file mode 100644
index 0000000000..ceac55382f
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/Dockerfile
@@ -0,0 +1,17 @@
+# set base image (host OS)
+FROM public.ecr.aws/bitnami/python:3.8-prod-debian-10
+
+# set the working directory in the container
+WORKDIR /code
+
+# copy the dependencies file to the working directory
+COPY requirements.txt .
+
+# install dependencies
+RUN pip install -r requirements.txt
+
+# copy the content of the local src directory to the working directory
+COPY src/ .
+
+# command to run on container start
+CMD [ "python", "./server.py" ]
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/requirements.txt b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/requirements.txt
new file mode 100644
index 0000000000..fb0dec5b66
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/requirements.txt
@@ -0,0 +1 @@
+Flask==2.0.3
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/src/server.py b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/src/server.py
new file mode 100644
index 0000000000..b46754de98
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockercompose-rest-express/python/src/server.py
@@ -0,0 +1,9 @@
+from flask import Flask
+server = Flask(__name__)
+
+@server.route('/images')
+def hello():
+ return 'Processing images...'
+
+if __name__ == "__main__":
+ server.run(host='0.0.0.0')
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/Dockerfile b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/Dockerfile
new file mode 100644
index 0000000000..189a7e546e
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/Dockerfile
@@ -0,0 +1,12 @@
+FROM public.ecr.aws/bitnami/node:14-prod-debian-10
+
+ENV PORT=8080
+EXPOSE 8080
+
+WORKDIR /usr/src/app
+
+COPY package*.json ./
+RUN npm install
+COPY . .
+
+CMD [ "node", "index.js" ]
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/DynamoDBActions.js b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/DynamoDBActions.js
new file mode 100644
index 0000000000..278e9975b2
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/DynamoDBActions.js
@@ -0,0 +1,60 @@
+const AWS = require('aws-sdk');
+const docClient = new AWS.DynamoDB.DocumentClient();
+
+const TableName = process.env.STORAGE_POSTS_NAME;
+
+const addPostToDDB = async ({ id, title, author, description, topic }) => {
+
+ var params = {
+ Item: {
+ id: parseInt(id, 10),
+ title: title,
+ author: author,
+ description: description,
+ topic: topic
+ },
+ TableName: TableName
+ }
+ try {
+ const data = await docClient.put(params).promise()
+ return params.Item;
+ } catch (err) {
+ console.log('Error: ' + err);
+ return err
+ }
+}
+
+const scanPostsFromDDB = async () => {
+ var params = {
+ TableName: TableName,
+ }
+
+ try {
+ const data = await docClient.scan(params).promise();
+ return data.Items;
+ } catch (err) {
+ console.log('Error: ' + err);
+ return err;
+ }
+}
+
+const getPostFromDDB = async (id) => {
+ const key = parseInt(id, 10);
+ var params = {
+ TableName: TableName,
+ Key: { id: key },
+ }
+ try {
+ const data = await docClient.get(params).promise()
+ return data.Item;
+ } catch (err) {
+ console.log('Error: ' + err);
+ return err
+ }
+}
+
+module.exports = {
+ addPostToDDB,
+ scanPostsFromDDB,
+ getPostFromDDB
+};
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/index.js b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/index.js
new file mode 100644
index 0000000000..0d40649139
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/index.js
@@ -0,0 +1,96 @@
+const express = require("express");
+const bodyParser = require('body-parser');
+const port = process.env.PORT || 3001;
+
+const {
+ addPostToDDB,
+ scanPostsFromDDB,
+ getPostFromDDB
+} = require('./DynamoDBActions');
+
+const app = express();
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: true }));
+
+// Enable CORS for all methods
+app.use(function (req, res, next) {
+ res.header("Access-Control-Allow-Origin", "*")
+ res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
+ next()
+});
+
+const checkAuthRules = (req, res, next) => {
+ const jwt = req.header("Authorization") || "";
+
+ const [, jwtBody] = jwt.split(".");
+
+ const obj = JSON.parse(
+ jwtBody ? Buffer.from(jwtBody, "base64").toString("utf-8") : "{}"
+ );
+
+ //Customer can perform logic on JWT body
+ //console.log(obj);
+ next();
+
+ //Failure example:
+ // const err = new Error("Access denied");
+ // err.statusCode = 403;
+ // return next(err);
+}
+
+app.use(checkAuthRules);
+
+app.get("/posts", async (req, res, next) => {
+
+ try {
+ const result = await scanPostsFromDDB();
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+app.get("/post", async (req, res, next) => {
+ console.log(req.query.id);
+
+ try {
+ const result = await getPostFromDDB(req.query.id);
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+app.post("/post", async (req, res, next) => {
+
+ try {
+ const result = await addPostToDDB(req.body);
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+app.use((req, res, next) => {
+
+ try {
+ const result = `Please try GET on /posts, /post?id=xyz, or a POST to /post with JSON {\"id\":\"123\",\"title\":\"Fargate test\"}`;
+ res.contentType("application/json").send(result);
+ } catch (err) {
+ next(err);
+ }
+});
+
+// Error middleware must be defined last
+app.use((err, req, res, next) => {
+ console.error(err.message);
+ if (!err.statusCode) err.statusCode = 500; // If err has no specified error code, set error code to 'Internal Server Error (500)'
+ res
+ .status(err.statusCode)
+ .json({ message: err.message })
+ .end();
+});
+
+app.listen(port, () => {
+ console.log('Example app listening at http://localhost:' + port);
+});
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/package.json b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/package.json
new file mode 100644
index 0000000000..87ea138cec
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/dockerfile-rest-express/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "express-lasagna",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "aws-sdk": "^2.1113.0",
+ "body-parser": "^1.19.0",
+ "express": "^4.17.1"
+ }
+ }
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/Dockerfile b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/Dockerfile
new file mode 100644
index 0000000000..8c5be4eb09
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/Dockerfile
@@ -0,0 +1,10 @@
+FROM public.ecr.aws/bitnami/node:14-prod-debian-10
+
+WORKDIR /usr/src/app
+COPY package*.json ./
+RUN npm install
+COPY . .
+
+EXPOSE 3000
+
+CMD ["node", "index.js"]
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/index.js b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/index.js
new file mode 100644
index 0000000000..685aa6d669
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/index.js
@@ -0,0 +1,27 @@
+'use strict';
+const PORT = 3000;
+var express = require('express');
+var { graphqlHTTP } = require('express-graphql');
+var { buildSchema } = require('graphql');
+var Query = require('./schema.js');
+var root = require('./resolvers.js');
+var app = express();
+// Enable CORS for all methods
+app.use(function (req, res, next) {
+ res.header("Access-Control-Allow-Origin", "*")
+ res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
+ next()
+});
+app.use('/graphql', graphqlHTTP({
+ schema: buildSchema(Query),
+ rootValue: root,
+ graphiql: true
+}));
+app.get('/', (req, res, next) => {
+ try {
+ res.redirect('/graphql');
+ } catch (err){
+ next(err);
+ }
+ });
+app.listen(PORT, () => console.log('Listening on localhost:' + PORT + '/graphql'));
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/package.json b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/package.json
new file mode 100644
index 0000000000..98445c57ea
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "graphql",
+ "version": "1.0.0",
+ "description": "Sample GraphQL server for Fargate",
+ "main": "index.js",
+ "scripts": {},
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "aws-sdk": "^2.1113.0",
+ "express": "^4.17.1",
+ "express-graphql": "^0.11.0",
+ "graphql": "^15.4.0"
+ }
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/resolvers.js b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/resolvers.js
new file mode 100644
index 0000000000..e9c295fdf1
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/resolvers.js
@@ -0,0 +1,58 @@
+const AWS = require('aws-sdk');
+const docClient = new AWS.DynamoDB.DocumentClient();
+
+const TableName = process.env.STORAGE_POSTS_NAME;
+
+const addPostToDDB = async ({ id, title, author, description, topic }) => {
+ var params = {
+ Item: {
+ id: id,
+ title: title,
+ author: author,
+ description: description,
+ topic: topic
+ },
+ TableName: TableName
+ }
+ try {
+ const data = await docClient.put(params).promise()
+ return params.Item;
+ } catch (err) {
+ return err
+ }
+}
+
+const scanPostsFromDDB = async () => {
+ var params = {
+ TableName: TableName,
+ }
+
+ try {
+ const data = await docClient.scan(params).promise();
+ return data.Items;
+ } catch (err) {
+ console.log(err);
+ return err;
+ }
+}
+
+const getPostFromDDB = async (id) => {
+ var params = {
+ TableName: TableName,
+ Key: id,
+ }
+ try {
+ const data = await docClient.get(params).promise()
+ return data.Item;
+ } catch (err) {
+ return err
+ }
+}
+
+var root = {
+ getPost: getPostFromDDB,
+ posts: scanPostsFromDDB,
+ addPost: addPostToDDB
+};
+
+module.exports = root;
diff --git a/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/schema.js b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/schema.js
new file mode 100644
index 0000000000..0c9b2ef376
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/container-templates/graphql-express/schema.js
@@ -0,0 +1,16 @@
+module.exports = `
+type Query {
+ getPost(id: Int!): Post
+ posts: [Post]
+},
+type Mutation {
+ addPost(id: Int!, title: String, author: String, description: String, topic: String): Post
+},
+type Post {
+ id: Int
+ title: String
+ author: String
+ description: String
+ topic: String
+}
+`
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/blank-schema.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/blank-schema.graphql
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema-v2.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema-v2.graphql
new file mode 100644
index 0000000000..2619b18729
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema-v2.graphql
@@ -0,0 +1,18 @@
+type Blog @model {
+ id: ID!
+ name: String!
+ posts: [Post] @hasMany
+}
+
+type Post @model {
+ id: ID!
+ title: String!
+ blog: Blog @belongsTo
+ comments: [Comment] @hasMany
+}
+
+type Comment @model {
+ id: ID!
+ post: Post @belongsTo
+ content: String!
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema.graphql
new file mode 100644
index 0000000000..cf765373f8
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/many-relationship-schema.graphql
@@ -0,0 +1,20 @@
+type Blog @model {
+ id: ID!
+ name: String!
+ posts: [Post] @connection(keyName: "byBlog", fields: ["id"])
+}
+
+type Post @model @key(name: "byBlog", fields: ["blogID"]) {
+ id: ID!
+ title: String!
+ blogID: ID!
+ blog: Blog @connection(fields: ["blogID"])
+ comments: [Comment] @connection(keyName: "byPost", fields: ["id"])
+}
+
+type Comment @model @key(name: "byPost", fields: ["postID", "content"]) {
+ id: ID!
+ postID: ID!
+ post: Post @connection(fields: ["postID"])
+ content: String!
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema-v2.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema-v2.graphql
new file mode 100644
index 0000000000..d1706a3bab
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema-v2.graphql
@@ -0,0 +1,18 @@
+type Task
+ @model
+ @auth(
+ rules: [
+ { allow: groups, groups: ["Managers"], operations: [create, update, read, delete] }
+ { allow: groups, groups: ["Employees"], operations: [read] }
+ ]
+ ) {
+ id: ID!
+ title: String!
+ description: String
+ status: String
+}
+
+type PrivateNote @model @auth(rules: [{ allow: owner }]) {
+ id: ID!
+ content: String!
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema.graphql
new file mode 100644
index 0000000000..3438d56938
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-auth-schema.graphql
@@ -0,0 +1,17 @@
+type Task
+ @model
+ @auth(
+ rules: [
+ { allow: groups, groups: ["Managers"], queries: null, mutations: [create, update, delete] }
+ { allow: groups, groups: ["Employees"], queries: [get, list], mutations: null }
+ ]
+ ) {
+ id: ID!
+ title: String!
+ description: String
+ status: String
+}
+type PrivateNote @model @auth(rules: [{ allow: owner }]) {
+ id: ID!
+ content: String!
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema-v2.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema-v2.graphql
new file mode 100644
index 0000000000..74b097ceae
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema-v2.graphql
@@ -0,0 +1,5 @@
+type Todo @model {
+ id: ID!
+ name: String!
+ description: String
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema.graphql b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema.graphql
new file mode 100644
index 0000000000..74b097ceae
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/graphql-schemas/single-object-schema.graphql
@@ -0,0 +1,5 @@
+type Todo @model {
+ id: ID!
+ name: String!
+ description: String
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/lambdas/pipeline-on-event.js b/packages/amplify-category-api/resources/awscloudformation/lambdas/pipeline-on-event.js
new file mode 100644
index 0000000000..bbec181a7e
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/lambdas/pipeline-on-event.js
@@ -0,0 +1,15 @@
+exports.handler = async function({ RequestType, PhysicalResourceId, ResourceProperties }) {
+ switch (RequestType) {
+ case 'Delete':
+ case 'Update':
+ return { PhysicalResourceId };
+ }
+
+ const { pipelineName } = ResourceProperties;
+
+ const result = {
+ PhysicalResourceId: `pipelineawaiter-${pipelineName}`,
+ };
+
+ return result;
+};
diff --git a/packages/amplify-category-api/resources/awscloudformation/lambdas/pipeline.js b/packages/amplify-category-api/resources/awscloudformation/lambdas/pipeline.js
new file mode 100644
index 0000000000..daa3a4e1a4
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/lambdas/pipeline.js
@@ -0,0 +1,108 @@
+const AWS = require('aws-sdk');
+
+const stageName = 'Source';
+const actionName = 'Source';
+
+const codePipeline = new AWS.CodePipeline();
+const cloudFormation = new AWS.CloudFormation();
+
+exports.handler = async function({ RequestType, ResourceProperties, StackId }) {
+ const { pipelineName, artifactBucketName, artifactKey, deploymentMechanism } = ResourceProperties;
+
+ console.log('Properties', ResourceProperties);
+
+ switch (RequestType) {
+ case 'Delete':
+ return { IsComplete: true };
+ case 'Update':
+ const [, StackName] = StackId.split('/');
+ const { Stacks } = await cloudFormation.describeStacks({ StackName }).promise();
+ const [{ StackStatus }] = Stacks;
+
+ if (StackStatus.includes('ROLLBACK')) {
+ return { IsComplete: true };
+ }
+ }
+
+ let stages;
+
+ try {
+ const { pipeline } = await codePipeline.getPipeline({ name: pipelineName }).promise();
+
+ ({ stages } = pipeline);
+ } catch (error) {
+ const { code } = error;
+
+ switch (code) {
+ case 'PipelineNotFoundException':
+ console.warn(error);
+
+ return {
+ IsComplete: false,
+ };
+ default:
+ throw error;
+ }
+ }
+
+ const stage = stages.find(({ name }) => name === stageName);
+
+ if (stage === undefined) {
+ throw new Error(`There is no stage named "${stageName}" in the "${pipelineName}" pipeline`);
+ }
+
+ const action = stage.actions.find(({ name }) => name === actionName);
+
+ if (action === undefined) {
+ throw new Error(`There is no action named "${actionName}" in the "${stageName}" stage of the "${pipelineName}" pipeline`);
+ }
+
+ const {
+ configuration,
+ configuration: { S3Bucket, S3ObjectKey },
+ } = action;
+
+ if (deploymentMechanism === 'FULLY_MANAGED') {
+ if (S3Bucket !== artifactBucketName || S3ObjectKey !== artifactKey) {
+ console.warn(
+ `Bucket "${artifactBucketName}" and key "${artifactKey}" dont match the "${actionName}" action configuration ${JSON.stringify(
+ configuration,
+ )}`,
+ );
+
+ return {
+ IsComplete: false,
+ };
+ }
+ }
+
+ let execution;
+
+ try {
+ const { pipelineExecutionSummaries } = await codePipeline.listPipelineExecutions({ pipelineName }).promise();
+
+ [execution] = pipelineExecutionSummaries;
+ } catch (error) {
+ console.warn(error);
+
+ return {
+ IsComplete: false,
+ };
+ }
+
+ console.log(execution);
+
+ const { status } = execution || {};
+
+ let IsComplete = false;
+
+ switch (status) {
+ case 'Failed':
+ case 'Stopped':
+ throw new Error("The execution didn't succeed");
+ case 'Succeeded':
+ IsComplete = true;
+ }
+
+ return { IsComplete };
+};
diff --git a/packages/amplify-category-api/resources/awscloudformation/lambdas/predeploy.js b/packages/amplify-category-api/resources/awscloudformation/lambdas/predeploy.js
new file mode 100644
index 0000000000..f5a0aa5423
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/lambdas/predeploy.js
@@ -0,0 +1,20 @@
+const AWS = require('aws-sdk');
+
+const codepipeline = new AWS.CodePipeline();
+const ecs = new AWS.ECS();
+
+const { DESIRED_COUNT: desiredCountStr, CLUSTER_NAME: cluster, SERVICE_NAME: service } = process.env;
+
+const desiredCount = parseInt(desiredCountStr, 10);
+
+exports.handler = async function({ 'CodePipeline.job': { id: jobId } }) {
+ await ecs
+ .updateService({
+ service,
+ cluster,
+ desiredCount,
+ })
+ .promise();
+
+ return await codepipeline.putJobSuccessResult({ jobId }).promise();
+};
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/override.ts.sample b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/override.ts.sample
new file mode 100644
index 0000000000..e0bc1fca53
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/override.ts.sample
@@ -0,0 +1,6 @@
+// This file is used to override the REST API resources configuration
+import { AmplifyApiRestResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper';
+
+export function override(resources: AmplifyApiRestResourceStackTemplate) {
+
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/package.json b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/package.json
new file mode 100644
index 0000000000..33e7fd156b
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "overrides",
+ "version": "1.0.0",
+ "description": "",
+ "scripts": {
+ "build": "tsc",
+ "watch": "tsc -w"
+ },
+ "dependencies": {
+ "@aws-amplify/cli-extensibility-helper": "^2.3.0"
+ },
+ "devDependencies": {
+ "typescript": "^4.2.4"
+ }
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/tsconfig.json b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/tsconfig.json
new file mode 100644
index 0000000000..c6f1a33b4d
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "module": "commonjs",
+ "strict": false,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "build"
+ }
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/tsconfig.resource.json b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/tsconfig.resource.json
new file mode 100644
index 0000000000..6504da8028
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/APIGW/tsconfig.resource.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "module": "commonjs",
+ "strict": false,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "./",
+ "rootDir": "../"
+ },
+ "include": ["../**/*"]
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/override.ts.sample b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/override.ts.sample
new file mode 100644
index 0000000000..936014b287
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/override.ts.sample
@@ -0,0 +1,5 @@
+import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper';
+
+export function override(resources: AmplifyApiGraphQlResourceStackTemplate) {
+
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/package.json b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/package.json
new file mode 100644
index 0000000000..33e7fd156b
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "overrides",
+ "version": "1.0.0",
+ "description": "",
+ "scripts": {
+ "build": "tsc",
+ "watch": "tsc -w"
+ },
+ "dependencies": {
+ "@aws-amplify/cli-extensibility-helper": "^2.3.0"
+ },
+ "devDependencies": {
+ "typescript": "^4.2.4"
+ }
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/tsconfig.json b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/tsconfig.json
new file mode 100644
index 0000000000..9a070be831
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "module": "commonjs",
+ "strict": false,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "build"
+ }
+}
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/tsconfig.resource.json b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/tsconfig.resource.json
new file mode 100644
index 0000000000..6504da8028
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/overrides-resource/AppSync/tsconfig.resource.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "target": "es6",
+ "module": "commonjs",
+ "strict": false,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "outDir": "./",
+ "rootDir": "../"
+ },
+ "include": ["../**/*"]
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/resolver-readme/RESOLVER_README.md b/packages/amplify-category-api/resources/awscloudformation/resolver-readme/RESOLVER_README.md
new file mode 100644
index 0000000000..89e564c5b3
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/resolver-readme/RESOLVER_README.md
@@ -0,0 +1,2 @@
+Any resolvers that you add in this directory will override the ones automatically generated by Amplify CLI and will be directly copied to the cloud.
+For more information, visit [https://docs.amplify.aws/cli/graphql-transformer/resolvers](https://docs.amplify.aws/cli/graphql-transformer/resolvers)
\ No newline at end of file
diff --git a/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-index.js.ejs b/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-index.js.ejs
new file mode 100644
index 0000000000..7034c711ce
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-index.js.ejs
@@ -0,0 +1,37 @@
+// This is sample code. Please update this to suite your schema
+
+exports.handler = async (event, context, callback) => {
+ console.log('Received event {}', JSON.stringify(event, 3));
+ let action, item;
+ switch (event.resolver.field) {
+ case 'updatePost':
+ if (event.existingItem.postId === '1') {
+ action = 'RESOLVE';
+ item = event.newItem;
+ } else {
+ action = 'REJECT';
+ }
+ break;
+ case 'deletePost':
+ if (event.existingItem.postId === '1') {
+ action = 'REMOVE';
+ } else {
+ action = 'REJECT';
+ }
+ break;
+ case 'addPost':
+ if (event.existingItem.postId === '1') {
+ action = 'RESOLVE';
+ item = event.newItem;
+ } else {
+ action = 'REJECT';
+ }
+ break;
+ default:
+ throw new Error('Unknown Resolver');
+ }
+ return {
+ action,
+ item,
+ };
+};
diff --git a/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-package.json.ejs b/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-package.json.ejs
new file mode 100644
index 0000000000..084c06bc3d
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-package.json.ejs
@@ -0,0 +1,6 @@
+{
+ "name": "<%= props.functionName %>",
+ "version": "1.0.0",
+ "description": "Lambda function generated by Amplify for conflict handling",
+ "main": "index.js"
+}
diff --git a/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-template.json.ejs b/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-template.json.ejs
new file mode 100644
index 0000000000..f898077cbd
--- /dev/null
+++ b/packages/amplify-category-api/resources/awscloudformation/sync-conflict-handler/sync-conflict-handler-template.json.ejs
@@ -0,0 +1,197 @@
+{
+ "AWSTemplateFormatVersion": "2010-09-09",
+ "Description": "Lambda resource stack creation using Amplify CLI",
+ "Parameters": {
+ "env": {
+ "Type": "String"
+ }<%if (props.dependsOn && props.dependsOn.length > 0) { %>,<% } %>
+ <% if (props.dependsOn) { %>
+ <% for(var i=0; i < props.dependsOn.length; i++) { %>
+ <% for(var j=0; j < props.dependsOn[i].attributes.length; j++) { %>
+ "<%= props.dependsOn[i].category %><%= props.dependsOn[i].resourceName %><%= props.dependsOn[i].attributes[j] %>": {
+ "Type": "String",
+ "Default": "<%= props.dependsOn[i].category %><%= props.dependsOn[i].resourceName %><%= props.dependsOn[i].attributes[j] %>"
+ }<%if (i !== props.dependsOn.length - 1 || j !== props.dependsOn[i].attributes.length - 1) { %>,<% } %>
+ <% } %>
+ <% } %>
+ <% } %>
+ },
+ "Conditions": {
+ "ShouldNotCreateEnvResources": {
+ "Fn::Equals": [
+ {
+ "Ref": "env"
+ },
+ "NONE"
+ ]
+ }
+ },
+ "Resources": {
+ "LambdaFunction": {
+ "Type": "AWS::Lambda::Function",
+ "Metadata": {
+ "aws:asset:path": "./src",
+ "aws:asset:property": "Code"
+ },
+ "Properties": {
+ "Handler": "index.handler",
+ "FunctionName": {
+ "Fn::If": [
+ "ShouldNotCreateEnvResources",
+ "<%= props.functionName %>",
+ {
+
+ "Fn::Join": [
+ "",
+ [
+ "<%= props.functionName %>",
+ "-",
+ {
+ "Ref": "env"
+ }
+ ]
+ ]
+ }
+ ]
+ },
+ "Environment": {
+ "Variables" : {
+ "ENV": {
+ "Ref": "env"
+ },
+ "REGION": {
+ "Ref": "AWS::Region"
+ }
+ <% if (props.resourceProperties && props.resourceProperties.length > 0) { %>,<%- props.resourceProperties%> <% } %>
+ }
+ },
+ "Role": { "Fn::GetAtt" : ["LambdaExecutionRole", "Arn"] },
+ "Runtime": "nodejs14.x",
+ "Timeout": 25
+ }
+ },
+ "LambdaExecutionRole": {
+ "Type": "AWS::IAM::Role",
+ "Properties": {
+ "RoleName": {
+ "Fn::If": [
+ "ShouldNotCreateEnvResources",
+ "<%=props.roleName %>",
+ {
+
+ "Fn::Join": [
+ "",
+ [
+ "<%=props.roleName %>",
+ "-",
+ {
+ "Ref": "env"
+ }
+ ]
+ ]
+ }
+ ]
+ },
+ "AssumeRolePolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Principal": {
+ "Service": [
+ "lambda.amazonaws.com"
+ ]
+ },
+ "Action": [
+ "sts:AssumeRole"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ,"lambdaexecutionpolicy": {
+ "DependsOn": ["LambdaExecutionRole"],
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": "lambda-execution-policy",
+ "Roles": [{ "Ref": "LambdaExecutionRole" }],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Effect": "Allow",
+ "Action":["logs:CreateLogGroup",
+ "logs:CreateLogStream",
+ "logs:PutLogEvents"],
+ "Resource": { "Fn::Sub" : [ "arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*", { "region": {"Ref": "AWS::Region"}, "account": {"Ref": "AWS::AccountId"}, "lambda": {"Ref": "LambdaFunction"}} ]}
+ }<% if (props.database && props.database.resourceName) { %>,
+ {
+ "Effect": "Allow",
+ "Action": ["dynamodb:GetItem","dynamodb:Query","dynamodb:Scan","dynamodb:PutItem","dynamodb:UpdateItem","dynamodb:DeleteItem"],
+ "Resource": [
+ <% if (props.database && props.database.Arn) { %>
+ "<%= props.database.Arn %>",
+ {
+ "Fn::Join": [
+ "/",
+ [
+ "<%= props.database.Arn %>",
+ "index/*"
+ ]
+ ]
+ }
+ <% } else { %>
+ { "Ref": "storage<%= props.database.resourceName %>Arn" },
+ {
+ "Fn::Join": [
+ "/",
+ [
+ { "Ref": "storage<%= props.database.resourceName %>Arn" },
+ "index/*"
+ ]
+ ]
+ }
+ <% } %>
+ ]
+ }
+ <% } %>
+ ]
+ }
+ }
+ }<% if (props.categoryPolicies && props.categoryPolicies.length > 0 ) { %>
+ ,"AmplifyResourcesPolicy": {
+ "DependsOn": ["LambdaExecutionRole"],
+ "Type": "AWS::IAM::Policy",
+ "Properties": {
+ "PolicyName": "amplify-lambda-execution-policy",
+ "Roles": [{ "Ref": "LambdaExecutionRole" }],
+ "PolicyDocument": {
+ "Version": "2012-10-17",
+ "Statement": <%- JSON.stringify(props.categoryPolicies) %>
+ }
+ }
+ }
+ <% } %>
+ },
+ "Outputs": {
+ "Name": {
+ "Value": {
+ "Ref": "LambdaFunction"
+ }
+ },
+ "Arn": {
+ "Value": {"Fn::GetAtt": ["LambdaFunction", "Arn"]}
+ },
+ "Region": {
+ "Value": {
+ "Ref": "AWS::Region"
+ }
+ },
+ "LambdaExecutionRole": {
+ "Value": {
+ "Ref": "LambdaExecutionRole"
+ }
+ }
+ }
+}
diff --git a/packages/amplify-category-api/resources/schemas/aPIGateway/APIGatewayCLIInputs.schema.json b/packages/amplify-category-api/resources/schemas/aPIGateway/APIGatewayCLIInputs.schema.json
new file mode 100644
index 0000000000..30f1827ea5
--- /dev/null
+++ b/packages/amplify-category-api/resources/schemas/aPIGateway/APIGatewayCLIInputs.schema.json
@@ -0,0 +1,65 @@
+{
+ "description": "Defines the json object expected by the amplify api category",
+ "type": "object",
+ "properties": {
+ "version": {
+ "description": "The schema version.",
+ "type": "number",
+ "enum": [1]
+ },
+ "paths": {
+ "description": "map of paths in the REST API.",
+ "type": "object",
+ "additionalProperties": {
+ "type": "object",
+ "properties": {
+ "lambdaFunction": {
+ "type": "string"
+ },
+ "permissions": {
+ "type": "object",
+ "properties": {
+ "setting": {
+ "$ref": "#/definitions/PermissionSetting"
+ },
+ "auth": {
+ "type": "array",
+ "items": {
+ "enum": ["create", "delete", "read", "update"],
+ "type": "string"
+ }
+ },
+ "guest": {
+ "type": "array",
+ "items": {
+ "enum": ["create", "delete", "read", "update"],
+ "type": "string"
+ }
+ },
+ "groups": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "array",
+ "items": {
+ "enum": ["create", "delete", "read", "update"],
+ "type": "string"
+ }
+ }
+ }
+ },
+ "required": ["setting"]
+ }
+ },
+ "required": ["lambdaFunction", "permissions"]
+ }
+ }
+ },
+ "required": ["paths", "version"],
+ "definitions": {
+ "PermissionSetting": {
+ "enum": ["open", "private", "protected"],
+ "type": "string"
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
diff --git a/packages/amplify-category-api/resources/schemas/appsync/AppSyncCLIInputs.schema.json b/packages/amplify-category-api/resources/schemas/appsync/AppSyncCLIInputs.schema.json
new file mode 100644
index 0000000000..cb286b6dd0
--- /dev/null
+++ b/packages/amplify-category-api/resources/schemas/appsync/AppSyncCLIInputs.schema.json
@@ -0,0 +1,289 @@
+{
+ "description": "Defines the json object expected by `amplify api category",
+ "type": "object",
+ "properties": {
+ "version": {
+ "description": "The schema version.",
+ "type": "number",
+ "enum": [1]
+ },
+ "serviceConfiguration": {
+ "$ref": "#/definitions/AppSyncServiceConfig",
+ "description": "The service configuration that will be interpreted by Amplify."
+ }
+ },
+ "required": ["serviceConfiguration", "version"],
+ "definitions": {
+ "AppSyncServiceConfig": {
+ "description": "Configuration exposed by AppSync. Currently this is the only API type supported by Amplify headless mode.",
+ "type": "object",
+ "properties": {
+ "serviceName": {
+ "description": "The service name of the resource provider.",
+ "type": "string",
+ "enum": ["AppSync"]
+ },
+ "apiName": {
+ "description": "The name of the API that will be created.",
+ "type": "string"
+ },
+ "gqlSchemaPath": {
+ "description": "Path to GraphQL schema that defines the AppSync API.",
+ "type": "string"
+ },
+ "defaultAuthType": {
+ "description": "The auth type that will be used by default.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/AppSyncAPIKeyAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncAWSIAMAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncCognitoUserPoolsAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncOpenIDConnectAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncLambdaAuthType"
+ }
+ ]
+ },
+ "additionalAuthTypes": {
+ "description": "Additional methods of authenticating API requests.",
+ "type": "array",
+ "items": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/AppSyncAPIKeyAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncAWSIAMAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncCognitoUserPoolsAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncOpenIDConnectAuthType"
+ },
+ {
+ "$ref": "#/definitions/AppSyncLambdaAuthType"
+ }
+ ]
+ }
+ },
+ "conflictResolution": {
+ "$ref": "#/definitions/ConflictResolution",
+ "description": "The strategy for resolving API write conflicts."
+ }
+ },
+ "required": ["apiName", "defaultAuthType", "gqlSchemaPath", "serviceName"]
+ },
+ "AppSyncAPIKeyAuthType": {
+ "description": "Specifies that the AppSync API should be secured using an API key.",
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": ["API_KEY"]
+ },
+ "expirationTime": {
+ "type": "number"
+ },
+ "apiKeyExpirationDate": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "keyDescription": {
+ "type": "string"
+ }
+ },
+ "required": ["mode"]
+ },
+ "AppSyncAWSIAMAuthType": {
+ "description": "Specifies that the AppSync API should be secured using AWS IAM.",
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": ["AWS_IAM"]
+ }
+ },
+ "required": ["mode"]
+ },
+ "AppSyncCognitoUserPoolsAuthType": {
+ "description": "Specifies that the AppSync API should be secured using Cognito.",
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": ["AMAZON_COGNITO_USER_POOLS"]
+ },
+ "cognitoUserPoolId": {
+ "description": "The user pool that will be used to authenticate requests.",
+ "type": "string"
+ }
+ },
+ "required": ["mode"]
+ },
+ "AppSyncOpenIDConnectAuthType": {
+ "description": "Specifies that the AppSync API should be secured using OpenID.",
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": ["OPENID_CONNECT"]
+ },
+ "openIDProviderName": {
+ "type": "string"
+ },
+ "openIDIssuerURL": {
+ "type": "string"
+ },
+ "openIDClientID": {
+ "type": "string"
+ },
+ "openIDAuthTTL": {
+ "type": "string"
+ },
+ "openIDIatTTL": {
+ "type": "string"
+ }
+ },
+ "required": ["mode", "openIDClientID", "openIDIssuerURL", "openIDProviderName"]
+ },
+ "AppSyncLambdaAuthType": {
+ "description": "Specifies that the AppSync API should be secured using Lambda.",
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": ["AWS_LAMBDA"]
+ },
+ "lambdaFunction": {
+ "type": "string"
+ },
+ "ttlSeconds": {
+ "type": "string"
+ }
+ },
+ "required": ["lambdaFunction", "mode"]
+ },
+ "ConflictResolution": {
+ "description": "Defines a strategy for resolving API write conflicts.",
+ "type": "object",
+ "properties": {
+ "defaultResolutionStrategy": {
+ "description": "The strategy that will be used for all models by default.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/PredefinedResolutionStrategy"
+ },
+ {
+ "$ref": "#/definitions/LambdaResolutionStrategy"
+ }
+ ]
+ },
+ "perModelResolutionStrategy": {
+ "description": "Strategies that will be used for individual models.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/PerModelResolutionstrategy"
+ }
+ }
+ }
+ },
+ "PredefinedResolutionStrategy": {
+ "description": "Resolution strategies provided by AppSync. See https://docs.aws.amazon.com/appsync/latest/devguide/conflict-detection-and-sync.html for details.",
+ "type": "object",
+ "properties": {
+ "type": {
+ "enum": ["AUTOMERGE", "NONE", "OPTIMISTIC_CONCURRENCY"],
+ "type": "string"
+ }
+ },
+ "required": ["type"]
+ },
+ "LambdaResolutionStrategy": {
+ "description": "Resolution strategy using a custom lambda function.",
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["LAMBDA"]
+ },
+ "resolver": {
+ "description": "The lambda function used to resolve conflicts.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/NewLambdaConflictResolver"
+ },
+ {
+ "$ref": "#/definitions/ExistingLambdaConflictResolver"
+ }
+ ]
+ }
+ },
+ "required": ["resolver", "type"]
+ },
+ "NewLambdaConflictResolver": {
+ "description": "Defines a new lambda conflict resolver. Using this resolver type will create a new lambda function with boilerplate resolver logic.",
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["NEW"]
+ }
+ },
+ "required": ["type"]
+ },
+ "ExistingLambdaConflictResolver": {
+ "description": "Defines an lambda conflict resolver that uses an existing lambda function.",
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["EXISTING"]
+ },
+ "name": {
+ "description": "The name of the lambda function (this must be a lambda function that exists in the Amplify project).",
+ "type": "string"
+ },
+ "region": {
+ "description": "The lambda function region.",
+ "type": "string"
+ },
+ "arn": {
+ "description": "A lambda function ARN. This could be an ARN outside of the Amplify project but in that case extra care must be taken to ensure the AppSync API has access to the Lambda.",
+ "type": "string"
+ }
+ },
+ "required": ["name", "type"]
+ },
+ "PerModelResolutionstrategy": {
+ "description": "Defines a resolution strategy for a single model.",
+ "type": "object",
+ "properties": {
+ "resolutionStrategy": {
+ "description": "The resolution strategy for the model.",
+ "anyOf": [
+ {
+ "$ref": "#/definitions/PredefinedResolutionStrategy"
+ },
+ {
+ "$ref": "#/definitions/LambdaResolutionStrategy"
+ }
+ ]
+ },
+ "entityName": {
+ "description": "The model name.",
+ "type": "string"
+ }
+ },
+ "required": ["entityName", "resolutionStrategy"]
+ }
+ },
+ "$schema": "http://json-schema.org/draft-07/schema#"
+}
diff --git a/packages/amplify-category-api/scripts/generateApiSchemas.ts b/packages/amplify-category-api/scripts/generateApiSchemas.ts
new file mode 100644
index 0000000000..8a79b9d65d
--- /dev/null
+++ b/packages/amplify-category-api/scripts/generateApiSchemas.ts
@@ -0,0 +1,17 @@
+import { AmplifySupportedService, CLIInputSchemaGenerator, TypeDef } from 'amplify-cli-core';
+
+const AppsyncApiTypeDef: TypeDef = {
+ typeName: 'AppSyncCLIInputs',
+ service: AmplifySupportedService.APPSYNC,
+};
+
+const ApigwTypeDef: TypeDef = {
+ typeName: 'APIGatewayCLIInputs',
+ service: AmplifySupportedService.APIGW,
+};
+
+// Defines the type names and the paths to the TS files that define them
+const apiCategoryTypeDefs: TypeDef[] = [AppsyncApiTypeDef, ApigwTypeDef];
+
+const schemaGenerator = new CLIInputSchemaGenerator(apiCategoryTypeDefs);
+schemaGenerator.generateJSONSchemas(); // convert CLI input data into json schemas.
diff --git a/packages/amplify-category-api/src/__tests__/commands/api/add-graphql-datasource.test.ts b/packages/amplify-category-api/src/__tests__/commands/api/add-graphql-datasource.test.ts
new file mode 100644
index 0000000000..b0acae414d
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/commands/api/add-graphql-datasource.test.ts
@@ -0,0 +1,22 @@
+import { readSchema } from '../../../commands/api/add-graphql-datasource';
+import * as path from 'path';
+
+describe('read schema', () => {
+ it('Valid schema present in folder', async () => {
+ const graphqlSchemaPath = path.join(__dirname, 'mock-data', 'schema.graphql');
+ expect(readSchema(graphqlSchemaPath)).toBeDefined();
+ });
+
+ it('Invalid schema present in folder', async () => {
+ function invalidSchema() {
+ const graphqlSchemaPath = path.join(__dirname, 'mock-data', 'invalid_schema.graphql');
+ readSchema(graphqlSchemaPath);
+ }
+ expect(invalidSchema).toThrowError('Could not parse graphql schema');
+ });
+
+ it('Empty schema present in folder', async () => {
+ const graphqlSchemaPath = path.join(__dirname, 'mock-data', 'empty_schema.graphql');
+ expect(readSchema(graphqlSchemaPath)).toBeNull();
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/commands/api/mock-data/empty_schema.graphql b/packages/amplify-category-api/src/__tests__/commands/api/mock-data/empty_schema.graphql
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/amplify-category-api/src/__tests__/commands/api/mock-data/invalid_schema.graphql b/packages/amplify-category-api/src/__tests__/commands/api/mock-data/invalid_schema.graphql
new file mode 100644
index 0000000000..4d56fbd77d
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/commands/api/mock-data/invalid_schema.graphql
@@ -0,0 +1,7 @@
+typo Todo @model {
+ id: ID!
+ name: String!
+ description: String
+ createdAt: AWSDateTime!
+ updatedAt: AWSDateTime!
+}
\ No newline at end of file
diff --git a/packages/amplify-category-api/src/__tests__/commands/api/mock-data/schema.graphql b/packages/amplify-category-api/src/__tests__/commands/api/mock-data/schema.graphql
new file mode 100644
index 0000000000..619867da0a
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/commands/api/mock-data/schema.graphql
@@ -0,0 +1,7 @@
+type Todo @model {
+ id: ID!
+ name: String!
+ description: String
+ createdAt: AWSDateTime!
+ updatedAt: AWSDateTime!
+}
diff --git a/packages/amplify-category-api/src/__tests__/commands/api/rebuild.test.ts b/packages/amplify-category-api/src/__tests__/commands/api/rebuild.test.ts
new file mode 100644
index 0000000000..ce28470ecd
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/commands/api/rebuild.test.ts
@@ -0,0 +1,64 @@
+import { $TSContext, FeatureFlags, stateManager } from 'amplify-cli-core';
+import { printer, prompter } from 'amplify-prompts';
+import { mocked } from 'ts-jest/utils';
+import { run } from '../../../commands/api/rebuild';
+
+jest.mock('amplify-cli-core');
+jest.mock('amplify-prompts');
+
+const FeatureFlags_mock = mocked(FeatureFlags);
+const stateManager_mock = mocked(stateManager);
+const printer_mock = mocked(printer);
+const prompter_mock = mocked(prompter);
+
+FeatureFlags_mock.getBoolean.mockReturnValue(true);
+
+beforeEach(jest.clearAllMocks);
+
+const pushResourcesMock = jest.fn();
+
+const context_stub = {
+ amplify: {
+ constructExeInfo: jest.fn(),
+ pushResources: pushResourcesMock,
+ },
+ parameters: {
+ first: 'resourceName',
+ },
+} as unknown as $TSContext;
+
+it('prints error if iterative updates not enabled', async () => {
+ FeatureFlags_mock.getBoolean.mockReturnValueOnce(false);
+
+ await run(context_stub);
+
+ expect(printer_mock.error.mock.calls.length).toBe(1);
+ expect(pushResourcesMock.mock.calls.length).toBe(0);
+});
+
+it('exits early if no api in project', async () => {
+ stateManager_mock.getMeta.mockReturnValueOnce({
+ api: {},
+ });
+
+ await run(context_stub);
+
+ expect(printer_mock.info.mock.calls.length).toBe(1);
+ expect(pushResourcesMock.mock.calls.length).toBe(0);
+});
+
+it('asks for strong confirmation before continuing', async () => {
+ stateManager_mock.getMeta.mockReturnValueOnce({
+ api: {
+ testapiname: {
+ service: 'AppSync',
+ },
+ },
+ });
+
+ await run(context_stub);
+
+ expect(prompter_mock.input.mock.calls.length).toBe(1);
+ expect(pushResourcesMock.mock.calls.length).toBe(1);
+ expect(pushResourcesMock.mock.calls[0][4]).toBe(true); // rebuild flag is set
+});
diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/api-key-helpers.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/api-key-helpers.test.ts
new file mode 100644
index 0000000000..8dad60e4d2
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/api-key-helpers.test.ts
@@ -0,0 +1,24 @@
+import { ApiKeyConfig } from '@aws-amplify/graphql-transformer-interfaces';
+import { hasApiKey } from '../../graphql-transformer/api-key-helpers';
+
+jest.mock('amplify-cli-core', () => {
+ const original = jest.requireActual('amplify-cli-core');
+ return {
+ ...original,
+ ApiCategoryFacade: {
+ getApiKeyConfig: jest.fn(() => ({
+ apiKeyExpirationDays: 2,
+ apiKeyExpirationDate: new Date('2021-08-20T20:38:07.585Z'),
+ description: '',
+ } as ApiKeyConfig)),
+ },
+ };
+});
+
+describe('hasApiKey', () => {
+ describe('if api key config is present', () => {
+ it('returns true if api key is present', () => {
+ expect(hasApiKey(expect.anything())).toBeTruthy();
+ });
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/graphql-push-schema-checks.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/graphql-push-schema-checks.test.ts
new file mode 100644
index 0000000000..449939fc49
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/graphql-push-schema-checks.test.ts
@@ -0,0 +1,191 @@
+import { stateManager, getGraphQLTransformerOpenSearchProductionDocLink, ApiCategoryFacade } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import { searchablePushChecks } from '../../graphql-transformer/api-utils';
+
+jest.mock('amplify-cli-core');
+jest.mock('amplify-prompts');
+
+const printerMock = printer as jest.Mocked;
+const stateManagerMock = stateManager as jest.Mocked;
+const getTransformerVersionMock = ApiCategoryFacade.getTransformerVersion as jest.MockedFunction
+const getGraphQLTransformerOpenSearchProductionDocLinkMock = getGraphQLTransformerOpenSearchProductionDocLink as jest.MockedFunction<
+ typeof getGraphQLTransformerOpenSearchProductionDocLink
+>;
+printerMock.warn.mockImplementation(jest.fn());
+getGraphQLTransformerOpenSearchProductionDocLinkMock.mockReturnValue('mockDocsLink');
+// use transformer v2 for tests
+getTransformerVersionMock.mockReturnValue(new Promise(resolve => resolve(2)));
+
+describe('graphql schema checks', () => {
+ const contextMock = {
+ amplify: {
+ getEnvInfo: jest.fn(),
+ },
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should warn users if they use not recommended open search instance without overrides', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({});
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'test' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).lastCalledWith(
+ 'Your instance type for OpenSearch is t2.small.elasticsearch, you may experience performance issues or data loss. Consider reconfiguring with the instructions here mockDocsLink',
+ );
+ });
+
+ it('should warn users if they use not recommended open search instance with overrides', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({
+ test: {
+ categories: {
+ api: {
+ test_api_name: {
+ OpenSearchInstanceType: 't2.small.elasticsearch',
+ },
+ },
+ },
+ },
+ });
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'test' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).lastCalledWith(
+ 'Your instance type for OpenSearch is t2.small.elasticsearch, you may experience performance issues or data loss. Consider reconfiguring with the instructions here mockDocsLink',
+ );
+ });
+
+ it('should warn users if they use not recommended elastic search instance with overrides', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({
+ test: {
+ categories: {
+ api: {
+ test_api_name: {
+ ElasticSearchInstanceType: 't2.small.elasticsearch',
+ },
+ },
+ },
+ },
+ });
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'test' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).lastCalledWith(
+ 'Your instance type for OpenSearch is t2.small.elasticsearch, you may experience performance issues or data loss. Consider reconfiguring with the instructions here mockDocsLink',
+ );
+ });
+
+ it('should NOT warn users if they use recommended open search instance', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({
+ test: {
+ categories: {
+ api: {
+ test_api_name: {
+ OpenSearchInstanceType: 't2.medium.elasticsearch',
+ },
+ },
+ },
+ },
+ });
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'test' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).not.toBeCalled();
+ });
+
+ it('should NOT warn users if they use recommended elastic search instance', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({
+ test: {
+ categories: {
+ api: {
+ test_api_name: {
+ ElasticSearchInstanceType: 't2.medium.elasticsearch',
+ },
+ },
+ },
+ },
+ });
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'test' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).not.toBeCalled();
+ });
+
+ it('should NOT warn users if they use recommended open search instance on the environment', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({
+ dev: {
+ categories: {
+ api: {
+ test_api_name: {
+ OpenSearchInstanceType: 't2.small.elasticsearch',
+ },
+ },
+ },
+ },
+ prod: {
+ categories: {
+ api: {
+ test_api_name: {
+ OpenSearchInstanceType: 't2.medium.elasticsearch',
+ },
+ },
+ },
+ },
+ });
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'prod' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).not.toBeCalled();
+ });
+
+ it('should NOT warn users if they use recommended elastic search instance on the environment', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({
+ dev: {
+ categories: {
+ api: {
+ test_api_name: {
+ ElasticSearchInstanceType: 't2.small.elasticsearch',
+ },
+ },
+ },
+ },
+ prod: {
+ categories: {
+ api: {
+ test_api_name: {
+ ElasticSearchInstanceType: 't2.medium.elasticsearch',
+ },
+ },
+ },
+ },
+ });
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'prod' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).not.toBeCalled();
+ });
+
+ it('should NOT warn users if they do NOT use searchable', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({});
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'test' });
+ const map = { Post: ['model'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).not.toBeCalled();
+ });
+
+ it('should warn users if they use not recommended open search instance with overrides', async () => {
+ stateManagerMock.getTeamProviderInfo.mockReturnValue({
+ test: {
+ categories: {},
+ },
+ });
+ contextMock.amplify.getEnvInfo.mockReturnValue({ envName: 'test' });
+ const map = { Post: ['model', 'searchable'] };
+ await searchablePushChecks(contextMock, map, 'test_api_name');
+ expect(printerMock.warn).lastCalledWith(
+ 'Your instance type for OpenSearch is t2.small.elasticsearch, you may experience performance issues or data loss. Consider reconfiguring with the instructions here mockDocsLink',
+ );
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/sandbox-mode-helpers.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/sandbox-mode-helpers.test.ts
new file mode 100644
index 0000000000..71c1c499e2
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/sandbox-mode-helpers.test.ts
@@ -0,0 +1,138 @@
+import { $TSContext } from 'amplify-cli-core';
+import chalk from 'chalk';
+import * as prompts from 'amplify-prompts';
+import { showSandboxModePrompts, showGlobalSandboxModeWarning, schemaHasSandboxModeEnabled } from '../../graphql-transformer/sandbox-mode-helpers';
+import * as apiKeyHelpers from '../../graphql-transformer/api-key-helpers';
+
+let ctx;
+let apiKeyPresent = true;
+
+describe('sandbox mode helpers', () => {
+ beforeEach(() => {
+ const envName = 'dev';
+ ctx = {
+ amplify: {
+ getEnvInfo() {
+ return { envName };
+ },
+ invokePluginMethod: jest.fn(),
+ },
+ } as unknown as $TSContext;
+
+ jest.spyOn(prompts.printer, 'info').mockImplementation();
+ jest.spyOn(apiKeyHelpers, 'hasApiKey').mockResolvedValue(apiKeyPresent);
+ });
+
+ describe('showSandboxModePrompts', () => {
+ describe('missing api key', () => {
+ beforeAll(() => {
+ apiKeyPresent = false;
+ });
+
+ it('displays warning', async () => {
+ await showSandboxModePrompts(ctx);
+
+ expect(prompts.printer.info).toBeCalledWith(
+ `
+⚠️ WARNING: Global Sandbox Mode has been enabled, which requires a valid API key. If
+you'd like to disable, remove ${chalk.green('"input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }"')}
+from your GraphQL schema and run 'amplify push' again. If you'd like to proceed with
+sandbox mode disabled, do not create an API Key.
+`,
+ 'yellow',
+ );
+ expect(ctx.amplify.invokePluginMethod).toBeCalledWith(ctx, 'api', undefined, 'promptToAddApiKey', [ctx]);
+ });
+ });
+ });
+
+ describe('showGlobalSandboxModeWarning', () => {
+ it('prints sandbox api key message', () => {
+ showGlobalSandboxModeWarning('mockLink');
+
+ expect(prompts.printer.info).toBeCalledWith(
+ `
+⚠️ WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: mockLink
+`,
+ 'yellow',
+ );
+ });
+ });
+
+ describe('schemaHasSandboxModeEnabled', () => {
+ it('parses sandbox AMPLIFY input on schema', () => {
+ const schema = `
+ input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }
+ `;
+
+ expect(schemaHasSandboxModeEnabled(schema, 'mockDocLink')).toEqual(true);
+ });
+
+ it('passes through when AMPLIFY input is not present', () => {
+ const schema = `
+ type Todo @model {
+ id: ID!
+ content: String
+ }
+ `;
+
+ expect(schemaHasSandboxModeEnabled(schema, 'mockDocLink')).toEqual(false);
+ });
+
+ describe('input AMPLIFY has incorrect values', () => {
+ it('checks for "globalAuthRule"', () => {
+ const schema = `
+ input AMPLIFY { auth_rule: AuthenticationRule = { allow: public } }
+ `;
+
+ expect(() => schemaHasSandboxModeEnabled(schema, 'mockLink')).toThrow(
+ Error('input AMPLIFY requires "globalAuthRule" field. Learn more here: mockLink'),
+ );
+ });
+
+ it('allows "global_auth_rule"', () => {
+ const schema = `
+ input AMPLIFY { global_auth_rule: AuthRule = { allow: public } }
+ `;
+
+ expect(schemaHasSandboxModeEnabled(schema, 'mockDocLink')).toEqual(true);
+ });
+
+ it('guards for AuthRule', () => {
+ const schema = `
+ input AMPLIFY { globalAuthRule: AuthenticationRule = { allow: public } }
+ `;
+
+ expect(() => schemaHasSandboxModeEnabled(schema, 'mockLink')).toThrow(
+ Error(
+ 'There was a problem with your auth configuration. Learn more about auth here: mockLink',
+ ),
+ );
+ });
+
+ it('checks for "allow" field name', () => {
+ const schema = `
+ input AMPLIFY { globalAuthRule: AuthRule = { allows: public } }
+ `;
+
+ expect(() => schemaHasSandboxModeEnabled(schema, 'mockLink')).toThrow(
+ Error(
+ 'There was a problem with your auth configuration. Learn more about auth here: mockLink',
+ ),
+ );
+ });
+
+ it('checks for "public" value from "allow" field', () => {
+ const schema = `
+ input AMPLIFY { globalAuthRule: AuthRule = { allow: private } }
+ `;
+
+ expect(() => schemaHasSandboxModeEnabled(schema, 'mockLink')).toThrowError(
+ Error(
+ 'There was a problem with your auth configuration. Learn more about auth here: mockLink',
+ ),
+ );
+ });
+ });
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/transformer-feature-flag-adapter.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/transformer-feature-flag-adapter.test.ts
new file mode 100644
index 0000000000..0855a9efa4
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/transformer-feature-flag-adapter.test.ts
@@ -0,0 +1,94 @@
+import { AmplifyCLIFeatureFlagAdapter } from '../../graphql-transformer/amplify-cli-feature-flag-adapter';
+import { FeatureFlags } from 'amplify-cli-core';
+
+jest.mock('amplify-cli-core');
+
+describe('AmplifyCLIFeatureFlagAdapter', () => {
+ const ff = new AmplifyCLIFeatureFlagAdapter();
+ const transformerFeatureFlagPrefix = 'graphQLTransformer';
+
+ describe('getBoolean', () => {
+ test('test getBoolean to return default value', () => {
+ (FeatureFlags.getBoolean).mockReturnValue(true);
+ const flagName = 'testFlag';
+ expect(ff.getBoolean(flagName, true)).toEqual(true);
+ expect(FeatureFlags.getBoolean).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+
+ test('test getBoolean should return defaultValue when the FF throw error', () => {
+ (FeatureFlags.getBoolean).mockImplementation(() => {
+ throw new Error('Error');
+ });
+ const flagName = 'testFlag';
+ expect(ff.getBoolean(flagName, true)).toEqual(true);
+ expect(FeatureFlags.getBoolean).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+
+ test('test getBoolean should throw error when defaultValue is missing and the FF throw error', () => {
+ (FeatureFlags.getBoolean).mockImplementation(() => {
+ throw new Error('Error');
+ });
+ const flagName = 'testFlag';
+ expect(() => ff.getBoolean(flagName)).toThrowError();
+ expect(FeatureFlags.getBoolean).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+ });
+
+ describe('getString', () => {
+ test('test getString to return default value', () => {
+ const expectedValue = 'StrValue';
+ (FeatureFlags.getString).mockReturnValue(expectedValue);
+ const flagName = 'testFlag';
+ expect(ff.getString(flagName, 'some other value')).toEqual(expectedValue);
+ expect(FeatureFlags.getString).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+
+ test('test getString should return defaultValue when the FF throw error', () => {
+ (FeatureFlags.getString).mockImplementation(() => {
+ throw new Error('Error');
+ });
+ const flagName = 'testFlag';
+ const expectedValue = 'StrValue';
+ expect(ff.getString(flagName, expectedValue)).toEqual(expectedValue);
+ expect(FeatureFlags.getString).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+
+ test('test getString should throw error when defaultValue is missing and the FF throw error', () => {
+ (FeatureFlags.getString).mockImplementation(() => {
+ throw new Error('Error');
+ });
+ const flagName = 'testFlag';
+ expect(() => ff.getString(flagName)).toThrowError();
+ expect(FeatureFlags.getString).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+ });
+
+ describe('getNumber', () => {
+ test('test getNumber to return default value', () => {
+ const expectedValue = 22;
+ (FeatureFlags.getNumber).mockReturnValue(expectedValue);
+ const flagName = 'testFlag';
+ expect(ff.getNumber(flagName, 12)).toEqual(expectedValue);
+ expect(FeatureFlags.getNumber).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+
+ test('test getNumber should return defaultValue when the FF throw error', () => {
+ (FeatureFlags.getNumber).mockImplementation(() => {
+ throw new Error('Error');
+ });
+ const flagName = 'testFlag';
+ const expectedValue = 44;
+ expect(ff.getNumber(flagName, expectedValue)).toEqual(expectedValue);
+ expect(FeatureFlags.getNumber).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+
+ test('test getNumber should throw error when defaultValue is missing and the FF throw error', () => {
+ (FeatureFlags.getNumber).mockImplementation(() => {
+ throw new Error('Error');
+ });
+ const flagName = 'testFlag';
+ expect(() => ff.getNumber(flagName)).toThrowError();
+ expect(FeatureFlags.getNumber).toHaveBeenCalledWith(`${transformerFeatureFlagPrefix}.${flagName}`);
+ });
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/user-defined-slots.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/user-defined-slots.test.ts
new file mode 100644
index 0000000000..13840dadc4
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/user-defined-slots.test.ts
@@ -0,0 +1,195 @@
+import { SLOT_NAMES, parseUserDefinedSlots } from '../../graphql-transformer';
+
+describe('user defined slots', () => {
+ describe('const SLOT_NAMES', () => {
+ it('has expected value', () => {
+ expect(SLOT_NAMES).toEqual(
+ new Set([
+ 'init',
+ 'preAuth',
+ 'auth',
+ 'postAuth',
+ 'preDataLoad',
+ 'preUpdate',
+ 'preSubscribe',
+ 'postDataLoad',
+ 'postUpdate',
+ 'finish',
+ ]),
+ );
+ });
+ });
+
+ describe('parseUserDefinedSlots', () => {
+ it('creates the user defined slots map', () => {
+ const resolvers = {
+ 'Query.listTodos.auth.2.req.vtl': 'template 1',
+ 'Query.getTodo.auth.2.req.vtl': 'template 2',
+ 'Query.getTodo.postAuth.2.req.vtl': 'template 3',
+ 'Mutation.createTodo.auth.2.req.vtl': 'template 4',
+ };
+
+ expect(parseUserDefinedSlots(resolvers)).toEqual({
+ 'Query.listTodos': [
+ {
+ requestResolver: {
+ fileName: 'Query.listTodos.auth.2.req.vtl',
+ template: 'template 1',
+ },
+ resolverTypeName: 'Query',
+ resolverFieldName: 'listTodos',
+ slotName: 'auth',
+ },
+ ],
+ 'Query.getTodo': [
+ {
+ requestResolver: {
+ fileName: 'Query.getTodo.auth.2.req.vtl',
+ template: 'template 2',
+ },
+ resolverTypeName: 'Query',
+ resolverFieldName: 'getTodo',
+ slotName: 'auth',
+ },
+ {
+ requestResolver: {
+ fileName: 'Query.getTodo.postAuth.2.req.vtl',
+ template: 'template 3',
+ },
+ resolverTypeName: 'Query',
+ resolverFieldName: 'getTodo',
+ slotName: 'postAuth',
+ },
+ ],
+ 'Mutation.createTodo': [
+ {
+ requestResolver: {
+ fileName: 'Mutation.createTodo.auth.2.req.vtl',
+ template: 'template 4',
+ },
+ resolverTypeName: 'Mutation',
+ resolverFieldName: 'createTodo',
+ slotName: 'auth',
+ },
+ ],
+ });
+ });
+
+ it('groups request and response resolvers in the same slot together', () => {
+ const resolvers = {
+ 'Query.getTodo.auth.1.req.vtl': 'request resolver 1',
+ 'Query.getTodo.auth.1.res.vtl': 'response resolver 1',
+ 'Mutation.createTodo.postAuth.2.req.vtl': 'request resolver 2',
+ 'Mutation.createTodo.postAuth.2.res.vtl': 'response resolver 2',
+ };
+
+ expect(parseUserDefinedSlots(resolvers)).toEqual({
+ 'Query.getTodo': [
+ {
+ requestResolver: {
+ fileName: 'Query.getTodo.auth.1.req.vtl',
+ template: 'request resolver 1',
+ },
+ responseResolver: {
+ fileName: 'Query.getTodo.auth.1.res.vtl',
+ template: 'response resolver 1',
+ },
+ resolverTypeName: 'Query',
+ resolverFieldName: 'getTodo',
+ slotName: 'auth',
+ },
+ ],
+ 'Mutation.createTodo': [
+ {
+ requestResolver: {
+ fileName: 'Mutation.createTodo.postAuth.2.req.vtl',
+ template: 'request resolver 2',
+ },
+ responseResolver: {
+ fileName: 'Mutation.createTodo.postAuth.2.res.vtl',
+ template: 'response resolver 2',
+ },
+ resolverTypeName: 'Mutation',
+ resolverFieldName: 'createTodo',
+ slotName: 'postAuth',
+ },
+ ],
+ });
+ });
+
+ it('orders multiple slot resolvers correctly', () => {
+ const resolvers = {
+ 'Query.getTodo.auth.3.req.vtl': 'request resolver 3',
+ 'Query.getTodo.auth.3.res.vtl': 'response resolver 3',
+ 'Query.getTodo.auth.1.req.vtl': 'request resolver 1',
+ 'Query.getTodo.auth.1.res.vtl': 'response resolver 1',
+ 'Query.getTodo.auth.2.req.vtl': 'request resolver 2',
+ 'Query.getTodo.auth.2.res.vtl': 'response resolver 2',
+ };
+
+ const result = parseUserDefinedSlots(resolvers);
+ expect(result).toEqual({
+ 'Query.getTodo': [
+ {
+ requestResolver: {
+ fileName: 'Query.getTodo.auth.1.req.vtl',
+ template: 'request resolver 1',
+ },
+ responseResolver: {
+ fileName: 'Query.getTodo.auth.1.res.vtl',
+ template: 'response resolver 1',
+ },
+ resolverTypeName: 'Query',
+ resolverFieldName: 'getTodo',
+ slotName: 'auth',
+ },
+ {
+ requestResolver: {
+ fileName: 'Query.getTodo.auth.2.req.vtl',
+ template: 'request resolver 2',
+ },
+ responseResolver: {
+ fileName: 'Query.getTodo.auth.2.res.vtl',
+ template: 'response resolver 2',
+ },
+ resolverTypeName: 'Query',
+ resolverFieldName: 'getTodo',
+ slotName: 'auth',
+ },
+ {
+ requestResolver: {
+ fileName: 'Query.getTodo.auth.3.req.vtl',
+ template: 'request resolver 3',
+ },
+ responseResolver: {
+ fileName: 'Query.getTodo.auth.3.res.vtl',
+ template: 'response resolver 3',
+ },
+ resolverTypeName: 'Query',
+ resolverFieldName: 'getTodo',
+ slotName: 'auth',
+ },
+ ],
+ });
+ });
+
+ it('excludes invalid slot names', () => {
+ const resolvers = {
+ 'Query.listTodos.beforeAuth.2.req.vtl': 'template 1',
+ 'Query.getTodo.beforeAuth.2.req.vtl': 'template 2',
+ 'Query.getTodo.afterAuth.2.req.vtl': 'template 3',
+ 'Mutation.createTodo.preCreate.2.req.vtl': 'template 4',
+ };
+
+ expect(parseUserDefinedSlots(resolvers)).toEqual({});
+ });
+
+ it('exclused README file', () => {
+ const resolvers = {
+ 'README.md': 'read me',
+ };
+
+ expect(parseUserDefinedSlots(resolvers)).toEqual({});
+ });
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/graphql-transformer/utils.test.ts b/packages/amplify-category-api/src/__tests__/graphql-transformer/utils.test.ts
new file mode 100644
index 0000000000..9eab0ca428
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/graphql-transformer/utils.test.ts
@@ -0,0 +1,279 @@
+import { mergeUserConfigWithTransformOutput, writeDeploymentToDisk } from '../../graphql-transformer/utils';
+import { TransformerProjectConfig, DeploymentResources } from '@aws-amplify/graphql-transformer-core';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { $TSContext, CloudformationProviderFacade } from 'amplify-cli-core';
+
+jest.mock('fs-extra');
+jest.mock('amplify-cli-core');
+
+const fs_mock = fs as jest.Mocked;
+const prePushCfnTemplateModifier_mock = jest.fn();
+
+CloudformationProviderFacade.prePushCfnTemplateModifier = prePushCfnTemplateModifier_mock;
+
+fs_mock.readdirSync.mockReturnValue([]);
+
+describe('graphql transformer utils', () => {
+ let userConfig: TransformerProjectConfig;
+ let transformerOutput: DeploymentResources;
+
+ beforeEach(() => {
+ transformerOutput = {
+ userOverriddenSlots: [],
+ resolvers: {
+ 'Query.listTodos.req.vtl': '## [Start] List Request. **\n' + '#set( $limit = $util.defaultIfNull($context.args.limit, 100) )\n',
+ },
+ pipelineFunctions: {},
+ functions: {},
+ schema: '',
+ stackMapping: {},
+ stacks: {},
+ rootStack: {
+ Parameters: {},
+ Resources: {},
+ },
+ } as DeploymentResources;
+ });
+
+ describe('writeDeploymentToDisk', () => {
+ it('executes the CFN pre-push processor on nested api stacks before writing to disk', async () => {
+ transformerOutput.stacks['TestStack'] = { Resources: { TestResource: { Type: 'testtest' } } };
+ transformerOutput.resolvers = {};
+ let hasTransformedTemplate = false;
+ let hasWrittenTransformedTemplate = false;
+ prePushCfnTemplateModifier_mock.mockImplementation(async () => {
+ hasTransformedTemplate = true;
+ });
+ fs_mock.writeFileSync.mockImplementation(filepath => {
+ if (typeof filepath === 'string' && filepath.includes(`${path.sep}stacks${path.sep}`)) {
+ if (hasTransformedTemplate) {
+ hasWrittenTransformedTemplate = true;
+ } else {
+ throw new Error('prePushCfnTemplateModifier was not applied to template before writing to disk');
+ }
+ }
+ });
+
+ const context = { amplify: {} } as unknown as $TSContext;
+ await writeDeploymentToDisk(context, transformerOutput, path.join('test', 'deployment'), undefined, {});
+ expect(hasWrittenTransformedTemplate).toBe(true);
+ });
+ });
+
+ describe('mergeUserConfigWithTransformOutput', () => {
+ describe('has user created functions', () => {
+ beforeAll(() => {
+ userConfig = {
+ schema: '',
+ functions: {
+ userFn: 'userFn()',
+ },
+ pipelineFunctions: {},
+ resolvers: {},
+ stacks: {},
+ config: { Version: 5, ElasticsearchWarning: true },
+ } as TransformerProjectConfig;
+ });
+
+ it('merges function with transform output functions', () => {
+ const { functions } = mergeUserConfigWithTransformOutput(userConfig, transformerOutput);
+
+ expect(functions['userFn']).toEqual('userFn()');
+ });
+ });
+
+ describe('has user-created resolvers', () => {
+ beforeAll(() => {
+ userConfig = {
+ schema: '',
+ functions: {},
+ pipelineFunctions: {},
+ resolvers: {
+ 'Query.listTodos.req.vtl': '$util.unauthorized\n',
+ },
+ stacks: {},
+ config: { Version: 5, ElasticsearchWarning: true },
+ } as TransformerProjectConfig;
+ });
+
+ it('merges the custom resolver with transformer output', () => {
+ const output = mergeUserConfigWithTransformOutput(userConfig, transformerOutput);
+
+ expect(output.resolvers['Query.listTodos.req.vtl']).toEqual('$util.unauthorized\n');
+ });
+ });
+
+ describe('has user created pipeline function', () => {
+ beforeAll(() => {
+ userConfig = {
+ schema: '',
+ functions: {},
+ pipelineFunctions: {
+ 'Query.listTodos.req.vtl': '$util.unauthorized\n',
+ },
+ resolvers: {},
+ stacks: {},
+ config: { Version: 5, ElasticsearchWarning: true },
+ } as TransformerProjectConfig;
+ });
+
+ it('merges custom pipeline function with transformer output', () => {
+ const { resolvers } = mergeUserConfigWithTransformOutput(userConfig, transformerOutput);
+
+ expect(resolvers['Query.listTodos.req.vtl']).toEqual('$util.unauthorized\n');
+ });
+ });
+
+ describe('has user created stacks', () => {
+ beforeAll(() => {
+ userConfig = {
+ schema: '',
+ functions: {},
+ pipelineFunctions: {},
+ resolvers: {},
+ stacks: {
+ 'CustomResources.json': {
+ Resources: {
+ QueryCommentsForTodoResolver: {
+ Type: 'AWS::AppSync::Resolver',
+ Properties: {
+ ApiId: {
+ Ref: 'AppSyncApiId',
+ },
+ DataSourceName: 'CommentTable',
+ TypeName: 'Query',
+ FieldName: 'commentsForTodo',
+ RequestMappingTemplateS3Location: {
+ 'Fn::Sub': [
+ 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.req.vtl',
+ {
+ S3DeploymentBucket: {
+ Ref: 'S3DeploymentBucket',
+ },
+ S3DeploymentRootKey: {
+ Ref: 'S3DeploymentRootKey',
+ },
+ },
+ ],
+ },
+ ResponseMappingTemplateS3Location: {
+ 'Fn::Sub': [
+ 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.res.vtl',
+ {
+ S3DeploymentBucket: {
+ Ref: 'S3DeploymentBucket',
+ },
+ S3DeploymentRootKey: {
+ Ref: 'S3DeploymentRootKey',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ Parameters: {
+ AppSyncApiId: {
+ Type: 'String',
+ Description: 'The id of the AppSync API associated with this project.',
+ },
+ AppSyncApiName: {
+ Type: 'String',
+ Description: 'The name of the AppSync API',
+ Default: 'AppSyncSimpleTransform',
+ },
+ env: {
+ Type: 'String',
+ Description: 'The environment name. e.g. Dev, Test, or Production',
+ Default: 'NONE',
+ },
+ S3DeploymentBucket: {
+ Type: 'String',
+ Description: 'The S3 bucket containing all deployment assets for the project.',
+ },
+ S3DeploymentRootKey: {
+ Type: 'String',
+ Description: 'An S3 key relative to the S3DeploymentBucket that points to the root\n' + 'of the deployment directory.',
+ },
+ },
+ },
+ },
+ config: { Version: 5, ElasticsearchWarning: true },
+ } as unknown as TransformerProjectConfig;
+ });
+
+ it('merges custom pipeline function with transformer output', () => {
+ const { stacks } = mergeUserConfigWithTransformOutput(userConfig, transformerOutput);
+
+ expect(stacks).toEqual({
+ 'CustomResources.json': {
+ Resources: {
+ QueryCommentsForTodoResolver: {
+ Type: 'AWS::AppSync::Resolver',
+ Properties: {
+ ApiId: {
+ Ref: 'AppSyncApiId',
+ },
+ DataSourceName: 'CommentTable',
+ TypeName: 'Query',
+ FieldName: 'commentsForTodo',
+ RequestMappingTemplateS3Location: {
+ 'Fn::Sub': [
+ 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.req.vtl',
+ {
+ S3DeploymentBucket: {
+ Ref: 'S3DeploymentBucket',
+ },
+ S3DeploymentRootKey: {
+ Ref: 'S3DeploymentRootKey',
+ },
+ },
+ ],
+ },
+ ResponseMappingTemplateS3Location: {
+ 'Fn::Sub': [
+ 's3://${S3DeploymentBucket}/${S3DeploymentRootKey}/pipelineFunctions/Query.commentsForTodo.res.vtl',
+ {
+ S3DeploymentBucket: {
+ Ref: 'S3DeploymentBucket',
+ },
+ S3DeploymentRootKey: {
+ Ref: 'S3DeploymentRootKey',
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ Parameters: {
+ AppSyncApiId: {
+ Type: 'String',
+ Description: 'The id of the AppSync API associated with this project.',
+ },
+ AppSyncApiName: {
+ Type: 'String',
+ Description: 'The name of the AppSync API',
+ Default: 'AppSyncSimpleTransform',
+ },
+ env: {
+ Type: 'String',
+ Description: 'The environment name. e.g. Dev, Test, or Production',
+ Default: 'NONE',
+ },
+ S3DeploymentBucket: {
+ Type: 'String',
+ Description: 'The S3 bucket containing all deployment assets for the project.',
+ },
+ S3DeploymentRootKey: {
+ Type: 'String',
+ Description: 'An S3 key relative to the S3DeploymentBucket that points to the root\n' + 'of the deployment directory.',
+ },
+ },
+ },
+ });
+ });
+ });
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/cfn-api-artifact-handler.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/cfn-api-artifact-handler.test.ts.snap
new file mode 100644
index 0000000000..3b9aa6928f
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/cfn-api-artifact-handler.test.ts.snap
@@ -0,0 +1,52 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`create artifacts creates the transform.conf.json file 1`] = `
+Array [
+ "backendDirPath/api/testApiName",
+ Object {
+ "ElasticsearchWarning": true,
+ "Version": undefined,
+ },
+]
+`;
+
+exports[`update artifacts updates additional auth if not empty 1`] = `
+Object {
+ "additionalAuthenticationProviders": Array [
+ Object {
+ "authenticationType": "AWS_IAM",
+ },
+ Object {
+ "apiKeyConfig": Object {
+ "apiKeyExpirationDate": undefined,
+ "apiKeyExpirationDays": undefined,
+ "description": undefined,
+ },
+ "authenticationType": "API_KEY",
+ },
+ ],
+ "defaultAuthentication": Object {
+ "apiKeyConfig": Object {
+ "apiKeyExpirationDays": 7,
+ "description": "",
+ },
+ "authenticationType": "API_KEY",
+ },
+}
+`;
+
+exports[`update artifacts updates default auth if not empty 1`] = `
+Object {
+ "additionalAuthenticationProviders": Array [
+ Object {
+ "authenticationType": "AMAZON_COGNITO_USER_POOLS",
+ "userPoolConfig": Object {
+ "userPoolId": "myUserPoolId",
+ },
+ },
+ ],
+ "defaultAuthentication": Object {
+ "authenticationType": "AWS_IAM",
+ },
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/legacy-add-resource.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/legacy-add-resource.test.ts.snap
new file mode 100644
index 0000000000..d07d099e0c
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/legacy-add-resource.test.ts.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`legacy add resource sets policy resource name in paths object before copying template 1`] = `
+Object {
+ "paths": Array [
+ Object {
+ "name": "/some/{path}/with/{params}",
+ "policyResourceName": "/some/*/with/*",
+ },
+ Object {
+ "name": "another/path/without/params",
+ "policyResourceName": "another/path/without/params",
+ },
+ ],
+ "resourceName": "mockResourceName",
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/legacy-update-resource.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/legacy-update-resource.test.ts.snap
new file mode 100644
index 0000000000..f44b0a4e9b
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/__snapshots__/legacy-update-resource.test.ts.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`legacy update resource sets policy resource name in paths object before copying template 1`] = `
+Object {
+ "paths": Array [
+ Object {
+ "name": "/some/{path}/with/{params}",
+ "policyResourceName": "/some/*/with/*",
+ },
+ Object {
+ "name": "another/path/without/params",
+ "policyResourceName": "another/path/without/params",
+ },
+ ],
+ "resourceName": "mockResourceName",
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/api-input-manager/appsync-api-input-state.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/api-input-manager/appsync-api-input-state.test.ts
new file mode 100644
index 0000000000..69faba39d3
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/api-input-manager/appsync-api-input-state.test.ts
@@ -0,0 +1,62 @@
+import { $TSContext } from 'amplify-cli-core';
+import { AppsyncApiInputState } from '../../../../provider-utils/awscloudformation/api-input-manager/appsync-api-input-state';
+
+jest.mock('fs-extra');
+
+jest.mock('amplify-cli-core', () => ({
+ ...(jest.requireActual('amplify-cli-core') as {}),
+ pathManager: {
+ getBackendDirPath: jest.fn().mockReturnValue('mockbackendDirPath'),
+ findProjectRoot: jest.fn().mockReturnValue('mockProject'),
+ },
+ JSONUtilities: {
+ parse: JSON.parse,
+ readJson: jest
+ .fn()
+ .mockReturnValueOnce({
+ version: 1,
+ serviceConfiguration: {
+ apiName: 'authv2migration1',
+ serviceName: 'AppSync',
+ gqlSchemaPath: 'mock/schema.graphql',
+ defaultAuthType: {
+ mode: 'AWS_IAM',
+ },
+ conflictResolution: {},
+ additionalAuthTypes: [
+ {
+ mode: 'API_KEY',
+ expirationTime: 7,
+ keyDescription: '',
+ },
+ ],
+ },
+ })
+ .mockReturnValueOnce({}),
+ },
+}));
+
+const mockContext: $TSContext = {
+ amplify: {
+ getCategoryPluginInfo: (_context: $TSContext, category: string) => {
+ return {
+ packageLocation: `@aws-amplify/amplify-category-${category}`,
+ };
+ },
+ },
+ input: {
+ options: {},
+ },
+} as unknown as $TSContext;
+
+test('Api Input State -> validate cli payload manual payload', async () => {
+ const resourceName = 'mockResource';
+ const apiState = new AppsyncApiInputState(mockContext, resourceName);
+ expect(await apiState.isCLIInputsValid()).toBe(true);
+});
+
+test('Api Input State -> validate cli payload manual payload to throw error', async () => {
+ const resourceName = 'mockResource';
+ const apiState = new AppsyncApiInputState(mockContext, resourceName);
+ expect(apiState.isCLIInputsValid()).rejects.toThrowError();
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/apigw-input-state.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/apigw-input-state.test.ts
new file mode 100644
index 0000000000..6211316a17
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/apigw-input-state.test.ts
@@ -0,0 +1,239 @@
+import { $TSContext, getMigrateResourceMessageForOverride, JSONUtilities, pathManager, stateManager } from 'amplify-cli-core';
+import { prompter } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import { ApigwInputState } from '../../../provider-utils/awscloudformation/apigw-input-state';
+
+jest.mock('amplify-cli-core');
+jest.mock('fs-extra');
+jest.mock('path');
+jest.mock('../../../provider-utils/awscloudformation/cdk-stack-builder');
+
+const fs_mock = fs as jest.Mocked;
+const JSONUtilities_mock = JSONUtilities as jest.Mocked;
+const pathManager_mock = pathManager as jest.Mocked;
+const prompter_mock = prompter as jest.Mocked;
+const stateManager_mock = stateManager as jest.Mocked;
+
+const context_mock = {
+ amplify: {
+ updateamplifyMetaAfterResourceAdd: jest.fn(),
+ updateamplifyMetaAfterResourceUpdate: jest.fn(),
+ },
+} as unknown as $TSContext;
+
+pathManager_mock.findProjectRoot = jest.fn().mockReturnValue('mockProjRoot');
+
+describe('REST API input state', () => {
+ afterEach(() => jest.clearAllMocks());
+
+ it('generates expected artifacts when adding a REST API', async () => {
+ const mockApiPaths = {
+ '/mock': {
+ permissions: {
+ setting: 'open',
+ },
+ lambdaFunction: 'mockLambda',
+ },
+ };
+
+ const inputState = new ApigwInputState(context_mock);
+ await expect(
+ inputState.addApigwResource((async () => ({ answers: { paths: mockApiPaths, resourceName: 'mockApi' } } as any))(), {}),
+ ).resolves.toEqual('mockApi');
+
+ expect(stateManager_mock.setResourceInputsJson).toHaveBeenCalledWith('mockProjRoot', 'api', 'mockApi', {
+ version: 1,
+ paths: mockApiPaths,
+ });
+ expect(stateManager_mock.setResourceParametersJson).toHaveBeenCalled();
+ expect(context_mock.amplify.updateamplifyMetaAfterResourceAdd).toHaveBeenCalled();
+ });
+
+ it('generates expected artifacts when updating a REST API', async () => {
+ const mockApiPaths = {
+ '/mock': {
+ permissions: {
+ setting: 'private',
+ auth: ['create', 'read'],
+ },
+ lambdaFunction: 'mockLambda',
+ },
+ };
+
+ const inputState = new ApigwInputState(context_mock);
+ await expect(
+ inputState.updateApigwResource((async () => ({ answers: { paths: mockApiPaths, resourceName: 'mockApi' } } as any))()),
+ ).resolves.toEqual('mockApi');
+
+ expect(stateManager_mock.setResourceInputsJson).toHaveBeenCalledWith('mockProjRoot', 'api', 'mockApi', {
+ version: 1,
+ paths: mockApiPaths,
+ });
+ expect(stateManager_mock.setResourceParametersJson).toHaveBeenCalled();
+ expect(context_mock.amplify.updateamplifyMetaAfterResourceUpdate).toHaveBeenCalled();
+ });
+
+ it('generates expected artifacts when choosing to migrate a REST API', async () => {
+ const mockDeprecatedParams = {
+ paths: [
+ {
+ name: '/mock',
+ lambdaFunction: 'mockLambda',
+ privacy: {
+ private: true,
+ auth: ['/GET'],
+ },
+ policyResourceName: '/mock',
+ },
+ ],
+ resourceName: 'mockApi',
+ apiName: 'mockApi',
+ functionArns: [
+ {
+ lambdaFunction: 'mockLambda',
+ },
+ ],
+ privacy: {
+ auth: 1,
+ unauth: 0,
+ authRoleName: 'mockauthRole',
+ unAuthRoleName: 'mockunauthRole',
+ },
+ dependsOn: [
+ {
+ category: 'function',
+ resourceName: 'mockLambda',
+ attributes: ['Name', 'Arn'],
+ },
+ ],
+ };
+
+ const mockApiPaths = {
+ '/mock': {
+ permissions: {
+ setting: 'private',
+ auth: ['read'],
+ },
+ lambdaFunction: 'mockLambda',
+ },
+ };
+
+ prompter_mock.yesOrNo = jest.fn().mockResolvedValueOnce(true); // yes to migration
+ JSONUtilities_mock.readJson = jest.fn().mockReturnValueOnce(mockDeprecatedParams);
+
+ const inputState = new ApigwInputState(context_mock);
+ await inputState.migrateApigwResource('mockApi');
+
+ expect(getMigrateResourceMessageForOverride).toHaveBeenCalled();
+ expect(stateManager_mock.setResourceInputsJson).toHaveBeenCalledWith('mockProjRoot', 'api', 'mockApi', {
+ version: 1,
+ paths: mockApiPaths,
+ });
+ expect(stateManager_mock.setResourceParametersJson).toHaveBeenCalled();
+ expect(fs_mock.removeSync).toHaveBeenCalledTimes(3);
+ });
+
+ it('does nothing when choosing NOT to migrate a REST API', async () => {
+ const inputState = new ApigwInputState(context_mock);
+
+ prompter_mock.yesOrNo = jest.fn().mockResolvedValueOnce(false); // no to migration
+
+ await inputState.migrateApigwResource('mockApi');
+
+ expect(getMigrateResourceMessageForOverride).toHaveBeenCalled();
+ expect(stateManager_mock.setResourceInputsJson).not.toHaveBeenCalled();
+ expect(stateManager_mock.setResourceParametersJson).not.toHaveBeenCalled();
+ expect(context_mock.amplify.updateamplifyMetaAfterResourceUpdate).not.toHaveBeenCalled();
+ expect(fs_mock.removeSync).not.toHaveBeenCalled();
+ });
+
+ it('generates expected artifacts when choosing to migrate an Admin Queries API', async () => {
+ prompter_mock.yesOrNo = jest.fn().mockResolvedValueOnce(true); // yes to migration
+
+ const inputState = new ApigwInputState(context_mock);
+ await inputState.migrateAdminQueries({
+ apiName: 'AdminQueries',
+ authResourceName: 'mockCognito',
+ functionName: 'mockLambda',
+ dependsOn: [],
+ });
+
+ expect(getMigrateResourceMessageForOverride).toHaveBeenCalled();
+ expect(stateManager_mock.setResourceInputsJson).toHaveBeenCalled();
+ expect(stateManager_mock.setResourceParametersJson).toHaveBeenCalled();
+ expect(fs_mock.removeSync).toHaveBeenCalledTimes(2);
+ });
+
+ it('does nothing when choosing NOT to migrate an Admin Queries API', async () => {
+ const inputState = new ApigwInputState(context_mock);
+
+ prompter_mock.yesOrNo = jest.fn().mockResolvedValueOnce(false); // no to migration
+
+ await inputState.migrateAdminQueries({
+ apiName: 'AdminQueries',
+ authResourceName: 'mockCognito',
+ functionName: 'mockLambda',
+ dependsOn: [],
+ });
+
+ expect(getMigrateResourceMessageForOverride).toHaveBeenCalled();
+ expect(stateManager_mock.setResourceInputsJson).not.toHaveBeenCalled();
+ expect(stateManager_mock.setResourceParametersJson).not.toHaveBeenCalled();
+ expect(context_mock.amplify.updateamplifyMetaAfterResourceUpdate).not.toHaveBeenCalled();
+ expect(fs_mock.removeSync).not.toHaveBeenCalled();
+ });
+
+ it('generates expected artifacts when adding an Admin Queries API', async () => {
+ const inputState = new ApigwInputState(context_mock);
+ const mockApiPaths = {
+ '/{proxy+}': {
+ permissions: {
+ setting: 'private',
+ auth: ['create', 'read', 'update', 'delete'],
+ },
+ lambdaFunction: 'mockLambda',
+ },
+ };
+
+ await inputState.addAdminQueriesResource({
+ apiName: 'AdminQueries',
+ authResourceName: 'mockCognito',
+ functionName: 'mockLambda',
+ dependsOn: [],
+ });
+
+ expect(stateManager_mock.setResourceInputsJson).toHaveBeenCalledWith('mockProjRoot', 'api', 'AdminQueries', {
+ version: 1,
+ paths: mockApiPaths,
+ });
+ expect(stateManager_mock.setResourceParametersJson).toHaveBeenCalled();
+ expect(context_mock.amplify.updateamplifyMetaAfterResourceAdd).toHaveBeenCalled();
+ });
+
+ it('generates expected artifacts when updating an Admin Queries API', async () => {
+ const inputState = new ApigwInputState(context_mock);
+ const mockApiPaths = {
+ '/{proxy+}': {
+ permissions: {
+ setting: 'private',
+ auth: ['create', 'read', 'update', 'delete'],
+ },
+ lambdaFunction: 'mockLambda',
+ },
+ };
+
+ await inputState.updateAdminQueriesResource({
+ apiName: 'AdminQueries',
+ authResourceName: 'mockCognito',
+ functionName: 'mockLambda',
+ dependsOn: [],
+ });
+
+ expect(stateManager_mock.setResourceInputsJson).toHaveBeenCalledWith('mockProjRoot', 'api', 'AdminQueries', {
+ version: 1,
+ paths: mockApiPaths,
+ });
+ expect(stateManager_mock.setResourceParametersJson).toHaveBeenCalled();
+ expect(context_mock.amplify.updateamplifyMetaAfterResourceUpdate).toHaveBeenCalled();
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-builder.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-builder.test.ts
new file mode 100644
index 0000000000..d4ab256e77
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-builder.test.ts
@@ -0,0 +1,47 @@
+import * as cdk from '@aws-cdk/core';
+import { Template } from '@aws-cdk/assertions';
+import { AmplifyApigwResourceStack } from '../../../../provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-builder';
+import { PermissionSetting } from '../../../../provider-utils/awscloudformation/cdk-stack-builder/types';
+
+describe('AmplifyApigwResourceStack', () => {
+ test('generateStackResources should synthesize the way we expected', () => {
+ const app = new cdk.App();
+ const amplifyApigwStack = new AmplifyApigwResourceStack(app, 'amplifyapigwstack', {
+ version: 1,
+ paths: {
+ '/path': {
+ lambdaFunction: 'lambdaFunction',
+ permissions: {
+ setting: PermissionSetting.OPEN,
+ },
+ },
+ },
+ });
+ amplifyApigwStack.generateStackResources('myapi');
+ const template = Template.fromStack(amplifyApigwStack);
+ template.hasResourceProperties('AWS::ApiGateway::GatewayResponse', {
+ ResponseType: 'DEFAULT_4XX',
+ ResponseParameters: {
+ 'gatewayresponse.header.Access-Control-Allow-Origin': "'*'",
+ 'gatewayresponse.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
+ 'gatewayresponse.header.Access-Control-Allow-Methods': "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'",
+ 'gatewayresponse.header.Access-Control-Expose-Headers': "'Date,X-Amzn-ErrorType'",
+ },
+ RestApiId: {
+ Ref: 'myapi',
+ },
+ });
+ template.hasResourceProperties('AWS::ApiGateway::GatewayResponse', {
+ ResponseType: 'DEFAULT_5XX',
+ ResponseParameters: {
+ 'gatewayresponse.header.Access-Control-Allow-Origin': "'*'",
+ 'gatewayresponse.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
+ 'gatewayresponse.header.Access-Control-Allow-Methods': "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'",
+ 'gatewayresponse.header.Access-Control-Expose-Headers': "'Date,X-Amzn-ErrorType'",
+ },
+ RestApiId: {
+ Ref: 'myapi',
+ },
+ });
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cfn-api-artifact-handler.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cfn-api-artifact-handler.test.ts
new file mode 100644
index 0000000000..dfef8a6a40
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/cfn-api-artifact-handler.test.ts
@@ -0,0 +1,366 @@
+import { $TSContext, pathManager } from 'amplify-cli-core';
+import { AddApiRequest, UpdateApiRequest } from 'amplify-headless-interface';
+import { printer } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import { writeTransformerConfiguration } from 'graphql-transformer-core';
+import _ from 'lodash';
+import * as path from 'path';
+import { AppsyncApiInputState } from '../../../provider-utils/awscloudformation/api-input-manager/appsync-api-input-state';
+import { category } from '../../../category-constants';
+import { ApiArtifactHandler } from '../../../provider-utils/api-artifact-handler';
+import { rootAssetDir } from '../../../provider-utils/awscloudformation/aws-constants';
+import { getCfnApiArtifactHandler } from '../../../provider-utils/awscloudformation/cfn-api-artifact-handler';
+import {
+ authConfigHasApiKey,
+ getAppSyncAuthConfig,
+ getAppSyncResourceName,
+} from '../../../provider-utils/awscloudformation/utils/amplify-meta-utils';
+
+const testAuthId = 'testAuthId';
+
+jest.mock('fs-extra');
+const printerMock = printer as jest.Mocked;
+printerMock.warn = jest.fn();
+
+jest.mock('../../../provider-utils/awscloudformation/api-input-manager/appsync-api-input-state');
+
+jest.mock('graphql-transformer-core', () => ({
+ readTransformerConfiguration: jest.fn(async () => ({})),
+ writeTransformerConfiguration: jest.fn(),
+}));
+
+jest.mock('../../../provider-utils/awscloudformation/utils/amplify-meta-utils', () => ({
+ checkIfAuthExists: jest.fn().mockImplementation(() => testAuthId),
+ getAppSyncResourceName: jest.fn(() => testApiName),
+ getAppSyncAuthConfig: jest.fn(() => ({})),
+ authConfigHasApiKey: jest.fn(() => true),
+ getImportedAuthUserPoolId: jest.fn(() => undefined),
+}));
+
+jest.mock('amplify-cli-core', () => ({
+ pathManager: {
+ getBackendDirPath: jest.fn().mockReturnValue('mockBackendDirPath'),
+ findProjectRoot: jest.fn().mockReturnValue('mockProject'),
+ },
+ stateManager: {
+ getMeta: jest.fn().mockReturnValue({}),
+ getBackendConfig: jest.fn(),
+ },
+ AmplifyCategories: {
+ API: 'api',
+ },
+ AmplifySupportedService: {
+ APPSYNC: 'Appsync',
+ },
+ JSONUtilities: {
+ readJson: jest.fn(),
+ writeJson: jest.fn(),
+ },
+ isResourceNameUnique: jest.fn(),
+}));
+
+const backendDirPathStub = 'backendDirPath';
+const testApiName = 'testApiName';
+
+const pathManagerMock = pathManager as jest.Mocked;
+pathManagerMock.getResourceDirectoryPath = jest.fn().mockReturnValue(`${backendDirPathStub}/api/${testApiName}`);
+
+const fsMock = (fs as unknown) as jest.Mocked;
+const writeTransformerConfigurationMock = writeTransformerConfiguration as jest.MockedFunction;
+const getAppSyncResourceNameMock = getAppSyncResourceName as jest.MockedFunction;
+const getAppSyncAuthConfigMock = getAppSyncAuthConfig as jest.MockedFunction;
+const authConfigHasApiKeyMock = authConfigHasApiKey as jest.MockedFunction;
+
+const contextStub = {
+ amplify: {
+ updateamplifyMetaAfterResourceAdd: jest.fn(),
+ updateamplifyMetaAfterResourceUpdate: jest.fn(),
+ updateBackendConfigAfterResourceUpdate: jest.fn(),
+ executeProviderUtils: jest.fn(),
+ copyBatch: jest.fn(),
+ },
+};
+
+describe('create artifacts', () => {
+ let cfnApiArtifactHandler: ApiArtifactHandler;
+ const addRequestStub: AddApiRequest = {
+ version: 1,
+ serviceConfiguration: {
+ serviceName: 'AppSync',
+ apiName: testApiName,
+ transformSchema: 'my test schema',
+ defaultAuthType: {
+ mode: 'API_KEY',
+ expirationTime: 10,
+ keyDescription: 'api key description',
+ },
+ },
+ };
+ beforeAll(() => {
+ fsMock.existsSync.mockImplementation(() => false);
+ getAppSyncResourceNameMock.mockImplementation(() => undefined);
+ });
+ beforeEach(() => {
+ jest.clearAllMocks();
+ cfnApiArtifactHandler = getCfnApiArtifactHandler((contextStub as unknown) as $TSContext);
+ });
+
+ it('does not create a second API if one already exists', async () => {
+ getAppSyncResourceNameMock.mockImplementationOnce(() => testApiName);
+ return expect(cfnApiArtifactHandler.createArtifacts(addRequestStub)).rejects.toMatchInlineSnapshot(
+ "[Error: GraphQL API testApiName already exists in the project. Use 'amplify update api' to make modifications.]",
+ );
+ });
+
+ it('creates the correct directories', async () => {
+ await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(fsMock.ensureDirSync.mock.calls.length).toBe(1);
+ expect(fsMock.ensureDirSync.mock.calls[0][0]).toBe(path.join(backendDirPathStub, category, testApiName));
+ expect(fsMock.mkdirSync.mock.calls.length).toBe(2);
+ expect(fsMock.mkdirSync.mock.calls[0][0]).toBe(path.join(backendDirPathStub, category, testApiName, 'resolvers'));
+ expect(fsMock.mkdirSync.mock.calls[1][0]).toBe(path.join(backendDirPathStub, category, testApiName, 'stacks'));
+ });
+
+ it('creates the transform.conf.json file', async () => {
+ await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(writeTransformerConfigurationMock.mock.calls.length).toBe(2);
+ expect(writeTransformerConfigurationMock.mock.calls[0]).toMatchSnapshot();
+ });
+
+ it('writes the default custom resources stack', async () => {
+ await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(fsMock.copyFileSync.mock.calls.length).toBe(2);
+ expect(fsMock.copyFileSync.mock.calls[1]).toEqual([
+ path.join(rootAssetDir, 'cloudformation-templates', 'defaultCustomResources.json'),
+ path.join(backendDirPathStub, category, addRequestStub.serviceConfiguration.apiName, 'stacks', 'CustomResources.json'),
+ ]);
+ });
+
+ it('creates correct cli-inputs', async () => {
+ jest.spyOn(AppsyncApiInputState.prototype, 'saveCLIInputPayload');
+ await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(AppsyncApiInputState.prototype.saveCLIInputPayload).toBeCalledWith({
+ serviceConfiguration: {
+ apiName: 'testApiName',
+ defaultAuthType: { expirationTime: 10, keyDescription: 'api key description', mode: 'API_KEY' },
+ gqlSchemaPath: 'backendDirPath/api/testApiName/schema.graphql',
+ serviceName: 'AppSync',
+ },
+ version: 1,
+ });
+ });
+
+ it('writes the selected template schema to project', async () => {
+ await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(fsMock.writeFileSync.mock.calls.length).toBe(1);
+ expect(fsMock.writeFileSync.mock.calls[0]).toEqual([
+ path.join(backendDirPathStub, category, addRequestStub.serviceConfiguration.apiName, 'schema.graphql'),
+ addRequestStub.serviceConfiguration.transformSchema,
+ ]);
+ });
+
+ it('executes compileSchema from the provider', async () => {
+ await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls.length).toBe(1);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls[0][0]).toStrictEqual(contextStub);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls[0][1]).toStrictEqual('awscloudformation');
+ expect(contextStub.amplify.executeProviderUtils.mock.calls[0][2]).toStrictEqual('compileSchema');
+ });
+
+ it('updates amplify meta', async () => {
+ await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceAdd.mock.calls.length).toBe(1);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceAdd.mock.calls[0][0]).toStrictEqual(category);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceAdd.mock.calls[0][1]).toStrictEqual(
+ addRequestStub.serviceConfiguration.apiName,
+ );
+ });
+
+ it('updates amplify meta with depends on auth if cognito specified', async () => {
+ const addRequestStubCognito = _.cloneDeep(addRequestStub);
+ addRequestStubCognito.serviceConfiguration.defaultAuthType = {
+ mode: 'AMAZON_COGNITO_USER_POOLS',
+ cognitoUserPoolId: testAuthId,
+ };
+ await cfnApiArtifactHandler.createArtifacts(addRequestStubCognito);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceAdd).toHaveBeenCalledTimes(1);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceAdd.mock.calls[0][2].dependsOn).toEqual([
+ {
+ category: 'auth',
+ resourceName: testAuthId,
+ attributes: ['UserPoolId'],
+ },
+ ]);
+ });
+
+ it('returns the api name', async () => {
+ const result = await cfnApiArtifactHandler.createArtifacts(addRequestStub);
+ expect(result).toBe(addRequestStub.serviceConfiguration.apiName);
+ });
+});
+
+describe('update artifacts', () => {
+ let cfnApiArtifactHandler: ApiArtifactHandler;
+ let updateRequestStub: UpdateApiRequest;
+ const updateRequestStubBase: UpdateApiRequest = {
+ version: 1,
+ serviceModification: {
+ serviceName: 'AppSync',
+ },
+ };
+
+ beforeAll(() => {
+ getAppSyncResourceNameMock.mockImplementation(() => testApiName);
+ getAppSyncAuthConfigMock.mockImplementation(() => ({
+ defaultAuthentication: {
+ authenticationType: 'API_KEY',
+ apiKeyConfig: {
+ apiKeyExpirationDays: 7,
+ description: '',
+ },
+ },
+ additionalAuthenticationProviders: [
+ {
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
+ userPoolConfig: {
+ userPoolId: 'myUserPoolId',
+ },
+ },
+ ],
+ }));
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ updateRequestStub = _.cloneDeep(updateRequestStubBase);
+ cfnApiArtifactHandler = getCfnApiArtifactHandler((contextStub as unknown) as $TSContext);
+ });
+
+ it('throws error if no GQL API in project', () => {
+ getAppSyncResourceNameMock.mockImplementationOnce(() => undefined);
+ return expect(cfnApiArtifactHandler.updateArtifacts(updateRequestStub)).rejects.toMatchInlineSnapshot(
+ "[Error: No AppSync API configured in the project. Use 'amplify add api' to create an API.]",
+ );
+ });
+
+ it('writes new schema if specified', async () => {
+ const newSchemaContents = 'a new schema';
+ updateRequestStub.serviceModification.transformSchema = newSchemaContents;
+ jest.spyOn(AppsyncApiInputState.prototype, 'getCLIInputPayload').mockReturnValue({
+ serviceConfiguration: {
+ apiName: 'testApiName',
+ defaultAuthType: { expirationTime: 10, keyDescription: 'api key description', mode: 'API_KEY' },
+ gqlSchemaPath: 'backendDirPath/api/testApiName/schema.graphql',
+ serviceName: 'AppSync',
+ },
+ version: 1,
+ });
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(fsMock.writeFileSync.mock.calls.length).toBe(1);
+ expect(fsMock.writeFileSync.mock.calls[0][1]).toBe(newSchemaContents);
+ });
+
+ it('updates default auth if not empty', async () => {
+ updateRequestStub.serviceModification.defaultAuthType = { mode: 'AWS_IAM' };
+ jest.spyOn(AppsyncApiInputState.prototype, 'getCLIInputPayload').mockReturnValue({
+ serviceConfiguration: {
+ apiName: 'testApiName',
+ defaultAuthType: { expirationTime: 10, keyDescription: 'api key description', mode: 'API_KEY' },
+ gqlSchemaPath: 'backendDirPath/api/testApiName/schema.graphql',
+ serviceName: 'AppSync',
+ },
+ version: 1,
+ });
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls.length).toBe(1);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls[0][3].authConfig).toMatchSnapshot();
+ });
+
+ it('updates correct cli-inputs', async () => {
+ updateRequestStub.serviceModification.additionalAuthTypes = [{ mode: 'AWS_IAM' }, { mode: 'API_KEY' }];
+ jest.spyOn(AppsyncApiInputState.prototype, 'saveCLIInputPayload');
+ jest.spyOn(AppsyncApiInputState.prototype, 'cliInputFileExists').mockReturnValueOnce(true);
+ jest.spyOn(AppsyncApiInputState.prototype, 'getCLIInputPayload').mockReturnValue({
+ serviceConfiguration: {
+ apiName: 'testApiName',
+ defaultAuthType: { expirationTime: 10, keyDescription: 'api key description', mode: 'API_KEY' },
+ gqlSchemaPath: 'backendDirPath/api/testApiName/schema.graphql',
+ serviceName: 'AppSync',
+ },
+ version: 1,
+ });
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(AppsyncApiInputState.prototype.saveCLIInputPayload).toBeCalledWith({
+ serviceConfiguration: {
+ additionalAuthTypes: [{ mode: 'AWS_IAM' }, { mode: 'API_KEY' }],
+ apiName: 'testApiName',
+ defaultAuthType: { expirationTime: 10, keyDescription: 'api key description', mode: 'API_KEY' },
+ gqlSchemaPath: 'backendDirPath/api/testApiName/schema.graphql',
+ serviceName: 'AppSync',
+ },
+ version: 1,
+ });
+ });
+
+ it('updates additional auth if not empty', async () => {
+ updateRequestStub.serviceModification.additionalAuthTypes = [{ mode: 'AWS_IAM' }, { mode: 'API_KEY' }];
+ jest.spyOn(AppsyncApiInputState.prototype, 'getCLIInputPayload').mockReturnValue({
+ serviceConfiguration: {
+ apiName: 'testApiName',
+ defaultAuthType: { expirationTime: 10, keyDescription: 'api key description', mode: 'API_KEY' },
+ gqlSchemaPath: 'backendDirPath/api/testApiName/schema.graphql',
+ serviceName: 'AppSync',
+ },
+ version: 1,
+ });
+
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls.length).toBe(1);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls[0][3].authConfig).toMatchSnapshot();
+ });
+
+ it('compiles the changes', async () => {
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(contextStub.amplify.executeProviderUtils.mock.calls.length).toBe(1);
+ });
+
+ it('updates meta files after update', async () => {
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceUpdate.mock.calls.length).toBe(2);
+ expect(contextStub.amplify.updateBackendConfigAfterResourceUpdate.mock.calls.length).toBe(2);
+ });
+
+ it('prints warning when adding API key auth', async () => {
+ authConfigHasApiKeyMock.mockImplementationOnce(() => false).mockImplementationOnce(() => true);
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(printerMock.warn.mock.calls.length).toBe(2);
+ });
+
+ it('prints warning when removing API key auth', async () => {
+ authConfigHasApiKeyMock.mockImplementationOnce(() => true).mockImplementationOnce(() => false);
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(printerMock.warn.mock.calls.length).toBe(3);
+ });
+
+ it('adds auth dependency if cognito auth specified', async () => {
+ getAppSyncAuthConfigMock.mockReturnValueOnce({
+ defaultAuthentication: {
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
+ userPoolConfig: {
+ userPoolId: testAuthId,
+ },
+ },
+ });
+ await cfnApiArtifactHandler.updateArtifacts(updateRequestStub);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceUpdate.mock.calls.length).toBe(2);
+ expect(contextStub.amplify.updateamplifyMetaAfterResourceUpdate.mock.calls[1][3]).toEqual([
+ {
+ category: 'auth',
+ resourceName: testAuthId,
+ attributes: [
+ 'UserPoolId',
+ ],
+ },
+ ]);
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/convert-deprecated-apigw-paths.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/convert-deprecated-apigw-paths.test.ts
new file mode 100644
index 0000000000..45bfc7bfb6
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/convert-deprecated-apigw-paths.test.ts
@@ -0,0 +1,73 @@
+import { JSONUtilities } from 'amplify-cli-core';
+
+jest.mock('amplify-cli-core');
+
+const JSONUtilities_mock = JSONUtilities as jest.Mocked;
+
+import { convertDeperecatedRestApiPaths } from '../../../provider-utils/awscloudformation/convert-deprecated-apigw-paths';
+
+describe('test apigw path migrate', () => {
+ it('migrates valid input successfully', async () => {
+ JSONUtilities_mock.readJson.mockReturnValueOnce({
+ paths: [
+ {
+ name: '/mockOpenPath',
+ lambdaFunction: 'mockOpenLambda',
+ privacy: {
+ open: true,
+ },
+ },
+ {
+ name: '/mockPrivatePath',
+ lambdaFunction: 'mockPrivateLambda',
+ privacy: {
+ auth: ['/GET', '/POST'],
+ private: true,
+ },
+ },
+ {
+ name: '/mockLegacyPath',
+ lambdaFunction: 'mockLegacyLambda',
+ privacy: {
+ auth: 'rw',
+ private: true,
+ },
+ },
+ ],
+ });
+
+ const convertedPaths = convertDeperecatedRestApiPaths('mockFileName.json', 'mock/file/path/mockFileName.json', 'mockApi');
+ expect(convertedPaths).toMatchObject({
+ '/mockOpenPath': {
+ permissions: {
+ setting: 'open',
+ },
+ lambdaFunction: 'mockOpenLambda',
+ },
+ '/mockPrivatePath': {
+ permissions: {
+ setting: 'private',
+ auth: ['read', 'create'],
+ },
+ lambdaFunction: 'mockPrivateLambda',
+ },
+ '/mockLegacyPath': {
+ permissions: {
+ setting: 'private',
+ auth: ['create', 'read', 'update', 'delete'],
+ },
+ lambdaFunction: 'mockLegacyLambda',
+ },
+ });
+ });
+
+ it('throws on invalid input', async () => {
+ JSONUtilities_mock.readJson.mockReturnValueOnce({});
+ expect(() => convertDeperecatedRestApiPaths('mockFileName.json', 'mock/file/path/mockFileName.json', 'mockApi')).toThrow();
+
+ JSONUtilities_mock.readJson.mockReturnValueOnce({
+ paths: [],
+ });
+ expect(() => convertDeperecatedRestApiPaths('mockFileName.json', 'mock/file/path/mockFileName.json', 'mockApi')).toThrow();
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/legacy-add-resource.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/legacy-add-resource.test.ts
new file mode 100644
index 0000000000..efb5f999e0
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/legacy-add-resource.test.ts
@@ -0,0 +1,43 @@
+import { legacyAddResource } from '../../../provider-utils/awscloudformation/legacy-add-resource';
+import { category } from '../../../category-constants';
+import { $TSAny, $TSContext } from 'amplify-cli-core';
+
+jest.mock('fs-extra');
+jest.mock('amplify-cli-core', () => ({
+ AmplifyCategories: { API: 'api' },
+ isResourceNameUnique: jest.fn().mockReturnValue(true),
+ JSONUtilities: {
+ readJson: jest.fn(),
+ writeJson: jest.fn(),
+ },
+ pathManager: {
+ getResourceDirectoryPath: jest.fn(_ => 'mock/backend/path'),
+ },
+}));
+
+describe('legacy add resource', () => {
+ const contextStub = {
+ amplify: {
+ updateamplifyMetaAfterResourceAdd: jest.fn(),
+ copyBatch: jest.fn(),
+ },
+ };
+
+ it('sets policy resource name in paths object before copying template', async () => {
+ const stubWalkthroughPromise: Promise<$TSAny> = Promise.resolve({
+ answers: {
+ resourceName: 'mockResourceName',
+ paths: [
+ {
+ name: '/some/{path}/with/{params}',
+ },
+ {
+ name: 'another/path/without/params',
+ },
+ ],
+ },
+ });
+ await legacyAddResource(stubWalkthroughPromise, contextStub as unknown as $TSContext, category, 'API Gateway', {});
+ expect(contextStub.amplify.copyBatch.mock.calls[0][2]).toMatchSnapshot();
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/legacy-update-resource.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/legacy-update-resource.test.ts
new file mode 100644
index 0000000000..2945ad4def
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/legacy-update-resource.test.ts
@@ -0,0 +1,42 @@
+import { $TSContext } from 'amplify-cli-core';
+import { legacyUpdateResource } from '../../../provider-utils/awscloudformation/legacy-update-resource';
+import { category } from '../../../category-constants';
+
+jest.mock('fs-extra');
+jest.mock('amplify-cli-core', () => ({
+ AmplifyCategories: { API: 'api' },
+ JSONUtilities: {
+ readJson: jest.fn(),
+ writeJson: jest.fn(),
+ },
+ pathManager: {
+ getResourceDirectoryPath: jest.fn(_ => 'mock/backend/path'),
+ },
+}));
+
+describe('legacy update resource', () => {
+ const contextStub = {
+ amplify: {
+ updateamplifyMetaAfterResourceUpdate: jest.fn(),
+ copyBatch: jest.fn(),
+ },
+ };
+
+ it('sets policy resource name in paths object before copying template', async () => {
+ const stubWalkthroughPromise: Promise = Promise.resolve({
+ answers: {
+ resourceName: 'mockResourceName',
+ paths: [
+ {
+ name: '/some/{path}/with/{params}',
+ },
+ {
+ name: 'another/path/without/params',
+ },
+ ],
+ },
+ });
+ await legacyUpdateResource(stubWalkthroughPromise, contextStub as unknown as $TSContext, category, 'API Gateway');
+ expect(contextStub.amplify.copyBatch.mock.calls[0][2]).toMatchSnapshot();
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/prompt-to-add-api-key.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/prompt-to-add-api-key.test.ts
new file mode 100644
index 0000000000..871b8c8f1e
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/prompt-to-add-api-key.test.ts
@@ -0,0 +1,44 @@
+import { $TSContext } from 'amplify-cli-core';
+import * as prompts from 'amplify-prompts';
+import { promptToAddApiKey } from '../../../provider-utils/awscloudformation/prompt-to-add-api-key';
+import * as walkthrough from '../../../provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough';
+import * as cfnApiArtifactHandler from '../../../provider-utils/awscloudformation/cfn-api-artifact-handler';
+
+jest.mock('../../../provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough', () => ({
+ askApiKeyQuestions: jest.fn(),
+}));
+
+jest.mock('../../../provider-utils/awscloudformation/cfn-api-artifact-handler', () => ({
+ getCfnApiArtifactHandler: jest.fn(() => {
+ return { updateArtifacts: jest.fn() };
+ }),
+}));
+
+jest.mock('amplify-prompts', () => ({
+ prompter: {
+ confirmContinue: jest.fn().mockImplementation(() => true),
+ },
+}));
+
+describe('prompt to add Api Key', () => {
+ it('runs through expected user flow: print info, update files', async () => {
+ const envName = 'envone';
+ const ctx = {
+ amplify: {
+ getEnvInfo() {
+ return { envName };
+ },
+ },
+ } as unknown as $TSContext;
+
+ jest.spyOn(prompts.prompter, 'confirmContinue');
+ jest.spyOn(walkthrough, 'askApiKeyQuestions');
+ jest.spyOn(cfnApiArtifactHandler, 'getCfnApiArtifactHandler');
+
+ await promptToAddApiKey(ctx);
+
+ expect(prompts.prompter.confirmContinue).toHaveBeenCalledWith('Would you like to create an API Key?');
+ expect(walkthrough.askApiKeyQuestions).toHaveBeenCalledTimes(1);
+ expect(cfnApiArtifactHandler.getCfnApiArtifactHandler).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/__snapshots__/appSync-walkthrough.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/__snapshots__/appSync-walkthrough.test.ts.snap
new file mode 100644
index 0000000000..19c9e1c5f2
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/__snapshots__/appSync-walkthrough.test.ts.snap
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`correct Auth Config dont configure additional auth types 1`] = `
+Object {
+ "additionalAuthenticationProviders": Array [
+ Object {
+ "authenticationType": "AMAZON_COGNITO_USER_POOLS",
+ },
+ ],
+ "defaultAuthentication": Object {
+ "authenticationType": "AWS_IAM",
+ },
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.test.ts
new file mode 100644
index 0000000000..67f1ec32a7
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.test.ts
@@ -0,0 +1,6 @@
+import { getIAMPolicies } from '../../../../provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough';
+
+test('getIAMPolicies', () => {
+ const output = getIAMPolicies('resourceName', ['read']);
+ expect(output.attributes).toStrictEqual(['ApiName', 'ApiId']);
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.test.ts
new file mode 100644
index 0000000000..9b2db83f7b
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.test.ts
@@ -0,0 +1,130 @@
+import { $TSAny, $TSContext, FeatureFlags, pathManager, stateManager } from 'amplify-cli-core';
+import {
+ askAdditionalAuthQuestions,
+ getIAMPolicies,
+} from '../../../../provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough';
+import { authConfigHasApiKey, getAppSyncAuthConfig } from '../../../../provider-utils/awscloudformation/utils/amplify-meta-utils';
+
+jest.mock('../../../../provider-utils/awscloudformation/utils/amplify-meta-utils', () => ({
+ getAppSyncAuthConfig: jest.fn(),
+ authConfigHasApiKey: jest.fn(),
+}));
+jest.mock('amplify-cli-core');
+const stateManager_mock = stateManager as jest.Mocked;
+stateManager_mock.getMeta = jest.fn();
+
+const pathManager_mock = pathManager as jest.Mocked;
+pathManager_mock.getResourceDirectoryPath = jest.fn().mockReturnValue('mocked/resource/path');
+
+const mockGetBoolean = FeatureFlags.getBoolean as jest.Mock;
+
+const authConfigHasApiKey_mock = authConfigHasApiKey as jest.MockedFunction;
+const getAppSyncAuthConfig_mock = getAppSyncAuthConfig as jest.MockedFunction;
+const confirmPromptFalse_mock = jest.fn(() => false);
+
+const context_stub = (prompt: jest.Mock) =>
+ ({
+ prompt: {
+ confirm: prompt,
+ },
+ amplify: {
+ getProjectMeta: jest.fn(),
+ },
+ } as unknown as $TSContext);
+
+type IAMArtifact = {
+ attributes: string[];
+ policy: $TSAny;
+};
+
+describe('get IAM policies', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ });
+
+ it('does not include API key if none exists', async () => {
+ mockGetBoolean.mockImplementationOnce(() => true);
+ authConfigHasApiKey_mock.mockImplementationOnce(() => false);
+ const iamArtifact: IAMArtifact = getIAMPolicies('testResourceName', ['Query']);
+ expect(iamArtifact.attributes).toMatchInlineSnapshot(`
+ Array [
+ "GraphQLAPIIdOutput",
+ "GraphQLAPIEndpointOutput",
+ ]
+ `);
+ expect(iamArtifact.policy.Resource[0]['Fn::Join'][1][6]).toMatch('/types/Query/*');
+ });
+
+ it('includes API key if it exists', async () => {
+ mockGetBoolean.mockImplementationOnce(() => true);
+ authConfigHasApiKey_mock.mockImplementationOnce(() => true);
+ const iamArtifact: IAMArtifact = getIAMPolicies('testResourceName', ['Query']);
+ expect(iamArtifact.attributes).toMatchInlineSnapshot(`
+ Array [
+ "GraphQLAPIIdOutput",
+ "GraphQLAPIEndpointOutput",
+ "GraphQLAPIKeyOutput",
+ ]
+ `);
+ expect(iamArtifact.policy.Resource[0]['Fn::Join'][1][6]).toMatch('/types/Query/*');
+ });
+
+ it('policy path includes the new format for graphql operations', async () => {
+ mockGetBoolean.mockImplementationOnce(() => true);
+ authConfigHasApiKey_mock.mockImplementationOnce(() => false);
+ const iamArtifact: IAMArtifact = getIAMPolicies('testResourceName', ['Query', 'Mutate']);
+ expect(iamArtifact.attributes).toMatchInlineSnapshot(`
+ Array [
+ "GraphQLAPIIdOutput",
+ "GraphQLAPIEndpointOutput",
+ ]
+ `);
+ expect(iamArtifact.policy.Resource[0]['Fn::Join'][1][6]).toMatch('/types/Query/*');
+ expect(iamArtifact.policy.Resource[1]['Fn::Join'][1][6]).toMatch('/types/Mutate/*');
+ });
+
+ it('policy path includes the old format for appsync api operations', async () => {
+ mockGetBoolean.mockImplementationOnce(() => false);
+ authConfigHasApiKey_mock.mockImplementationOnce(() => false);
+ const iamArtifact: IAMArtifact = getIAMPolicies('testResourceName', ['create', 'update']);
+ expect(iamArtifact.attributes).toMatchInlineSnapshot(`
+ Array [
+ "GraphQLAPIIdOutput",
+ "GraphQLAPIEndpointOutput",
+ ]
+ `);
+ expect(iamArtifact.policy.Action).toHaveLength(4);
+ expect(iamArtifact.policy.Action).toEqual(['appsync:Create*', 'appsync:StartSchemaCreation', 'appsync:GraphQL', 'appsync:Update*']);
+ expect(iamArtifact.policy.Resource).toHaveLength(2);
+ expect(iamArtifact.policy.Resource[0]['Fn::Join'][1][6]).toMatch('/*');
+ });
+});
+
+describe('correct Auth Config', () => {
+ it('dont configure additional auth types ', async () => {
+ const authConfig_mock = {
+ defaultAuthentication: {
+ authenticationType: 'AWS_IAM',
+ },
+ additionalAuthenticationProviders: [],
+ };
+ const defaultAuthType_mock = 'AWS_IAM';
+
+ const currentAuthConfig = {
+ defaultAuthentication: {
+ authenticationType: 'API_KEY',
+ },
+ additionalAuthenticationProviders: [
+ {
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
+ },
+ {
+ authenticationType: 'AWS_IAM',
+ },
+ ],
+ };
+ getAppSyncAuthConfig_mock.mockImplementationOnce(() => currentAuthConfig);
+ const authConfig = await askAdditionalAuthQuestions(context_stub(confirmPromptFalse_mock), authConfig_mock, defaultAuthType_mock);
+ expect(authConfig).toMatchSnapshot();
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts.snap
new file mode 100644
index 0000000000..3c18c0fa24
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts.snap
@@ -0,0 +1,73 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AppSyncAuthType to authConfig maps AMAZON_COGNITO_USER_POOLS correctly 1`] = `
+Object {
+ "authenticationType": "AMAZON_COGNITO_USER_POOLS",
+ "userPoolConfig": Object {
+ "userPoolId": "someID",
+ },
+}
+`;
+
+exports[`AppSyncAuthType to authConfig maps API_KEY correctly 1`] = `
+Object {
+ "apiKeyConfig": Object {
+ "apiKeyExpirationDate": undefined,
+ "apiKeyExpirationDays": 120,
+ "description": undefined,
+ },
+ "authenticationType": "API_KEY",
+}
+`;
+
+exports[`AppSyncAuthType to authConfig maps AWS_IAM correctly 1`] = `
+Object {
+ "authenticationType": "AWS_IAM",
+}
+`;
+
+exports[`AppSyncAuthType to authConfig maps OPENID_CONNECT correctly 1`] = `
+Object {
+ "authenticationType": "OPENID_CONNECT",
+ "openIDConnectConfig": Object {
+ "authTTL": undefined,
+ "clientId": "client id",
+ "iatTTL": undefined,
+ "issuerUrl": "issuer url",
+ "name": "providerName",
+ },
+}
+`;
+
+exports[`authConfig to AppSyncAuthType maps AMAZON_COGNITO_USER_POOLS auth correctly 1`] = `
+Object {
+ "cognitoUserPoolId": "userPoolId",
+ "mode": "AMAZON_COGNITO_USER_POOLS",
+}
+`;
+
+exports[`authConfig to AppSyncAuthType maps API_KEY auth correctly 1`] = `
+Object {
+ "apiKeyExpirationDate": undefined,
+ "expirationTime": 120,
+ "keyDescription": "api key description",
+ "mode": "API_KEY",
+}
+`;
+
+exports[`authConfig to AppSyncAuthType maps AWS_IAM auth correctly 1`] = `
+Object {
+ "mode": "AWS_IAM",
+}
+`;
+
+exports[`authConfig to AppSyncAuthType maps OPENID_CONNECT auth correclty 1`] = `
+Object {
+ "mode": "OPENID_CONNECT",
+ "openIDAuthTTL": "auth TTL",
+ "openIDClientID": "client id",
+ "openIDIatTTL": "iat TTL",
+ "openIDIssuerURL": "issuer url",
+ "openIDProviderName": "openid name",
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/get-appsync-auth-config.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/get-appsync-auth-config.test.ts.snap
new file mode 100644
index 0000000000..438e5b8a64
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/get-appsync-auth-config.test.ts.snap
@@ -0,0 +1,28 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`test function with default and additional auth config 1`] = `
+Object {
+ "additionalAuthenticationProviders": Array [
+ Object {
+ "apiKeyConfig": Object {
+ "apiKeyExpirationDate": undefined,
+ "apiKeyExpirationDays": 7,
+ "description": "",
+ },
+ "authenticationType": "API_KEY",
+ },
+ ],
+ "defaultAuthentication": Object {
+ "authenticationType": "AWS_IAM",
+ },
+}
+`;
+
+exports[`test function with default auth config 1`] = `
+Object {
+ "additionalAuthenticationProviders": Array [],
+ "defaultAuthentication": Object {
+ "authenticationType": "AWS_IAM",
+ },
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/resolver-config-to-conflict-resolution-bi-di-mapper.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/resolver-config-to-conflict-resolution-bi-di-mapper.test.ts.snap
new file mode 100644
index 0000000000..9668a5c86e
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/resolver-config-to-conflict-resolution-bi-di-mapper.test.ts.snap
@@ -0,0 +1,45 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`transform ConflictResolution to ResolverConfig maps properties correctly 1`] = `
+Object {
+ "models": Object {
+ "MyType": Object {
+ "ConflictDetection": "VERSION",
+ "ConflictHandler": "LAMBDA",
+ "LambdaConflictHandler": Object {
+ "lambdaArn": undefined,
+ "name": "someLambdaName",
+ "region": undefined,
+ },
+ },
+ },
+ "project": Object {
+ "ConflictDetection": "VERSION",
+ "ConflictHandler": "AUTOMERGE",
+ },
+}
+`;
+
+exports[`transform ConflictResolution to ResolverConfig throws when trying to convert new lambda resolution strategy 1`] = `"Tried to convert LambdaResolutionStrategy \\"NEW\\" to SyncConfig. New resources must be generated prior to this conversion and then replaced with a LambdaResolutionStrategy of type \\"EXISTING\\""`;
+
+exports[`transform ResolverConfig to ConflictResolution maps properties correctly 1`] = `
+Object {
+ "defaultResolutionStrategy": Object {
+ "type": "AUTOMERGE",
+ },
+ "perModelResolutionStrategy": Array [
+ Object {
+ "entityName": "MyType",
+ "resolutionStrategy": Object {
+ "resolver": Object {
+ "arn": undefined,
+ "name": "myLambdaConflictHandler",
+ "region": undefined,
+ "type": "EXISTING",
+ },
+ "type": "LAMBDA",
+ },
+ },
+ ],
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/service-walkthrough-result-to-add-api-request.test.ts.snap b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/service-walkthrough-result-to-add-api-request.test.ts.snap
new file mode 100644
index 0000000000..843fd5f5bc
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/__snapshots__/service-walkthrough-result-to-add-api-request.test.ts.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`walkthrough result to AddApiRequest conversion maps properties correctly 1`] = `
+Object {
+ "serviceConfiguration": Object {
+ "additionalAuthTypes": Array [
+ Object {
+ "mode": "AWS_IAM",
+ },
+ ],
+ "apiName": "myApiName",
+ "conflictResolution": Object {},
+ "defaultAuthType": Object {
+ "mode": "AWS_IAM",
+ },
+ "serviceName": "AppSync",
+ "transformSchema": "mySchemaContent",
+ },
+ "version": 1,
+}
+`;
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/amplify-meta-utils.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/amplify-meta-utils.test.ts
new file mode 100644
index 0000000000..0ca8d87ce6
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/amplify-meta-utils.test.ts
@@ -0,0 +1,45 @@
+import { authConfigHasApiKey } from '../../../../provider-utils/awscloudformation/utils/amplify-meta-utils';
+
+describe('auth config has api key', () => {
+ it('returns true when default auth is api key', () => {
+ const authConfig = {
+ defaultAuthentication: {
+ authenticationType: 'API_KEY',
+ },
+ };
+
+ expect(authConfigHasApiKey(authConfig)).toBe(true);
+ });
+
+ it('returns true when addtl auth contains api key', () => {
+ const authConfig = {
+ additionalAuthenticationProviders: [
+ {
+ authenticationType: 'AWS_IAM',
+ },
+ {
+ authenticationType: 'API_KEY',
+ },
+ ],
+ };
+ expect(authConfigHasApiKey(authConfig)).toBe(true);
+ });
+
+ it('returns false when no auth type is api key', () => {
+ const authConfig = {
+ defaultAuthentication: {
+ authenticationType: 'AWS_IAM',
+ },
+ additionalAuthenticationProviders: [
+ {
+ authenticationType: 'OTHER',
+ },
+ {
+ authenticationType: 'OPENID',
+ },
+ ],
+ };
+
+ expect(authConfigHasApiKey(authConfig)).toBe(false);
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts
new file mode 100644
index 0000000000..0b7d99a2e1
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.test.ts
@@ -0,0 +1,102 @@
+import {
+ authConfigToAppSyncAuthType,
+ appSyncAuthTypeToAuthConfig,
+} from '../../../../provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper';
+import {
+ AppSyncAPIKeyAuthType,
+ AppSyncAWSIAMAuthType,
+ AppSyncCognitoUserPoolsAuthType,
+ AppSyncOpenIDConnectAuthType,
+} from 'amplify-headless-interface';
+
+describe('authConfig to AppSyncAuthType', () => {
+ it('maps API_KEY auth correctly', () => {
+ const authConfig = {
+ authenticationType: 'API_KEY',
+ apiKeyConfig: {
+ apiKeyExpirationDays: 120,
+ description: 'api key description',
+ },
+ };
+
+ expect(authConfigToAppSyncAuthType(authConfig)).toMatchSnapshot();
+ });
+
+ it('maps AWS_IAM auth correctly', () => {
+ const authConfig = {
+ authenticationType: 'AWS_IAM',
+ };
+
+ expect(authConfigToAppSyncAuthType(authConfig)).toMatchSnapshot();
+ });
+
+ it('maps AMAZON_COGNITO_USER_POOLS auth correctly', () => {
+ const authConfig = {
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
+ userPoolConfig: {
+ userPoolId: 'userPoolId',
+ },
+ };
+
+ expect(authConfigToAppSyncAuthType(authConfig)).toMatchSnapshot();
+ });
+
+ it('maps OPENID_CONNECT auth correclty', () => {
+ const authConfig = {
+ authenticationType: 'OPENID_CONNECT',
+ openIDConnectConfig: {
+ name: 'openid name',
+ issuerUrl: 'issuer url',
+ clientId: 'client id',
+ authTTL: 'auth TTL',
+ iatTTL: 'iat TTL',
+ },
+ };
+
+ expect(authConfigToAppSyncAuthType(authConfig)).toMatchSnapshot();
+ });
+
+ it('returns undefined on undefined input', () => {
+ expect(authConfigToAppSyncAuthType(undefined)).toBeUndefined();
+ });
+});
+
+describe('AppSyncAuthType to authConfig', () => {
+ it('maps API_KEY correctly', () => {
+ const authType: AppSyncAPIKeyAuthType = {
+ mode: 'API_KEY',
+ expirationTime: 120,
+ };
+ expect(appSyncAuthTypeToAuthConfig(authType)).toMatchSnapshot();
+ });
+
+ it('maps AWS_IAM correctly', () => {
+ const authType: AppSyncAWSIAMAuthType = {
+ mode: 'AWS_IAM',
+ };
+ expect(appSyncAuthTypeToAuthConfig(authType)).toMatchSnapshot();
+ });
+
+ it('maps AMAZON_COGNITO_USER_POOLS correctly', () => {
+ const authType: AppSyncCognitoUserPoolsAuthType = {
+ mode: 'AMAZON_COGNITO_USER_POOLS',
+ cognitoUserPoolId: 'someID',
+ };
+ expect(appSyncAuthTypeToAuthConfig(authType)).toMatchSnapshot();
+ });
+
+ it('maps OPENID_CONNECT correctly', () => {
+ const authType: AppSyncOpenIDConnectAuthType = {
+ mode: 'OPENID_CONNECT',
+ openIDProviderName: 'providerName',
+ openIDClientID: 'client id',
+ openIDIssuerURL: 'issuer url',
+ };
+
+ expect(appSyncAuthTypeToAuthConfig(authType)).toMatchSnapshot();
+ });
+
+ it('returns undefined on undefined input', () => {
+ expect(appSyncAuthTypeToAuthConfig(undefined)).toBeUndefined();
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/containers/set-existing-secret-arns.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/containers/set-existing-secret-arns.test.ts
new file mode 100644
index 0000000000..63a0ccbbd4
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/containers/set-existing-secret-arns.test.ts
@@ -0,0 +1,60 @@
+import { setExistingSecretArns } from '../../../../../provider-utils/awscloudformation/utils/containers/set-existing-secret-arns';
+
+describe('set existing secret arns', () => {
+ it('does nothing if no template found', () => {
+ const secretMap = new Map();
+ setExistingSecretArns(secretMap, {});
+ expect(secretMap.size).toBe(0);
+ });
+
+ it('does nothing if template does not have secrets', () => {
+ const mockTemplate = {
+ Resources: {
+ TaskDefinition: {
+ Type: 'AWS::ECS::TaskDefinition',
+ Properties: {
+ ContainerDefinitions: [
+ {
+ Secrets: [],
+ },
+ ],
+ },
+ },
+ },
+ };
+ const secretMap = new Map();
+ setExistingSecretArns(secretMap, mockTemplate);
+ expect(secretMap.size).toBe(0);
+ });
+
+ it('adds all secrets to secret map', () => {
+ const mockTemplate = {
+ Resources: {
+ TaskDefinition: {
+ Type: 'AWS::ECS::TaskDefinition',
+ Properties: {
+ ContainerDefinitions: [
+ {
+ Secrets: [
+ {
+ Name: 'SOMETHING',
+ ValueFrom: 'some:secretsmanager:arn',
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ };
+ const secretMap = new Map();
+ setExistingSecretArns(secretMap, mockTemplate);
+ expect(secretMap.size).toBe(1);
+ expect(secretMap.entries().next().value).toMatchInlineSnapshot(`
+ Array [
+ "SOMETHING",
+ "some:secretsmanager:arn",
+ ]
+ `);
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/get-appsync-auth-config.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/get-appsync-auth-config.test.ts
new file mode 100644
index 0000000000..7dbfe74d3b
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/get-appsync-auth-config.test.ts
@@ -0,0 +1,69 @@
+import { $TSContext } from 'amplify-cli-core';
+import { getAuthConfig } from '../../../../provider-utils/awscloudformation/utils/get-appsync-auth-config';
+
+const getCLIInputPayload_mock = jest
+ .fn()
+ .mockReturnValueOnce({
+ version: 1,
+ serviceConfiguration: {
+ apiName: 'authv2migration1',
+ serviceName: 'AppSync',
+ gqlSchemaPath: 'mock/schema.graphql',
+ defaultAuthType: {
+ mode: 'AWS_IAM',
+ },
+ conflictResolution: {},
+ additionalAuthTypes: [],
+ },
+ })
+ .mockReturnValueOnce({
+ version: 1,
+ serviceConfiguration: {
+ apiName: 'authv2migration1',
+ serviceName: 'AppSync',
+ gqlSchemaPath: 'mock/schema.graphql',
+ defaultAuthType: {
+ mode: 'AWS_IAM',
+ },
+ conflictResolution: {},
+ additionalAuthTypes: [
+ {
+ mode: 'API_KEY',
+ expirationTime: 7,
+ keyDescription: '',
+ },
+ ],
+ },
+ });
+
+jest.mock('../../../../provider-utils/awscloudformation/api-input-manager/appsync-api-input-state.ts', () => {
+ return {
+ AppsyncApiInputState: jest.fn().mockImplementation(() => {
+ return {
+ getCLIInputPayload: getCLIInputPayload_mock,
+ cliInputFileExists: jest.fn().mockReturnValue(true),
+ };
+ }),
+ };
+});
+
+const mockContext: $TSContext = {
+ amplify: {
+ getCategoryPluginInfo: (_context: $TSContext, category: string) => {
+ return {
+ packageLocation: `@aws-amplify/amplify-category-${category}`,
+ };
+ },
+ },
+ input: {
+ options: {},
+ },
+} as unknown as $TSContext;
+
+test('test function with default auth config', async () => {
+ expect(await getAuthConfig(mockContext, 'mockapiResource')).toMatchSnapshot();
+});
+
+test('test function with default and additional auth config', async () => {
+ expect(await getAuthConfig(mockContext, 'mockapiResource')).toMatchSnapshot();
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/global-sandbox-mode.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/global-sandbox-mode.test.ts
new file mode 100644
index 0000000000..28716c527e
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/global-sandbox-mode.test.ts
@@ -0,0 +1,10 @@
+import { defineGlobalSandboxMode } from '../../../../provider-utils/awscloudformation/utils/global-sandbox-mode';
+
+describe('global sandbox mode GraphQL directive', () => {
+ it('returns input AMPLIFY with code comment', () => {
+ expect(defineGlobalSandboxMode('mockLink')).toEqual(`# This "input" configures a global authorization rule to enable public access to
+# all models in this schema. Learn more about authorization rules here: mockLink
+input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!\n
+`);
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/migrate-api-override-resource.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/migrate-api-override-resource.test.ts
new file mode 100644
index 0000000000..b91d4049f3
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/migrate-api-override-resource.test.ts
@@ -0,0 +1,79 @@
+import { JSONUtilities } from 'amplify-cli-core';
+import { migrateResourceToSupportOverride } from '../../../../provider-utils/awscloudformation/utils/migrate-api-override-resource';
+import * as path from 'path';
+
+jest.mock('amplify-prompts');
+jest.mock('fs-extra');
+
+jest.mock('amplify-cli-core', () => ({
+ ...(jest.requireActual('amplify-cli-core') as {}),
+ pathManager: {
+ findProjectRoot: jest.fn().mockReturnValue('somePath'),
+ getBackendDirPath: jest.fn().mockReturnValue('mockProjectPath'),
+ getResourceDirectoryPath: jest.fn().mockReturnValue('mockProjectPath'),
+ },
+ stateManager: {
+ getMeta: jest.fn().mockReturnValue({
+ api: {
+ apiunittests: {
+ service: 'AppSync',
+ providerPlugin: 'awscloudformation',
+ output: {
+ authConfig: {
+ defaultAuthentication: {
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
+ userPoolConfig: {
+ userPoolId: 'authapiunittests2778e848',
+ },
+ },
+ additionalAuthenticationProviders: [
+ {
+ authenticationType: 'AWS_IAM',
+ },
+ ],
+ },
+ },
+ },
+ },
+ }),
+ },
+ JSONUtilities: {
+ readJson: jest.fn().mockReturnValue({
+ ResolverConfig: {
+ project: {
+ ConflictHandler: 'AUTOMERGE',
+ ConflictDetection: 'VERSION',
+ },
+ },
+ }),
+ writeJson: jest.fn(),
+ },
+}));
+test('migrate resource', async () => {
+ const resourceName = 'apiunittests';
+ migrateResourceToSupportOverride(resourceName);
+ const expectedPath = path.join('mockProjectPath', 'cli-inputs.json');
+ const expectedPayload = {
+ version: 1,
+ serviceConfiguration: {
+ serviceName: 'AppSync',
+ defaultAuthType: {
+ mode: 'AMAZON_COGNITO_USER_POOLS',
+ cognitoUserPoolId: 'authapiunittests2778e848',
+ },
+ additionalAuthTypes: [
+ {
+ mode: 'AWS_IAM',
+ },
+ ],
+ conflictResolution: {
+ defaultResolutionStrategy: {
+ type: 'AUTOMERGE',
+ },
+ },
+ apiName: 'apiunittests',
+ gqlSchemaPath: 'mockProjectPath/schema.graphql',
+ },
+ };
+ expect(JSONUtilities.writeJson).toBeCalledWith(expectedPath, expectedPayload);
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper.test.ts
new file mode 100644
index 0000000000..e0f14fa597
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper.test.ts
@@ -0,0 +1,72 @@
+import { ConflictResolution } from 'amplify-headless-interface';
+import {
+ conflictResolutionToResolverConfig,
+ resolverConfigToConflictResolution,
+} from '../../../../provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper';
+import { ResolverConfig, ConflictHandlerType } from 'graphql-transformer-core';
+
+describe('transform ConflictResolution to ResolverConfig', () => {
+ it('maps properties correctly', () => {
+ const conflictResolution: ConflictResolution = {
+ defaultResolutionStrategy: {
+ type: 'AUTOMERGE',
+ },
+ perModelResolutionStrategy: [
+ {
+ entityName: 'MyType',
+ resolutionStrategy: {
+ type: 'LAMBDA',
+ resolver: {
+ type: 'EXISTING',
+ name: 'someLambdaName',
+ },
+ },
+ },
+ ],
+ };
+ expect(conflictResolutionToResolverConfig(conflictResolution)).toMatchSnapshot();
+ });
+
+ it('throws when trying to convert new lambda resolution strategy', () => {
+ const conflictResolution: ConflictResolution = {
+ defaultResolutionStrategy: {
+ type: 'LAMBDA',
+ resolver: {
+ type: 'NEW',
+ },
+ },
+ };
+
+ expect(() => conflictResolutionToResolverConfig(conflictResolution)).toThrowErrorMatchingSnapshot();
+ });
+
+ it('returns an empty object when ConflictResolution is not present', () => {
+ expect(conflictResolutionToResolverConfig(undefined)).toEqual(undefined);
+ });
+});
+
+describe('transform ResolverConfig to ConflictResolution', () => {
+ it('maps properties correctly', () => {
+ const resolverConfig: ResolverConfig = {
+ project: {
+ ConflictHandler: ConflictHandlerType.AUTOMERGE,
+ ConflictDetection: 'VERSION',
+ },
+ models: {
+ MyType: {
+ ConflictHandler: ConflictHandlerType.LAMBDA,
+ ConflictDetection: 'VERSION',
+ LambdaConflictHandler: {
+ name: 'myLambdaConflictHandler',
+ },
+ },
+ },
+ };
+
+ expect(resolverConfigToConflictResolution(resolverConfig)).toMatchSnapshot();
+ });
+
+ it('returns an empty object when ResolverConfig is not present', () => {
+ expect(resolverConfigToConflictResolution(undefined)).toEqual({});
+ });
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/rest-api-path-utils.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/rest-api-path-utils.test.ts
new file mode 100644
index 0000000000..6facce6d56
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/rest-api-path-utils.test.ts
@@ -0,0 +1,70 @@
+import {
+ validatePathName,
+ checkForPathOverlap,
+ formatCFNPathParamsForExpressJs,
+} from '../../../../provider-utils/awscloudformation/utils/rest-api-path-utils';
+
+const stubOtherPaths = ['/other/path', '/sub/path', '/path/{with}/{params}'];
+
+test('validatePathName_validPath', () => {
+ expect(validatePathName('/some/path')).toBe(true);
+ expect(validatePathName('/path/{with}/{params}')).toBe(true);
+ expect(validatePathName('/')).toBe(true);
+});
+
+test('validatePath_empty', () => {
+ expect(validatePathName('')).toStrictEqual('The path must not be empty');
+});
+
+test('validatePathName_noLeadingSlash', () => {
+ expect(validatePathName('no/leading/slash')).toStrictEqual('The path must begin with / e.g. /items');
+});
+
+test('validatePathName_hasTrailingSlash', () => {
+ expect(validatePathName('/has/trailing/slash/')).toStrictEqual('The path must not end with /');
+});
+
+test('validatePathName_invalidCharacters', () => {
+ // setup
+ const errorMessage =
+ 'Each path part must use characters a-z A-Z 0-9 - and must not be empty.\nOptionally, a path part can be surrounded by { } to denote a path parameter.';
+
+ // test
+ expect(validatePathName('/invalid+/{char}')).toStrictEqual(errorMessage);
+ expect(validatePathName('/invalid/{char@}')).toStrictEqual(errorMessage);
+ expect(validatePathName('/invalid/{param')).toStrictEqual(errorMessage);
+});
+
+test('checkForPathOverlap_subPathMatch', () => {
+ expect(checkForPathOverlap('/sub/path/match', stubOtherPaths)).toEqual({
+ higherOrderPath: '/sub/path',
+ lowerOrderPath: '/sub/path/match',
+ });
+});
+
+test('checkForPathOverlap_subPathParamsMatch', () => {
+ expect(checkForPathOverlap('/path/{with-different}/{params}', stubOtherPaths)).toEqual({
+ higherOrderPath: '/path/{with}/{params}',
+ lowerOrderPath: '/path/{with-different}/{params}',
+ });
+});
+
+test('checkForPathOverlap_subPathParamsNoMatch', () => {
+ expect(checkForPathOverlap('/path/{with-non-ovelapping-params}', stubOtherPaths)).toEqual(false);
+ expect(checkForPathOverlap('/path/{with}/non-overlapping-params', stubOtherPaths)).toEqual(false);
+ expect(checkForPathOverlap('/path/{with}/non/overlapping/params', stubOtherPaths)).toEqual(false);
+});
+
+test('checkForPathOverlap_pathMatch', () => {
+ expect(checkForPathOverlap(stubOtherPaths[0], stubOtherPaths)).toEqual({
+ higherOrderPath: stubOtherPaths[0],
+ lowerOrderPath: stubOtherPaths[0],
+ });
+});
+
+test('formatCFNPathParamsForExpressJs', () => {
+ expect(formatCFNPathParamsForExpressJs('/')).toStrictEqual('/');
+ expect(formatCFNPathParamsForExpressJs('/path')).toStrictEqual('/path');
+ expect(formatCFNPathParamsForExpressJs('/path/{param}')).toStrictEqual('/path/:param');
+ expect(formatCFNPathParamsForExpressJs('/path/{param}/suffix')).toStrictEqual('/path/:param/suffix');
+});
diff --git a/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/service-walkthrough-result-to-add-api-request.test.ts b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/service-walkthrough-result-to-add-api-request.test.ts
new file mode 100644
index 0000000000..0a742a6c62
--- /dev/null
+++ b/packages/amplify-category-api/src/__tests__/provider-utils/awscloudformation/utils/service-walkthrough-result-to-add-api-request.test.ts
@@ -0,0 +1,28 @@
+import { serviceWalkthroughResultToAddApiRequest } from '../../../../provider-utils/awscloudformation/utils/service-walkthrough-result-to-add-api-request';
+import { AppSyncAuthType, ConflictResolution } from 'amplify-headless-interface';
+
+jest.mock('../../../../provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper', () => ({
+ authConfigToAppSyncAuthType: jest.fn((): AppSyncAuthType => ({ mode: 'AWS_IAM' })),
+}));
+jest.mock('../../../../provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper', () => ({
+ resolverConfigToConflictResolution: jest.fn((): ConflictResolution => ({})),
+}));
+
+describe('walkthrough result to AddApiRequest conversion', () => {
+ it('maps properties correctly', () => {
+ const walkthroughResultStub = {
+ answers: {
+ apiName: 'myApiName',
+ },
+ schemaContent: 'mySchemaContent',
+ output: {
+ authConfig: {
+ defaultAuthentication: 'defaultAuth',
+ additionalAuthenticationProviders: ['otherAuth'],
+ },
+ },
+ };
+ const result = serviceWalkthroughResultToAddApiRequest(walkthroughResultStub);
+ expect(result).toMatchSnapshot();
+ });
+});
diff --git a/packages/amplify-category-api/src/category-constants.ts b/packages/amplify-category-api/src/category-constants.ts
new file mode 100644
index 0000000000..1b460621d7
--- /dev/null
+++ b/packages/amplify-category-api/src/category-constants.ts
@@ -0,0 +1,5 @@
+import { AmplifyCategories } from 'amplify-cli-core';
+
+export const ADMIN_QUERIES_NAME = 'AdminQueries';
+export const NETWORK_STACK_LOGICAL_ID = 'NetworkStack';
+export const category = AmplifyCategories.API;
diff --git a/packages/amplify-category-api/src/commands/api.ts b/packages/amplify-category-api/src/commands/api.ts
new file mode 100644
index 0000000000..5eab38a325
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api.ts
@@ -0,0 +1,64 @@
+import { $TSContext, AmplifyCategories } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import * as path from 'path';
+
+export const name = AmplifyCategories.API;
+
+export const run = async (context: $TSContext) => {
+ if (/^win/.test(process.platform)) {
+ try {
+ const { run } = await import(path.join('.', AmplifyCategories.API, context.parameters.first));
+ return run(context);
+ } catch (e) {
+ printer.error('Command not found');
+ }
+ }
+ const header = `amplify ${AmplifyCategories.API} `;
+ const commands = [
+ {
+ name: 'add',
+ description: `Takes you through a CLI flow to add a ${AmplifyCategories.API} resource to your local backend`,
+ },
+ {
+ name: 'push',
+ description: `Provisions ${AmplifyCategories.API} cloud resources and its dependencies with the latest local developments`,
+ },
+ {
+ name: 'remove',
+ description: `Removes ${AmplifyCategories.API} resource from your local backend which would be removed from the cloud on the next push command`,
+ },
+ {
+ name: 'update',
+ description: `Takes you through steps in the CLI to update an ${AmplifyCategories.API} resource`,
+ },
+ {
+ name: 'gql-compile',
+ description: 'Compiles your GraphQL schema and generates a corresponding cloudformation template',
+ },
+ {
+ name: 'add-graphql-datasource',
+ description: 'Provisions the AppSync resources and its dependencies for the provided Aurora Serverless data source',
+ },
+ {
+ name: 'console',
+ description: 'Opens the web console for the selected api service',
+ },
+ {
+ name: 'migrate',
+ description: 'Migrates GraphQL schemas to the latest GraphQL transformer version',
+ },
+ {
+ name: 'rebuild',
+ description:
+ 'Removes and recreates all DynamoDB tables backing a GraphQL API. Useful for resetting test data during the development phase of an app',
+ },
+ {
+ name: 'override',
+ description: 'Generates overrides file to apply custom modifications to CloudFormation',
+ },
+ ];
+
+ context.amplify.showHelp(header, commands);
+
+ printer.blankLine();
+};
diff --git a/packages/amplify-category-api/src/commands/api/add-graphql-datasource.ts b/packages/amplify-category-api/src/commands/api/add-graphql-datasource.ts
new file mode 100644
index 0000000000..31179381d3
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/add-graphql-datasource.ts
@@ -0,0 +1,248 @@
+import { mergeTypeDefs } from '@graphql-tools/merge';
+import {
+ $TSAny, $TSContext, exitOnNextTick, FeatureFlags, pathManager, ResourceDoesNotExistError, stateManager,
+} from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import * as graphql from 'graphql';
+import {
+ AuroraServerlessMySQLDatabaseReader,
+ RelationalDBSchemaTransformer,
+ RelationalDBTemplateGenerator,
+} from 'graphql-relational-schema-transformer';
+import inquirer from 'inquirer';
+import _ from 'lodash';
+import * as path from 'path';
+import { supportedDataSources } from '../../provider-utils/supported-datasources';
+
+const subcommand = 'add-graphql-datasource';
+const categories = 'categories';
+const category = 'api';
+const providerName = 'awscloudformation';
+
+export const name = subcommand;
+
+/**
+ * Entry point for adding RDS data source
+ */
+export const run = async (context: $TSContext): Promise => {
+ try {
+ const AWS = await getAwsClient(context, 'list');
+
+ const result: $TSAny = await datasourceSelectionPrompt(context, supportedDataSources);
+
+ const providerController = await import(path.join('..', '..', 'provider-utils', result.providerName, 'index'));
+
+ if (!providerController) {
+ printer.error('Provider not configured for this category');
+ return;
+ }
+
+ const { datasource } = result;
+ const answers = await providerController.addDatasource(context, category, datasource);
+
+ const { resourceName, databaseName } = answers;
+
+ /**
+ * Write the new env specific datasource information into
+ * the team-provider-info file
+ */
+ const currentEnv = context.amplify.getEnvInfo().envName;
+ const teamProviderInfo = stateManager.getTeamProviderInfo();
+
+ _.set(teamProviderInfo, [currentEnv, categories, category, resourceName], {
+ rdsRegion: answers.region,
+ rdsClusterIdentifier: answers.dbClusterArn,
+ rdsSecretStoreArn: answers.secretStoreArn,
+ rdsDatabaseName: answers.databaseName,
+ });
+
+ stateManager.setTeamProviderInfo(undefined, teamProviderInfo);
+
+ const backendConfig = stateManager.getBackendConfig();
+
+ backendConfig[category][resourceName].rdsInit = true;
+
+ stateManager.setBackendConfig(undefined, backendConfig);
+
+ /**
+ * Load the MySqlRelationalDBReader
+ */
+ const dbReader = new AuroraServerlessMySQLDatabaseReader(
+ answers.region,
+ answers.secretStoreArn,
+ answers.dbClusterArn,
+ answers.databaseName,
+ AWS,
+ );
+
+ /**
+ * Instantiate a new Relational Schema Transformer and perform
+ * the db introspection to get the GraphQL Schema and Template Context
+ */
+ // eslint-disable-next-line spellcheck/spell-checker
+ const improvePluralizationFlag = FeatureFlags.getBoolean('graphqltransformer.improvePluralization');
+ const relationalSchemaTransformer = new RelationalDBSchemaTransformer(dbReader, answers.databaseName, improvePluralizationFlag);
+ const graphqlSchemaContext = await relationalSchemaTransformer.introspectDatabaseSchema();
+
+ if (graphqlSchemaContext === null) {
+ printer.warn('No importable tables were found in the selected Database.');
+ printer.blankLine();
+ return;
+ }
+
+ /**
+ * Merge the GraphQL Schema with the existing schema.graphql in the projects stack
+ *
+ */
+ const apiDirPath = pathManager.getResourceDirectoryPath(undefined, category, resourceName);
+
+ fs.ensureDirSync(apiDirPath);
+
+ const graphqlSchemaFilePath = path.join(apiDirPath, 'schema.graphql');
+ const rdsGraphQLSchemaDoc = graphqlSchemaContext.schemaDoc;
+ const schemaDirectoryPath = path.join(apiDirPath, 'schema');
+
+ if (fs.existsSync(graphqlSchemaFilePath)) {
+ const typesToBeMerged = [rdsGraphQLSchemaDoc];
+ const currentGraphQLSchemaDoc = readSchema(graphqlSchemaFilePath);
+
+ if (currentGraphQLSchemaDoc) {
+ typesToBeMerged.unshift(currentGraphQLSchemaDoc);
+ } else {
+ printer.warn(`Graphql Schema file "${graphqlSchemaFilePath}" is empty.`);
+ printer.blankLine();
+ }
+
+ const concatGraphQLSchemaDoc = mergeTypeDefs(typesToBeMerged);
+
+ fs.writeFileSync(graphqlSchemaFilePath, graphql.print(concatGraphQLSchemaDoc), 'utf8');
+ } else if (fs.existsSync(schemaDirectoryPath)) {
+ const rdsSchemaFilePath = path.join(schemaDirectoryPath, 'rds.graphql');
+
+ fs.writeFileSync(rdsSchemaFilePath, graphql.print(rdsGraphQLSchemaDoc), 'utf8');
+ } else {
+ throw new Error(`Could not find a schema in either ${graphqlSchemaFilePath} or schema directory at ${schemaDirectoryPath}`);
+ }
+
+ const resolversDir = path.join(apiDirPath, 'resolvers');
+
+ /**
+ * Instantiate a new Relational Template Generator and create
+ * the template and relational resolvers
+ */
+
+ const templateGenerator = new RelationalDBTemplateGenerator(graphqlSchemaContext);
+
+ let template = templateGenerator.createTemplate(context);
+
+ template = templateGenerator.addRelationalResolvers(template, resolversDir, improvePluralizationFlag);
+
+ const cfn = templateGenerator.printCloudformationTemplate(template);
+
+ /**
+ * Add the generated the CFN to the appropriate nested stacks directory
+ */
+
+ const stacksDir = path.join(apiDirPath, 'stacks');
+ const writeToPath = path.join(stacksDir, `${resourceName}-${databaseName}-rds.json`);
+
+ fs.writeFileSync(writeToPath, cfn, 'utf8');
+
+ context.amplify.executeProviderUtils(context, 'awscloudformation', 'compileSchema', { forceCompile: true });
+
+ printer.success(`Successfully added the ${datasource} datasource locally`);
+ printer.blankLine();
+ printer.success('Some next steps:');
+ printer.info('"amplify push" will build all your local backend resources and provision it in the cloud');
+ printer.info(
+ '"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud',
+ );
+ printer.blankLine();
+ } catch (error) {
+ printer.error('There was an error adding the datasource');
+ throw error;
+ }
+};
+
+// eslint-disable-next-line @typescript-eslint/no-shadow
+const datasourceSelectionPrompt = async (context: $TSContext, supportedDataSources): Promise => {
+ const options = [];
+ Object.keys(supportedDataSources).forEach(datasource => {
+ const optionName = supportedDataSources[datasource].alias
+ || `${supportedDataSources[datasource].providerName}:${supportedDataSources[datasource].service}`;
+ options.push({
+ name: optionName,
+ value: {
+ provider: supportedDataSources[datasource].provider,
+ datasource,
+ providerName: supportedDataSources[datasource].provider,
+ },
+ });
+ });
+
+ if (options.length === 0) {
+ const errMessage = `No data sources defined by configured providers for category: ${category}`;
+
+ printer.error(errMessage);
+
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+
+ exitOnNextTick(1);
+ }
+
+ if (options.length === 1) {
+ // No need to ask questions
+ printer.info(`Using datasource: ${options[0].value.datasource}, provided by: ${options[0].value.providerName}`);
+
+ return new Promise(resolve => {
+ resolve(options[0].value);
+ });
+ }
+
+ const question = [
+ {
+ name: 'datasource',
+ message: 'Please select from one of the below mentioned data sources',
+ type: 'list',
+ choices: options,
+ },
+ ];
+
+ return inquirer.prompt(question).then(answer => answer.datasource);
+};
+
+const getAwsClient = async (context: $TSContext, action: string): Promise<$TSAny> => {
+ const providerPlugins = context.amplify.getProviderPlugins(context);
+ // eslint-disable-next-line
+ const provider = require(providerPlugins[providerName]);
+
+ return provider.getConfiguredAWSClient(context, 'aurora-serverless', action);
+};
+
+/**
+ * Read the GraphQL schema
+ */
+export const readSchema = (graphqlSchemaFilePath: string): graphql.DocumentNode => {
+ const graphqlSchemaRaw = fs.readFileSync(graphqlSchemaFilePath).toString();
+
+ if (graphqlSchemaRaw.trim().length === 0) {
+ return null;
+ }
+
+ let currentGraphQLSchemaDoc: graphql.DocumentNode;
+
+ try {
+ currentGraphQLSchemaDoc = graphql.parse(graphqlSchemaRaw);
+ } catch (err) {
+ const relativePathToInput = path.relative(process.cwd(), graphqlSchemaRaw);
+
+ const error = new Error(`Could not parse graphql schema \n${relativePathToInput}\n${err.message}`);
+
+ error.stack = undefined;
+
+ throw error;
+ }
+
+ return currentGraphQLSchemaDoc;
+};
diff --git a/packages/amplify-category-api/src/commands/api/add.ts b/packages/amplify-category-api/src/commands/api/add.ts
new file mode 100644
index 0000000000..53eaa40181
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/add.ts
@@ -0,0 +1,62 @@
+import { $TSContext, $TSObject, AmplifyCategories, AmplifySupportedService } from 'amplify-cli-core';
+import { printer, prompter } from 'amplify-prompts';
+import * as path from 'path';
+
+const subcommand = 'add';
+const category = AmplifyCategories.API;
+
+export const name = subcommand;
+
+export const run = async (context: $TSContext) => {
+ const servicesMetadata = (await import(path.join('..', '..', 'provider-utils', 'supported-services'))).supportedServices;
+ return context.amplify
+ .serviceSelectionPrompt(context, category, servicesMetadata)
+ .then(async result => {
+ const options = {
+ service: result.service,
+ providerPlugin: result.providerName,
+ };
+ const providerController = await import(path.join('..', '..', 'provider-utils', result.providerName, 'index'));
+ if (!providerController) {
+ printer.error('Provider not configured for this category');
+ return;
+ }
+
+ if ((await shouldUpdateExistingRestApi(context, result.service)) === true) {
+ return providerController.updateResource(context, category, result.service, { allowContainers: false });
+ }
+
+ return providerController.addResource(context, result.service, options);
+ })
+ .then((resourceName: string) => {
+ printer.success(`Successfully added resource ${resourceName} locally`);
+ printer.blankLine();
+ printer.success('Some next steps:');
+ printer.info('"amplify push" will build all your local backend resources and provision it in the cloud');
+ printer.info(
+ '"amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud',
+ );
+ printer.blankLine();
+ })
+ .catch(async err => {
+ printer.error('There was an error adding the API resource');
+ throw err;
+ });
+};
+
+async function shouldUpdateExistingRestApi(context: $TSContext, selectedService: string): Promise {
+ if (selectedService !== AmplifySupportedService.APIGW) {
+ return false;
+ }
+
+ const { allResources } = await context.amplify.getResourceStatus();
+ const hasRestApis = allResources.some(
+ (resource: $TSObject) => resource.service === AmplifySupportedService.APIGW && resource.mobileHubMigrated !== true,
+ );
+
+ if (!hasRestApis) {
+ return false;
+ }
+
+ return prompter.confirmContinue('Would you like to add a new path to an existing REST API:');
+}
diff --git a/packages/amplify-category-api/src/commands/api/console.ts b/packages/amplify-category-api/src/commands/api/console.ts
new file mode 100644
index 0000000000..3840cc4366
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/console.ts
@@ -0,0 +1,22 @@
+import { $TSContext, AmplifyCategories } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import * as path from 'path';
+
+const subcommand = 'console';
+
+export const name = subcommand;
+
+export const run = async (context: $TSContext) => {
+ const servicesMetadata = (await import(path.join('..', '..', 'provider-utils', 'supported-services'))).supportedServices;
+ const result = await context.amplify.serviceSelectionPrompt(context, AmplifyCategories.API, servicesMetadata);
+ try {
+ const providerController = await import(path.join('..', '..', 'provider-utils', result.providerName, 'index'));
+ if (!providerController) {
+ throw new Error(`Provider "${result.providerName}" is not configured for this category`);
+ }
+ return providerController.console(context, result.service);
+ } catch (err) {
+ printer.error('Error opening console.');
+ throw err;
+ }
+};
diff --git a/packages/amplify-category-api/src/commands/api/gql-compile.ts b/packages/amplify-category-api/src/commands/api/gql-compile.ts
new file mode 100644
index 0000000000..32ef55c489
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/gql-compile.ts
@@ -0,0 +1,15 @@
+import { $TSContext } from 'amplify-cli-core';
+
+const subcommand = 'gql-compile';
+
+export const name = subcommand;
+
+export const run = async (context: $TSContext) => {
+ const {
+ parameters: { options },
+ } = context;
+ return context.amplify.executeProviderUtils(context, 'awscloudformation', 'compileSchema', {
+ forceCompile: true,
+ minify: options['minify'],
+ });
+};
diff --git a/packages/amplify-category-api/src/commands/api/migrate.ts b/packages/amplify-category-api/src/commands/api/migrate.ts
new file mode 100644
index 0000000000..3389023cad
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/migrate.ts
@@ -0,0 +1,40 @@
+import { $TSAny, $TSContext, AmplifyCategories, pathManager, stateManager } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import { attemptV2TransformerMigration, revertV2Migration } from '@aws-amplify/graphql-transformer-migrator';
+import * as path from 'path';
+import { checkAppsyncApiResourceMigration } from '../../provider-utils/awscloudformation/utils/check-appsync-api-migration';
+
+const subcommand = 'migrate';
+
+export const name = subcommand;
+
+export const run = async (context: $TSContext) => {
+ const apiNames = Object.entries(stateManager.getMeta()?.api || {})
+ .filter(([_, apiResource]) => (apiResource as $TSAny).service === 'AppSync')
+ .map(([name]) => name);
+ if (apiNames.length === 0) {
+ printer.info(
+ 'No GraphQL API configured in the project. Only GraphQL APIs can be migrated. To add a GraphQL API run `amplify add api`.',
+ );
+ return;
+ }
+ if (apiNames.length > 1) {
+ // this condition should never hit as we have upstream defensive logic to prevent multiple GraphQL APIs. But just to cover all the bases
+ printer.error(
+ 'You have multiple GraphQL APIs in the project. Only one GraphQL API is allowed per project. Run `amplify remove api` to remove an API.',
+ );
+ return;
+ }
+ const apiName = apiNames[0];
+ const apiResourceDir = path.join(pathManager.getBackendDirPath(), AmplifyCategories.API, apiName);
+
+ if (await checkAppsyncApiResourceMigration(context, apiName, true)) {
+ await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'compileSchema', [context, { forceCompile: true }]);
+ }
+
+ if (context.parameters?.options?.revert) {
+ await revertV2Migration(apiResourceDir, stateManager.getCurrentEnvName());
+ return;
+ }
+ await attemptV2TransformerMigration(apiResourceDir, apiName, stateManager.getCurrentEnvName());
+};
diff --git a/packages/amplify-category-api/src/commands/api/override.ts b/packages/amplify-category-api/src/commands/api/override.ts
new file mode 100644
index 0000000000..e201258fc0
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/override.ts
@@ -0,0 +1,98 @@
+import {
+ $TSContext,
+ $TSObject,
+ AmplifyCategories,
+ AmplifySupportedService,
+ ApiCategoryFacade,
+ generateOverrideSkeleton,
+ pathManager,
+ stateManager,
+} from 'amplify-cli-core';
+import { printer, prompter } from 'amplify-prompts';
+import * as path from 'path';
+import { ADMIN_QUERIES_NAME } from '../../category-constants';
+import { AdminQueriesProps, ApigwInputState } from '../../provider-utils/awscloudformation/apigw-input-state';
+import { ApigwStackTransform } from '../../provider-utils/awscloudformation/cdk-stack-builder';
+import { checkAppsyncApiResourceMigration } from '../../provider-utils/awscloudformation/utils/check-appsync-api-migration';
+
+export const name = 'override';
+
+export const run = async (context: $TSContext) => {
+ const amplifyMeta = stateManager.getMeta();
+ const apiResources: string[] = [];
+
+ if (amplifyMeta[AmplifyCategories.API]) {
+ Object.keys(amplifyMeta[AmplifyCategories.API]).forEach(resourceName => {
+ apiResources.push(resourceName);
+ });
+ }
+
+ if (apiResources.length === 0) {
+ const errMessage = 'No resources to override. You need to add a resource.';
+ printer.error(errMessage);
+ return;
+ }
+
+ let selectedResourceName: string = apiResources[0];
+
+ if (apiResources.length > 1) {
+ selectedResourceName = await prompter.pick('Which resource would you like to add overrides for?', apiResources);
+ }
+
+ const { service }: { service: string } = amplifyMeta[AmplifyCategories.API][selectedResourceName];
+ const destPath = pathManager.getResourceDirectoryPath(undefined, AmplifyCategories.API, selectedResourceName);
+
+ const srcPath = path.join(
+ __dirname,
+ '..',
+ '..',
+ '..',
+ 'resources',
+ 'awscloudformation',
+ 'overrides-resource',
+ service === AmplifySupportedService.APIGW ? 'APIGW' : service, // avoid space in filename
+ );
+
+ // Make sure to migrate first
+ if (service === AmplifySupportedService.APPSYNC) {
+ /**
+ * Below steps checks for TransformerV1 app and updates the FF { useexperimentalpipelinedtransformer , transformerversion}
+ */
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ if (transformerVersion === 2 && (await checkAppsyncApiResourceMigration(context, selectedResourceName, false))) {
+ await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'compileSchema', [context, { forceCompile: true }]);
+ await generateOverrideSkeleton(context, srcPath, destPath);
+ } else {
+ printer.warn(
+ 'The GraphQL API is using transformer version 1. Run `amplify migrate api` to upgrade to transformer version 2 and rerun amplify override api to enable override functionality for API',
+ );
+ }
+ } else if (service === AmplifySupportedService.APIGW) {
+ // Migration logic goes in here
+ const apigwInputState = new ApigwInputState(context, selectedResourceName);
+ if (!apigwInputState.cliInputsFileExists()) {
+ if (selectedResourceName === ADMIN_QUERIES_NAME) {
+ const { dependsOn }: { dependsOn: $TSObject[] } = amplifyMeta[AmplifyCategories.API][selectedResourceName];
+ if (!Array.isArray(dependsOn) || dependsOn.length === 0) {
+ throw new Error(`Invalid dependsOn entry found in amplify-meta.json for "${ADMIN_QUERIES_NAME}"`);
+ }
+
+ const getResourceNameFromDependsOn = (categoryName: string, dependsOn: $TSObject[]) =>
+ dependsOn.filter(entry => entry.category === categoryName)[0].resourceName;
+
+ const props: AdminQueriesProps = {
+ apiName: selectedResourceName,
+ authResourceName: getResourceNameFromDependsOn(AmplifyCategories.AUTH, dependsOn),
+ functionName: getResourceNameFromDependsOn(AmplifyCategories.FUNCTION, dependsOn),
+ dependsOn: dependsOn,
+ };
+ await apigwInputState.migrateAdminQueries(props);
+ } else {
+ await apigwInputState.migrateApigwResource(selectedResourceName);
+ const stackGenerator = new ApigwStackTransform(context, selectedResourceName);
+ stackGenerator.transform();
+ }
+ }
+ await generateOverrideSkeleton(context, srcPath, destPath);
+ }
+};
diff --git a/packages/amplify-category-api/src/commands/api/push.ts b/packages/amplify-category-api/src/commands/api/push.ts
new file mode 100644
index 0000000000..0defd33099
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/push.ts
@@ -0,0 +1,11 @@
+import { $TSContext, AmplifyCategories } from 'amplify-cli-core';
+
+const subcommand = 'push';
+
+export const name = subcommand;
+
+export const run = async (context: $TSContext) => {
+ const resourceName = context.parameters.first;
+ context.amplify.constructExeInfo(context);
+ return context.amplify.pushResources(context, AmplifyCategories.API, resourceName);
+};
diff --git a/packages/amplify-category-api/src/commands/api/rebuild.ts b/packages/amplify-category-api/src/commands/api/rebuild.ts
new file mode 100644
index 0000000000..55c269da95
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/rebuild.ts
@@ -0,0 +1,39 @@
+import { $TSAny, $TSContext, AmplifyCategories, FeatureFlags, stateManager } from 'amplify-cli-core';
+import { printer, prompter, exact } from 'amplify-prompts';
+
+const subcommand = 'rebuild';
+
+export const name = subcommand;
+
+const rebuild = true;
+
+export const run = async (context: $TSContext) => {
+ if (!FeatureFlags.getBoolean('graphqlTransformer.enableIterativeGSIUpdates')) {
+ printer.error('Iterative GSI Updates must be enabled to rebuild an API. See https://docs.amplify.aws/cli/reference/feature-flags/');
+ return;
+ }
+ const apiNames = Object.entries(stateManager.getMeta()?.api || {})
+ .filter(([_, apiResource]) => (apiResource as $TSAny).service === 'AppSync')
+ .map(([name]) => name);
+ if (apiNames.length === 0) {
+ printer.info('No GraphQL API configured in the project. Only GraphQL APIs can be rebuilt. To add a GraphQL API run `amplify add api`.');
+ return;
+ }
+ if (apiNames.length > 1) {
+ // this condition should never hit as we have upstream defensive logic to prevent multiple GraphQL APIs. But just to cover all the bases
+ printer.error(
+ 'You have multiple GraphQL APIs in the project. Only one GraphQL API is allowed per project. Run `amplify remove api` to remove an API.',
+ );
+ return;
+ }
+ const apiName = apiNames[0];
+ printer.warn(`This will recreate all tables backing models in your GraphQL API ${apiName}.`);
+ printer.warn('ALL EXISTING DATA IN THESE TABLES WILL BE LOST.');
+ await prompter.input('Type the name of the API to confirm you want to continue', {
+ validate: exact(apiName, 'Input does not match the GraphQL API name'),
+ });
+ const { amplify, parameters } = context;
+ const resourceName = parameters.first;
+ amplify.constructExeInfo(context);
+ return amplify.pushResources(context, AmplifyCategories.API, resourceName, undefined, rebuild);
+};
diff --git a/packages/amplify-category-api/src/commands/api/remove.ts b/packages/amplify-category-api/src/commands/api/remove.ts
new file mode 100644
index 0000000000..e6a78c9323
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/remove.ts
@@ -0,0 +1,30 @@
+import { $TSContext, AmplifyCategories, AmplifySupportedService } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import * as path from 'path';
+
+const subcommand = 'remove';
+const gqlConfigFilename = '.graphqlconfig.yml';
+
+export const name = subcommand;
+
+export const run = async (context: $TSContext) => {
+ const resourceName = context.parameters.first;
+
+ const resourceValues = await context.amplify.removeResource(context, AmplifyCategories.API, resourceName, {
+ serviceSuffix: { [AmplifySupportedService.APPSYNC]: '(GraphQL API)', [AmplifySupportedService.APIGW]: '(REST API)' },
+ });
+ try {
+ if (!resourceValues) {
+ return;
+ } // indicates that the customer selected "no" at the confirmation prompt
+ if (resourceValues.service === AmplifySupportedService.APPSYNC) {
+ const { projectPath } = context.amplify.getEnvInfo();
+
+ const gqlConfigFile = path.normalize(path.join(projectPath, gqlConfigFilename));
+ context.filesystem.remove(gqlConfigFile);
+ }
+ } catch (err) {
+ printer.error('There was an error removing the api resource');
+ throw err;
+ }
+};
diff --git a/packages/amplify-category-api/src/commands/api/update.ts b/packages/amplify-category-api/src/commands/api/update.ts
new file mode 100644
index 0000000000..ce3490f7f7
--- /dev/null
+++ b/packages/amplify-category-api/src/commands/api/update.ts
@@ -0,0 +1,24 @@
+import { $TSContext, AmplifyCategories } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import * as path from 'path';
+
+const subcommand = 'update';
+
+export const name = subcommand;
+export const alias = ['configure'];
+
+export const run = async (context: $TSContext) => {
+ const servicesMetadata = (await import(path.join('..', '..', 'provider-utils', 'supported-services'))).supportedServices;
+
+ return context.amplify
+ .serviceSelectionPrompt(context, AmplifyCategories.API, servicesMetadata)
+ .then(async result => {
+ const providerController = await import(path.join('..', '..', 'provider-utils', result.providerName, 'index'));
+ if (!providerController) {
+ printer.error('Provider not configured for this category');
+ return;
+ }
+ return providerController.updateResource(context, AmplifyCategories.API, result.service);
+ })
+ .then(() => printer.success('Successfully updated resource'));
+};
diff --git a/packages/amplify-category-api/src/graphql-transformer/amplify-cli-feature-flag-adapter.ts b/packages/amplify-category-api/src/graphql-transformer/amplify-cli-feature-flag-adapter.ts
new file mode 100644
index 0000000000..24b977a416
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/amplify-cli-feature-flag-adapter.ts
@@ -0,0 +1,41 @@
+import { FeatureFlags } from 'amplify-cli-core';
+import { FeatureFlagProvider } from 'graphql-transformer-core';
+import { FeatureFlagProvider as NewTransformerFFProvider } from '@aws-amplify/graphql-transformer-interfaces'
+export class AmplifyCLIFeatureFlagAdapterBase implements FeatureFlagProvider {
+ getBoolean(featureName: string, defaultValue?: boolean): boolean {
+ return this.getValue(featureName, 'boolean', defaultValue);
+ }
+ getString(featureName: string, defaultValue?: string): string {
+ return this.getValue(featureName, 'string', defaultValue);
+ }
+ getNumber(featureName: string, defaultValue?: number): number {
+ return this.getValue(featureName, 'number', defaultValue);
+ }
+ getObject(): object {
+ // Todo: for future extensibility
+ throw new Error('Not implemented');
+ }
+
+ protected getValue(featureName: string, type: 'boolean' | 'number' | 'string', defaultValue: T): T {
+ const keyName = `graphQLTransformer.${featureName}`;
+ try {
+ switch (type) {
+ case 'boolean':
+ return FeatureFlags.getBoolean(keyName) as T;
+ case 'number':
+ return FeatureFlags.getNumber(keyName) as T;
+ case 'string':
+ return FeatureFlags.getString(keyName) as T;
+ }
+ } catch (e) {
+ if (defaultValue) {
+ return defaultValue;
+ }
+ throw e;
+ }
+ }
+}
+
+// Mapping to new type to ensure the provider interface is implemented
+export class AmplifyCLIFeatureFlagAdapter
+ extends AmplifyCLIFeatureFlagAdapterBase implements NewTransformerFFProvider {}
\ No newline at end of file
diff --git a/packages/amplify-category-api/src/graphql-transformer/api-key-helpers.ts b/packages/amplify-category-api/src/graphql-transformer/api-key-helpers.ts
new file mode 100644
index 0000000000..abab1949be
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/api-key-helpers.ts
@@ -0,0 +1,6 @@
+import { $TSContext, CloudformationProviderFacade } from 'amplify-cli-core';
+
+export async function hasApiKey(context: $TSContext): Promise {
+ const apiKeyConfig = await CloudformationProviderFacade.getApiKeyConfig(context);
+ return !!apiKeyConfig && !!apiKeyConfig?.apiKeyExpirationDays;
+}
diff --git a/packages/amplify-category-api/src/graphql-transformer/api-utils.ts b/packages/amplify-category-api/src/graphql-transformer/api-utils.ts
new file mode 100644
index 0000000000..661608fd97
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/api-utils.ts
@@ -0,0 +1,24 @@
+import { stateManager, getGraphQLTransformerOpenSearchProductionDocLink, ApiCategoryFacade } from "amplify-cli-core";
+import { printer } from "amplify-prompts";
+import { ResourceConstants } from "graphql-transformer-common";
+import _ from "lodash";
+
+export async function searchablePushChecks(context, map, apiName): Promise {
+ const searchableModelTypes = Object.keys(map).filter(type => map[type].includes('searchable') && map[type].includes('model'));
+ if (searchableModelTypes.length) {
+ const currEnv = context.amplify.getEnvInfo().envName;
+ const teamProviderInfo = stateManager.getTeamProviderInfo();
+ const getInstanceType = (instanceTypeParam: string) => _.get(teamProviderInfo, [currEnv, 'categories', 'api', apiName, instanceTypeParam]);
+ const instanceType =
+ getInstanceType(ResourceConstants.PARAMETERS.OpenSearchInstanceType) ??
+ getInstanceType(ResourceConstants.PARAMETERS.ElasticsearchInstanceType) ??
+ 't2.small.elasticsearch';
+ if (instanceType === 't2.small.elasticsearch' || instanceType === 't3.small.elasticsearch') {
+ const version = await ApiCategoryFacade.getTransformerVersion(context);
+ const docLink = getGraphQLTransformerOpenSearchProductionDocLink(version);
+ printer.warn(
+ `Your instance type for OpenSearch is ${instanceType}, you may experience performance issues or data loss. Consider reconfiguring with the instructions here ${docLink}`,
+ );
+ }
+ }
+ }
\ No newline at end of file
diff --git a/packages/amplify-category-api/src/graphql-transformer/auth-mode-compare.ts b/packages/amplify-category-api/src/graphql-transformer/auth-mode-compare.ts
new file mode 100644
index 0000000000..8d97b0c226
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/auth-mode-compare.ts
@@ -0,0 +1,38 @@
+import _ from "lodash";
+
+export function isAuthModeUpdated(options): boolean {
+ const { authConfig, previousAuthConfig } = getAuthConfigForCompare(options);
+ return authConfig && previousAuthConfig && !_.isEqual(authConfig, previousAuthConfig);
+}
+
+function getAuthConfigForCompare(options) {
+ if (!(options.authConfig && options.previousAuthConfig)) {
+ return {};
+ }
+
+ // Deep copy the authConfig for comparision
+ let authConfig = _.cloneDeep(options.authConfig);
+ let previousAuthConfig = _.cloneDeep(options.previousAuthConfig);
+
+ // Remove apiKeyExpirationDate key for comparision as this may change even if there are no auth mode changes
+ if (authConfig) {
+ authConfig.defaultAuthentication = removeApiKeyExpirationDate(authConfig.defaultAuthentication);
+ authConfig.additionalAuthenticationProviders = authConfig.additionalAuthenticationProviders?.map(mode => removeApiKeyExpirationDate(mode));
+ }
+ if (previousAuthConfig) {
+ previousAuthConfig.defaultAuthentication = removeApiKeyExpirationDate(previousAuthConfig.defaultAuthentication);
+ previousAuthConfig.additionalAuthenticationProviders = previousAuthConfig.additionalAuthenticationProviders?.map(mode => removeApiKeyExpirationDate(mode));
+ }
+
+ return {
+ authConfig,
+ previousAuthConfig,
+ };
+}
+
+function removeApiKeyExpirationDate(mode) {
+ if (mode?.apiKeyConfig) {
+ delete mode.apiKeyConfig.apiKeyExpirationDate;
+ }
+ return mode;
+}
\ No newline at end of file
diff --git a/packages/amplify-category-api/src/graphql-transformer/directive-definitions.ts b/packages/amplify-category-api/src/graphql-transformer/directive-definitions.ts
new file mode 100644
index 0000000000..376d50b0e1
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/directive-definitions.ts
@@ -0,0 +1,27 @@
+import {
+ getAppSyncServiceExtraDirectives,
+} from '@aws-amplify/graphql-transformer-core';
+import {
+ $TSContext,
+} from 'amplify-cli-core';
+import { print } from 'graphql';
+import { getTransformerFactory } from './transformer-factory';
+import { getTransformerVersion } from './transformer-version';
+
+/**
+ * Return the set of directive definitions for the project, includes both appsync and amplify supported directives.
+ * This will return the relevant set determined by whether or not the customer is using GQL transformer v1 or 2 in their project.
+ */
+export const getDirectiveDefinitions = async (context: $TSContext, resourceDir: string): Promise => {
+ const transformerVersion = await getTransformerVersion(context);
+ const transformer = await getTransformerFactory(context, resourceDir);
+ const transformList = transformerVersion === 2
+ ? await transformer({ addSearchableTransformer: true, authConfig: {} })
+ : await transformer(true);
+
+ const transformDirectives = transformList
+ .map(transformPluginInst => [transformPluginInst.directive, ...transformPluginInst.typeDefinitions].map(node => print(node)).join('\n'))
+ .join('\n');
+
+ return [getAppSyncServiceExtraDirectives(), transformDirectives].join('\n');
+};
diff --git a/packages/amplify-category-api/src/graphql-transformer/index.ts b/packages/amplify-category-api/src/graphql-transformer/index.ts
new file mode 100644
index 0000000000..9fa8410dc3
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/index.ts
@@ -0,0 +1,4 @@
+export { getDirectiveDefinitions } from './directive-definitions';
+export { getTransformerVersion } from './transformer-version';
+export { SLOT_NAMES, parseUserDefinedSlots } from './user-defined-slots';
+export { transformGraphQLSchema } from './transform-graphql-schema';
diff --git a/packages/amplify-category-api/src/graphql-transformer/sandbox-mode-helpers.ts b/packages/amplify-category-api/src/graphql-transformer/sandbox-mode-helpers.ts
new file mode 100644
index 0000000000..ad73aee882
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/sandbox-mode-helpers.ts
@@ -0,0 +1,69 @@
+import chalk from 'chalk';
+import { $TSContext } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import { parse } from 'graphql';
+import { hasApiKey } from './api-key-helpers';
+
+const AMPLIFY = 'AMPLIFY';
+const AUTHORIZATION_RULE = 'AuthRule';
+const ALLOW = 'allow';
+const PUBLIC = 'public';
+
+export async function showSandboxModePrompts(context: $TSContext): Promise {
+ if (!(await hasApiKey(context))) {
+ printer.info(
+ `
+⚠️ WARNING: Global Sandbox Mode has been enabled, which requires a valid API key. If
+you'd like to disable, remove ${chalk.green('"input AMPLIFY { globalAuthRule: AuthRule = { allow: public } }"')}
+from your GraphQL schema and run 'amplify push' again. If you'd like to proceed with
+sandbox mode disabled, do not create an API Key.
+`,
+ 'yellow',
+ );
+ return await context.amplify.invokePluginMethod(context, 'api', undefined, 'promptToAddApiKey', [context]);
+ }
+}
+
+export function showGlobalSandboxModeWarning(doclink: string): void {
+ printer.info(
+ `
+⚠️ WARNING: your GraphQL API currently allows public create, read, update, and delete access to all models via an API Key. To configure PRODUCTION-READY authorization rules, review: ${doclink}
+`,
+ 'yellow',
+ );
+}
+
+function matchesGlobalAuth(field: any): boolean {
+ return ['global_auth_rule', 'globalAuthRule'].includes(field.name.value);
+}
+
+export function schemaHasSandboxModeEnabled(schema: string, docLink: string): boolean {
+ const { definitions } = parse(schema);
+ const amplifyInputType: any = definitions.find((d: any) => d.kind === 'InputObjectTypeDefinition' && d.name.value === AMPLIFY);
+
+ if (!amplifyInputType) {
+ return false;
+ }
+
+ const authRuleField = amplifyInputType.fields.find(matchesGlobalAuth);
+
+ if (!authRuleField) {
+ throw Error(`input AMPLIFY requires "globalAuthRule" field. Learn more here: ${docLink}`);
+ }
+
+ const typeName = authRuleField.type.name.value;
+ const defaultValueField = authRuleField.defaultValue.fields[0];
+ const defaultValueName = defaultValueField.name.value;
+ const defaultValueValue = defaultValueField.value.value;
+ const authScalarMatch = typeName === AUTHORIZATION_RULE;
+ const defaultValueNameMatch = defaultValueName === ALLOW;
+ const defaultValueValueMatch = defaultValueValue === PUBLIC;
+
+ if (authScalarMatch && defaultValueNameMatch && defaultValueValueMatch) {
+ return true;
+ } else {
+ throw Error(
+ `There was a problem with your auth configuration. Learn more about auth here: ${docLink}`,
+ );
+ }
+}
diff --git a/packages/amplify-category-api/src/graphql-transformer/transform-config.ts b/packages/amplify-category-api/src/graphql-transformer/transform-config.ts
new file mode 100644
index 0000000000..d48a0a5e64
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/transform-config.ts
@@ -0,0 +1,98 @@
+import { TransformConfig } from '@aws-amplify/graphql-transformer-core';
+import fs from 'fs-extra';
+import { TRANSFORM_CONFIG_FILE_NAME } from 'graphql-transformer-core';
+import * as path from 'path';
+
+export interface ProjectOptions {
+ projectDirectory?: string;
+ transformersFactory: Function;
+ transformersFactoryArgs: object[];
+ currentCloudBackendDirectory: string;
+ rootStackFileName?: string;
+ dryRun?: boolean;
+ disableFunctionOverrides?: boolean;
+ disablePipelineFunctionOverrides?: boolean;
+ disableResolverOverrides?: boolean;
+ buildParameters?: Object;
+ minify?: boolean;
+}
+
+/**
+ * try to load transformer config from specified projectDir
+ * if it does not exist then we return a blank object
+ * */
+
+export async function loadConfig(projectDir: string): Promise {
+ // Initialize the config always with the latest version, other members are optional for now.
+ let config: TransformConfig = {};
+ try {
+ const configPath = path.join(projectDir, TRANSFORM_CONFIG_FILE_NAME);
+ const configExists = fs.existsSync(configPath);
+ if (configExists) {
+ const configStr = await fs.readFile(configPath, 'utf-8');
+ config = JSON.parse(configStr);
+ }
+ return config as TransformConfig;
+ } catch (err) {
+ return config;
+ }
+}
+
+export async function writeConfig(projectDir: string, config: TransformConfig): Promise {
+ const configFilePath = path.join(projectDir, TRANSFORM_CONFIG_FILE_NAME);
+ await fs.writeFile(configFilePath, JSON.stringify(config, null, 4));
+ return config;
+}
+
+export function throwIfNotJSONExt(stackFile: string): void {
+ const extension = path.extname(stackFile);
+ if (extension === '.yaml' || extension === '.yml') {
+ throw new Error(`Yaml is not yet supported. Please convert the CloudFormation stack ${stackFile} to json.`);
+ }
+ if (extension !== '.json') {
+ throw new Error(`Invalid extension ${extension} for stack ${stackFile}`);
+ }
+}
+
+/**
+ * Given a project directory read the schema from disk. The schema may be a
+ * single schema.graphql or a set of .graphql files in a directory named `schema`.
+ * Preference is given to the `schema.graphql` if provided.
+ * @param projectDirectory The project directory.
+ */
+export async function readSchema(projectDirectory: string): Promise {
+ const schemaFilePath = path.join(projectDirectory, 'schema.graphql');
+ const schemaDirectoryPath = path.join(projectDirectory, 'schema');
+ const schemaFileExists = fs.existsSync(schemaFilePath);
+ const schemaDirectoryExists = fs.existsSync(schemaDirectoryPath);
+ let schema;
+ if (schemaFileExists) {
+ schema = (await fs.readFile(schemaFilePath)).toString();
+ } else if (schemaDirectoryExists) {
+ schema = (await readSchemaDocuments(schemaDirectoryPath)).join('\n');
+ } else {
+ throw new Error(`Could not find a schema at ${schemaFilePath}`);
+ }
+ return schema;
+}
+
+async function readSchemaDocuments(schemaDirectoryPath: string): Promise {
+ const files = await fs.readdir(schemaDirectoryPath);
+ let schemaDocuments = [];
+ for (const fileName of files) {
+ if (fileName.indexOf('.') === 0) {
+ continue;
+ }
+
+ const fullPath = `${schemaDirectoryPath}/${fileName}`;
+ const stats = await fs.lstat(fullPath);
+ if (stats.isDirectory()) {
+ const childDocs = await readSchemaDocuments(fullPath);
+ schemaDocuments = schemaDocuments.concat(childDocs);
+ } else if (stats.isFile()) {
+ const schemaDoc = await fs.readFile(fullPath);
+ schemaDocuments.push(schemaDoc);
+ }
+ }
+ return schemaDocuments;
+}
diff --git a/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v1.ts b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v1.ts
new file mode 100644
index 0000000000..5e066c34d9
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v1.ts
@@ -0,0 +1,465 @@
+import fs from 'fs-extra';
+import path from 'path';
+import chalk from 'chalk';
+import inquirer from 'inquirer';
+import { AmplifyCLIFeatureFlagAdapter } from './amplify-cli-feature-flag-adapter';
+import {
+ $TSContext,
+ AmplifyCategories,
+ ApiCategoryFacade,
+ CloudformationProviderFacade,
+ getGraphQLTransformerAuthDocLink,
+ getGraphQLTransformerAuthSubscriptionsDocLink,
+ getGraphQLTransformerOpenSearchProductionDocLink,
+ JSONUtilities,
+ pathManager,
+ stateManager,
+} from 'amplify-cli-core';
+import { ResourceConstants } from 'graphql-transformer-common';
+import { isAuthModeUpdated } from './auth-mode-compare';
+import {
+ collectDirectivesByTypeNames,
+ readTransformerConfiguration,
+ writeTransformerConfiguration,
+ TRANSFORM_CONFIG_FILE_NAME,
+ TRANSFORM_BASE_VERSION,
+ CLOUDFORMATION_FILE_NAME,
+ revertAPIMigration,
+ migrateAPIProject,
+ readProjectConfiguration,
+ buildAPIProject,
+ getSanityCheckRules,
+} from 'graphql-transformer-core';
+import { exitOnNextTick } from 'amplify-cli-core';
+import { searchablePushChecks } from './api-utils';
+import { getTransformerFactory } from './transformer-factory';
+
+const apiCategory = 'api';
+const parametersFileName = 'parameters.json';
+const schemaFileName = 'schema.graphql';
+const schemaDirName = 'schema';
+const ROOT_APPSYNC_S3_KEY = 'amplify-appsync-files';
+const DESTRUCTIVE_UPDATES_FLAG = 'allow-destructive-graphql-schema-updates';
+const PROVIDER_NAME = 'awscloudformation';
+
+async function warnOnAuth(context, map) {
+ const unAuthModelTypes = Object.keys(map).filter(type => !map[type].includes('auth') && map[type].includes('model'));
+ if (unAuthModelTypes.length) {
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ const docLink = getGraphQLTransformerAuthDocLink(transformerVersion);
+ context.print.warning("\nThe following types do not have '@auth' enabled. Consider using @auth with @model");
+ context.print.warning(unAuthModelTypes.map(type => `\t - ${type}`).join('\n'));
+ context.print.info(`Learn more about @auth here: ${docLink}\n`);
+ }
+}
+
+/**
+ * @TODO Include a map of versions to keep track
+ */
+async function transformerVersionCheck(context, resourceDir, cloudBackendDirectory, updatedResources, usedDirectives) {
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ const authDocLink = getGraphQLTransformerAuthSubscriptionsDocLink(transformerVersion);
+ const searchable = getGraphQLTransformerOpenSearchProductionDocLink(transformerVersion);
+ const versionChangeMessage = `The default behavior for @auth has changed in the latest version of Amplify\nRead here for details: ${authDocLink}`;
+ const warningESMessage = `The behavior for @searchable has changed after version 4.14.1.\nRead here for details: ${searchable}`;
+ const checkVersionExist = config => config && config.Version;
+ const checkESWarningExists = config => config && config.ElasticsearchWarning;
+ let writeToConfig = false;
+
+ // this is where we check if there is a prev version of the transformer being used
+ // by using the transformer.conf.json file
+ const cloudTransformerConfig = await readTransformerConfiguration(cloudBackendDirectory);
+ const cloudVersionExist = checkVersionExist(cloudTransformerConfig);
+ const cloudWarningExist = checkESWarningExists(cloudTransformerConfig);
+
+ // check local resource if the question has been answered before
+ const localTransformerConfig = await readTransformerConfiguration(resourceDir);
+ const localVersionExist = checkVersionExist(localTransformerConfig);
+ const localWarningExist = checkESWarningExists(localTransformerConfig);
+
+ // if we already asked the confirmation question before at a previous push
+ // or during current operations we should not ask again.
+ const showPrompt = !(cloudVersionExist || localVersionExist);
+ const showWarning = !(cloudWarningExist || localWarningExist);
+
+ const resources = updatedResources.filter(resource => resource.service === 'AppSync');
+ if (resources.length > 0) {
+ if (showPrompt && usedDirectives.includes('auth')) {
+ await warningMessage(context, versionChangeMessage);
+ }
+ if (showWarning && usedDirectives.includes('searchable')) {
+ await warningMessage(context, warningESMessage);
+ }
+ }
+
+ // searchable warning flag
+
+ // Only touch the file if it misses the Version property
+ // Always set to the base version, to not to break existing projects when coming
+ // from an older version of the CLI.
+ if (!localTransformerConfig.Version) {
+ localTransformerConfig.Version = TRANSFORM_BASE_VERSION;
+ writeToConfig = true;
+ }
+ // Add the warning as noted in the elasticsearch
+ if (!localTransformerConfig.warningESMessage) {
+ localTransformerConfig.ElasticsearchWarning = true;
+ writeToConfig = true;
+ }
+ if (writeToConfig) {
+ await writeTransformerConfiguration(resourceDir, localTransformerConfig);
+ }
+}
+
+async function warningMessage(context, warningMessage) {
+ if (context.exeInfo && context.exeInfo.inputParams && context.exeInfo.inputParams.yes) {
+ context.print.warning(`\n${warningMessage}\n`);
+ } else {
+ context.print.warning(`\n${warningMessage}\n`);
+ const response = await inquirer.prompt({
+ name: 'transformerConfig',
+ type: 'confirm',
+ message: `Do you wish to continue?`,
+ default: false,
+ });
+ if (!response.transformerConfig) {
+ await context.usageData.emitSuccess();
+ exitOnNextTick(0);
+ }
+ }
+}
+
+function apiProjectIsFromOldVersion(pathToProject, resourcesToBeCreated) {
+ const resources = resourcesToBeCreated.filter(resource => resource.service === 'AppSync');
+ if (!pathToProject || resources.length > 0) {
+ return false;
+ }
+ return fs.existsSync(`${pathToProject}/${CLOUDFORMATION_FILE_NAME}`) && !fs.existsSync(`${pathToProject}/${TRANSFORM_CONFIG_FILE_NAME}`);
+}
+
+/**
+ * API migration happens in a few steps. First we calculate which resources need
+ * to remain in the root stack (DDB tables, ES Domains, etc) and write them to
+ * transform.conf.json. We then call CF's update stack on the root stack such
+ * that only the resources that need to be in the root stack remain there
+ * (this deletes resolvers from the schema). We then compile the project with
+ * the new implementation and call update stack again.
+ * @param {*} context
+ * @param {*} resourceDir
+ */
+async function migrateProject(context, options) {
+ const { resourceDir, isCLIMigration, cloudBackendDirectory } = options;
+ const updateAndWaitForStack = options.handleMigration || (() => Promise.resolve('Skipping update'));
+ let oldProjectConfig;
+ let oldCloudBackend;
+ try {
+ context.print.info('\nMigrating your API. This may take a few minutes.');
+ const { project, cloudBackend } = await migrateAPIProject({
+ projectDirectory: resourceDir,
+ cloudBackendDirectory,
+ });
+ oldProjectConfig = project;
+ oldCloudBackend = cloudBackend;
+ await updateAndWaitForStack({ isCLIMigration });
+ } catch (e) {
+ await revertAPIMigration(resourceDir, oldProjectConfig);
+ throw e;
+ }
+ try {
+ // After the intermediate update, we need the transform function
+ // to look at this directory since we did not overwrite the currentCloudBackend with the build
+ options.cloudBackendDirectory = resourceDir;
+ await transformGraphQLSchemaV1(context, options);
+ const result = await updateAndWaitForStack({ isCLIMigration });
+ context.print.info('\nFinished migrating API.');
+ return result;
+ } catch (e) {
+ context.print.error('Reverting API migration.');
+ await revertAPIMigration(resourceDir, oldCloudBackend);
+ try {
+ await updateAndWaitForStack({ isReverting: true, isCLIMigration });
+ } catch (e) {
+ context.print.error('Error reverting intermediate migration stack.');
+ }
+ await revertAPIMigration(resourceDir, oldProjectConfig);
+ context.print.error('API successfully reverted.');
+ throw e;
+ }
+}
+
+export async function transformGraphQLSchemaV1(context, options) {
+ const backEndDir = context.amplify.pathManager.getBackendDirPath();
+ const flags = context.parameters.options;
+ if (flags['no-gql-override']) {
+ return;
+ }
+
+ let { resourceDir, parameters } = options;
+ const { forceCompile } = options;
+
+ // Compilation during the push step
+ const { resourcesToBeCreated, resourcesToBeUpdated, allResources } = await context.amplify.getResourceStatus(apiCategory);
+ let resources = resourcesToBeCreated.concat(resourcesToBeUpdated);
+
+ // When build folder is missing include the API
+ // to be compiled without the backend/api//build
+ // cloud formation push will fail even if there is no changes in the GraphQL API
+ // https://github.com/aws-amplify/amplify-console/issues/10
+ const resourceNeedCompile = allResources
+ .filter(r => !resources.includes(r))
+ .filter(r => {
+ const buildDir = path.normalize(path.join(backEndDir, apiCategory, r.resourceName, 'build'));
+ return !fs.existsSync(buildDir);
+ });
+ resources = resources.concat(resourceNeedCompile);
+
+ if (forceCompile) {
+ resources = resources.concat(allResources);
+ }
+ resources = resources.filter(resource => resource.service === 'AppSync');
+ // check if api is in update status or create status
+ const isNewAppSyncAPI: boolean = resourcesToBeCreated.filter(resource => resource.service === 'AppSync').length === 0 ? false : true;
+
+ if (!resourceDir) {
+ // There can only be one appsync resource
+ if (resources.length > 0) {
+ const resource = resources[0];
+ if (resource.providerPlugin !== PROVIDER_NAME) {
+ return;
+ }
+ const { category, resourceName } = resource;
+ resourceDir = path.normalize(path.join(backEndDir, category, resourceName));
+ } else {
+ // No appsync resource to update/add
+ return;
+ }
+ }
+
+ let previouslyDeployedBackendDir = options.cloudBackendDirectory;
+ if (!previouslyDeployedBackendDir) {
+ if (resources.length > 0) {
+ const resource = resources[0];
+ if (resource.providerPlugin !== PROVIDER_NAME) {
+ return;
+ }
+ const { category, resourceName } = resource;
+ const cloudBackendRootDir = context.amplify.pathManager.getCurrentCloudBackendDirPath();
+ /* eslint-disable */
+ previouslyDeployedBackendDir = path.normalize(path.join(cloudBackendRootDir, category, resourceName));
+ /* eslint-enable */
+ }
+ }
+
+ const parametersFilePath = path.join(resourceDir, parametersFileName);
+
+ if (!parameters && fs.existsSync(parametersFilePath)) {
+ try {
+ parameters = JSONUtilities.readJson(parametersFilePath);
+ } catch (e) {
+ parameters = {};
+ }
+ }
+
+ const isCLIMigration = options.migrate;
+ const isOldApiVersion = apiProjectIsFromOldVersion(previouslyDeployedBackendDir, resourcesToBeCreated);
+ const migrateOptions = {
+ ...options,
+ resourceDir,
+ migrate: false,
+ isCLIMigration,
+ cloudBackendDirectory: previouslyDeployedBackendDir,
+ };
+ if (isCLIMigration && isOldApiVersion) {
+ return await migrateProject(context, migrateOptions);
+ } else if (isOldApiVersion) {
+ let IsOldApiProject;
+
+ if (context.exeInfo && context.exeInfo.inputParams && context.exeInfo.inputParams.yes) {
+ IsOldApiProject = context.exeInfo.inputParams.yes;
+ } else {
+ const migrateMessage =
+ `${chalk.bold('The CLI is going to take the following actions during the migration step:')}\n` +
+ '\n1. If you have a GraphQL API, we will update the corresponding Cloudformation stack to support larger annotated schemas and custom resolvers.\n' +
+ 'In this process, we will be making Cloudformation API calls to update your GraphQL API Cloudformation stack. This operation will result in deletion of your AppSync resolvers and then the creation of new ones and for a brief while your AppSync API will be unavailable until the migration finishes\n' +
+ '\n2. We will be updating your local Cloudformation files present inside the ‘amplify/‘ directory of your app project, for the GraphQL API service\n' +
+ '\n3. If for any reason the migration fails, the CLI will rollback your cloud and local changes and you can take a look at https://aws-amplify.github.io/docs/cli/migrate?sdk=js for manually migrating your project so that it’s compatible with the latest version of the CLI\n' +
+ '\n4. ALL THE ABOVE MENTIONED OPERATIONS WILL NOT DELETE ANY DATA FROM ANY OF YOUR DATA STORES\n' +
+ `\n${chalk.bold('Before the migration, please be aware of the following things:')}\n` +
+ '\n1. Make sure to have an internet connection through the migration process\n' +
+ '\n2. Make sure to not exit/terminate the migration process (by interrupting it explicitly in the middle of migration), as this will lead to inconsistency within your project\n' +
+ '\n3. Make sure to take a backup of your entire project (including the amplify related config files)\n' +
+ '\nDo you want to continue?\n';
+ ({ IsOldApiProject } = await inquirer.prompt({
+ name: 'IsOldApiProject',
+ type: 'confirm',
+ message: migrateMessage,
+ default: true,
+ }));
+ }
+ if (!IsOldApiProject) {
+ throw new Error('Migration cancelled. Please downgrade to a older version of the Amplify CLI or migrate your API project.');
+ }
+ return await migrateProject(context, migrateOptions);
+ }
+
+ let { authConfig } = options;
+
+ //
+ // If we don't have an authConfig from the caller, use it from the
+ // already read resources[0], which is an AppSync API.
+ //
+
+ if (!authConfig) {
+ if (resources[0].output.securityType) {
+ // Convert to multi-auth format if needed.
+ authConfig = {
+ defaultAuthentication: {
+ authenticationType: resources[0].output.securityType,
+ },
+ additionalAuthenticationProviders: [],
+ };
+ } else {
+ ({ authConfig } = resources[0].output);
+ }
+ }
+
+ // for the predictions directive get storage config
+ const s3ResourceName = await invokeS3GetResourceName(context);
+ const storageConfig = {
+ bucketName: s3ResourceName ? await getBucketName(context, s3ResourceName) : undefined,
+ };
+
+ const buildDir = path.normalize(path.join(resourceDir, 'build'));
+ const schemaFilePath = path.normalize(path.join(resourceDir, schemaFileName));
+ const schemaDirPath = path.normalize(path.join(resourceDir, schemaDirName));
+ let deploymentRootKey = await getPreviousDeploymentRootKey(previouslyDeployedBackendDir);
+ if (!deploymentRootKey) {
+ const deploymentSubKey = await CloudformationProviderFacade.hashDirectory(context, resourceDir);
+ deploymentRootKey = `${ROOT_APPSYNC_S3_KEY}/${deploymentSubKey}`;
+ }
+ const projectBucket = options.dryRun ? 'fake-bucket' : getProjectBucket(context);
+ const buildParameters = {
+ ...parameters,
+ S3DeploymentBucket: projectBucket,
+ S3DeploymentRootKey: deploymentRootKey,
+ };
+
+ // If it is a dry run, don't create the build folder as it could make a follow-up command
+ // to not to trigger a build, hence a corrupt deployment.
+ if (!options.dryRun) {
+ fs.ensureDirSync(buildDir);
+ }
+
+ // Transformer compiler code
+ // const schemaText = await readProjectSchema(resourceDir);
+ const project = await readProjectConfiguration(resourceDir);
+
+ // Check for common errors
+ const directiveMap = collectDirectivesByTypeNames(project.schema);
+ await warnOnAuth(context, directiveMap.types);
+ await searchablePushChecks(context, directiveMap.types, parameters[ResourceConstants.PARAMETERS.AppSyncApiName]);
+
+ await transformerVersionCheck(context, resourceDir, previouslyDeployedBackendDir, resourcesToBeUpdated, directiveMap.directives);
+
+ const transformerListFactory = await getTransformerFactory(context, resourceDir, authConfig);
+
+ let searchableTransformerFlag = false;
+
+ if (directiveMap.directives.includes('searchable')) {
+ searchableTransformerFlag = true;
+ }
+
+ const ff = new AmplifyCLIFeatureFlagAdapter();
+ const allowDestructiveUpdates = context?.input?.options?.[DESTRUCTIVE_UPDATES_FLAG] || context?.input?.options?.force;
+ const sanityCheckRulesList = getSanityCheckRules(isNewAppSyncAPI, ff, allowDestructiveUpdates);
+
+ const buildConfig = {
+ ...options,
+ buildParameters,
+ projectDirectory: resourceDir,
+ transformersFactory: transformerListFactory,
+ transformersFactoryArgs: [searchableTransformerFlag, storageConfig],
+ rootStackFileName: 'cloudformation-template.json',
+ currentCloudBackendDirectory: previouslyDeployedBackendDir,
+ minify: options.minify,
+ featureFlags: ff,
+ sanityCheckRules: sanityCheckRulesList,
+ };
+ const transformerOutput = await buildAPIProject(buildConfig);
+
+ context.print.success(`GraphQL schema compiled successfully.\n\nEdit your schema at ${schemaFilePath} or \
+place .graphql files in a directory at ${schemaDirPath}`);
+
+ if (isAuthModeUpdated(options)) {
+ parameters.AuthModeLastUpdated = new Date();
+ }
+ if (!options.dryRun) {
+ JSONUtilities.writeJson(parametersFilePath, parameters);
+ }
+
+ return transformerOutput;
+}
+
+function getProjectBucket(context) {
+ const projectDetails = context.amplify.getProjectDetails();
+ const projectBucket = projectDetails.amplifyMeta.providers ? projectDetails.amplifyMeta.providers[PROVIDER_NAME].DeploymentBucketName : '';
+ return projectBucket;
+}
+
+async function getPreviousDeploymentRootKey(previouslyDeployedBackendDir) {
+ // this is the function
+ let parameters;
+ try {
+ const parametersPath = path.join(previouslyDeployedBackendDir, 'build', parametersFileName);
+ const parametersExists = fs.existsSync(parametersPath);
+ if (parametersExists) {
+ const parametersString = await fs.readFile(parametersPath);
+ parameters = JSON.parse(parametersString.toString());
+ }
+ return parameters.S3DeploymentRootKey;
+ } catch (err) {
+ return undefined;
+ }
+}
+
+// TODO: Remove until further discussion
+// function getTransformerOptions(project, transformerName) {
+// if (
+// project &&
+// project.config &&
+// project.config.TransformerOptions &&
+// project.config.TransformerOptions[transformerName]
+// ) {
+// return project.config.TransformerOptions[transformerName];
+// }
+// return undefined;
+// }
+
+/**
+ * S3API
+ * TBD: Remove this once all invoke functions are moved to a library shared across amplify
+ * */
+async function invokeS3GetResourceName(context) {
+ const s3ResourceName = await context.amplify.invokePluginMethod(context, 'storage', undefined, 's3GetResourceName', [context]);
+ return s3ResourceName;
+}
+
+async function getBucketName(context: $TSContext, s3ResourceName: string) {
+ const { amplify } = context;
+ const { amplifyMeta } = amplify.getProjectDetails();
+ const stackName = amplifyMeta.providers.awscloudformation.StackName;
+ const s3ResourcePath = pathManager.getResourceDirectoryPath(undefined, AmplifyCategories.STORAGE, s3ResourceName);
+ const cliInputsPath = path.join(s3ResourcePath, 'cli-inputs.json');
+ let bucketParameters;
+ // get bucketParameters 1st from cli-inputs , if not present, then parameters.json
+ if (fs.existsSync(cliInputsPath)) {
+ bucketParameters = JSONUtilities.readJson(cliInputsPath);
+ } else {
+ bucketParameters = stateManager.getResourceParametersJson(undefined, AmplifyCategories.STORAGE, s3ResourceName);
+ }
+
+ const bucketName = stackName.startsWith('amplify-')
+ ? `${bucketParameters.bucketName}\${hash}-\${env}`
+ : `${bucketParameters.bucketName}${s3ResourceName}-\${env}`;
+ return bucketName;
+}
diff --git a/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v2.ts b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v2.ts
new file mode 100644
index 0000000000..4064ee9791
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema-v2.ts
@@ -0,0 +1,473 @@
+import {
+ collectDirectivesByTypeNames,
+ DeploymentResources,
+ GraphQLTransform,
+ ResolverConfig,
+ TransformerProjectConfig,
+} from '@aws-amplify/graphql-transformer-core';
+import { Template } from '@aws-amplify/graphql-transformer-core/lib/config/project-config';
+import { OverrideConfig } from '@aws-amplify/graphql-transformer-core/lib/transformation/types';
+import { AppSyncAuthConfiguration, TransformerPluginProvider } from '@aws-amplify/graphql-transformer-interfaces';
+import {
+ $TSAny,
+ $TSContext,
+ $TSMeta,
+ $TSObject,
+ AmplifyCategories,
+ AmplifySupportedService,
+ ApiCategoryFacade,
+ CloudformationProviderFacade,
+ getGraphQLTransformerAuthDocLink,
+ JSONUtilities,
+ pathManager,
+ stateManager,
+} from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import fs from 'fs-extra';
+import { ResourceConstants } from 'graphql-transformer-common';
+import {
+ DiffRule,
+ getSanityCheckRules,
+ loadProject,
+ ProjectRule,
+ sanityCheckProject,
+} from 'graphql-transformer-core';
+import _ from 'lodash';
+import path from 'path';
+/* eslint-disable-next-line import/no-cycle */
+import { searchablePushChecks } from './api-utils';
+import { AmplifyCLIFeatureFlagAdapter } from './amplify-cli-feature-flag-adapter';
+import { isAuthModeUpdated } from './auth-mode-compare';
+import { schemaHasSandboxModeEnabled, showGlobalSandboxModeWarning, showSandboxModePrompts } from './sandbox-mode-helpers';
+import { parseUserDefinedSlots } from './user-defined-slots';
+import {
+ getAdminRoles, getIdentityPoolId, mergeUserConfigWithTransformOutput, writeDeploymentToDisk,
+} from './utils';
+import { getTransformerFactory } from './transformer-factory';
+
+const PARAMETERS_FILENAME = 'parameters.json';
+const SCHEMA_FILENAME = 'schema.graphql';
+const SCHEMA_DIR_NAME = 'schema';
+const ROOT_APPSYNC_S3_KEY = 'amplify-appsync-files';
+const DESTRUCTIVE_UPDATES_FLAG = 'allow-destructive-graphql-schema-updates';
+const PROVIDER_NAME = 'awscloudformation';
+
+type SanityCheckRules = {
+ diffRules: DiffRule[];
+ projectRules: ProjectRule[];
+};
+
+const warnOnAuth = (map: $TSObject, docLink: string): void => {
+ const unAuthModelTypes = Object.keys(map).filter(type => !map[type].includes('auth') && map[type].includes('model'));
+ if (unAuthModelTypes.length) {
+ printer.info(
+ `
+⚠️ WARNING: Some types do not have authorization rules configured. That means all create, read, update, and delete operations are denied on these types:`,
+ 'yellow',
+ );
+ printer.info(unAuthModelTypes.map(type => `\t - ${type}`).join('\n'), 'yellow');
+ printer.info(`Learn more about "@auth" authorization rules here: ${docLink}`, 'yellow');
+ }
+};
+
+/**
+ * Transform GraphQL Schema
+ */
+export const transformGraphQLSchemaV2 = async (context: $TSContext, options): Promise => {
+ let resourceName: string;
+ const backEndDir = pathManager.getBackendDirPath();
+ const flags = context.parameters.options;
+ if (flags['no-gql-override']) {
+ return undefined;
+ }
+
+ let { resourceDir, parameters } = options;
+ const { forceCompile } = options;
+
+ // Compilation during the push step
+ const { resourcesToBeCreated, resourcesToBeUpdated, allResources } = await context.amplify.getResourceStatus(AmplifyCategories.API);
+ let resources = resourcesToBeCreated.concat(resourcesToBeUpdated);
+
+ // When build folder is missing include the API
+ // to be compiled without the backend/api//build
+ // cloud formation push will fail even if there is no changes in the GraphQL API
+ // https://github.com/aws-amplify/amplify-console/issues/10
+ const resourceNeedCompile = allResources
+ .filter(r => !resources.includes(r))
+ .filter(r => {
+ const buildDir = path.normalize(path.join(backEndDir, AmplifyCategories.API, r.resourceName, 'build'));
+ return !fs.existsSync(buildDir);
+ });
+ resources = resources.concat(resourceNeedCompile);
+
+ if (forceCompile) {
+ resources = resources.concat(allResources);
+ }
+ resources = resources.filter(resource => resource.service === 'AppSync');
+
+ if (!resourceDir) {
+ // There can only be one appsync resource
+ if (resources.length > 0) {
+ const resource = resources[0];
+ if (resource.providerPlugin !== PROVIDER_NAME) {
+ return undefined;
+ }
+ const { category } = resource;
+ ({ resourceName } = resource);
+ resourceDir = path.normalize(path.join(backEndDir, category, resourceName));
+ } else {
+ // No appsync resource to update/add
+ return undefined;
+ }
+ }
+
+ let previouslyDeployedBackendDir = options.cloudBackendDirectory;
+ if (!previouslyDeployedBackendDir) {
+ if (resources.length > 0) {
+ const resource = resources[0];
+ if (resource.providerPlugin !== PROVIDER_NAME) {
+ return undefined;
+ }
+ const { category } = resource;
+ resourceName = resource.resourceName;
+ const cloudBackendRootDir = pathManager.getCurrentCloudBackendDirPath();
+ /* eslint-disable */
+ previouslyDeployedBackendDir = path.normalize(path.join(cloudBackendRootDir, category, resourceName));
+ /* eslint-enable */
+ }
+ }
+
+ const parametersFilePath = path.join(resourceDir, PARAMETERS_FILENAME);
+
+ if (!parameters && fs.existsSync(parametersFilePath)) {
+ try {
+ parameters = JSONUtilities.readJson(parametersFilePath);
+
+ // OpenSearch Instance type support for x.y.search types
+ if (parameters[ResourceConstants.PARAMETERS.OpenSearchInstanceType]) {
+ parameters[ResourceConstants.PARAMETERS.OpenSearchInstanceType] = parameters[ResourceConstants.PARAMETERS.OpenSearchInstanceType]
+ .replace('.search', '.elasticsearch');
+ }
+ } catch (e) {
+ parameters = {};
+ }
+ }
+
+ let { authConfig }: { authConfig: AppSyncAuthConfiguration } = options;
+
+ if (_.isEmpty(authConfig) && !_.isEmpty(resources)) {
+ authConfig = await context.amplify.invokePluginMethod(
+ context,
+ AmplifyCategories.API,
+ AmplifySupportedService.APPSYNC,
+ 'getAuthConfig',
+ [context, resources[0].resourceName],
+ );
+ // handle case where auth project is not migrated , if Auth not migrated above function will return empty Object
+ if (_.isEmpty(authConfig)) {
+ //
+ // If we don't have an authConfig from the caller, use it from the
+ // already read resources[0], which is an AppSync API.
+ //
+ if (resources[0].output.securityType) {
+ // Convert to multi-auth format if needed.
+ authConfig = {
+ defaultAuthentication: {
+ authenticationType: resources[0].output.securityType,
+ },
+ additionalAuthenticationProviders: [],
+ };
+ } else {
+ ({ authConfig } = resources[0].output);
+ }
+ }
+ }
+
+ // for auth transformer we get any admin roles and a cognito identity pool to check for
+ // potential authenticated roles outside of the provided authRole
+ const adminRoles = await getAdminRoles(context, resourceName);
+ const identityPoolId = await getIdentityPoolId(context);
+
+ // for the predictions directive get storage config
+ const s3Resource = s3ResourceAlreadyExists();
+ const storageConfig = s3Resource ? getBucketName(s3Resource) : undefined;
+
+ const buildDir = path.normalize(path.join(resourceDir, 'build'));
+ const schemaFilePath = path.normalize(path.join(resourceDir, SCHEMA_FILENAME));
+ const schemaDirPath = path.normalize(path.join(resourceDir, SCHEMA_DIR_NAME));
+ let deploymentRootKey = await getPreviousDeploymentRootKey(previouslyDeployedBackendDir);
+ if (!deploymentRootKey) {
+ const deploymentSubKey = await CloudformationProviderFacade.hashDirectory(context, resourceDir);
+ deploymentRootKey = `${ROOT_APPSYNC_S3_KEY}/${deploymentSubKey}`;
+ }
+ const projectBucket = options.dryRun ? 'fake-bucket' : getProjectBucket();
+ const buildParameters = {
+ ...parameters,
+ S3DeploymentBucket: projectBucket,
+ S3DeploymentRootKey: deploymentRootKey,
+ };
+
+ // If it is a dry run, don't create the build folder as it could make a follow-up command
+ // to not to trigger a build, hence a corrupt deployment.
+ if (!options.dryRun) {
+ fs.ensureDirSync(buildDir);
+ }
+
+ const project = await loadProject(resourceDir);
+
+ const lastDeployedProjectConfig = fs.existsSync(previouslyDeployedBackendDir)
+ ? await loadProject(previouslyDeployedBackendDir)
+ : undefined;
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ const docLink = getGraphQLTransformerAuthDocLink(transformerVersion);
+ const sandboxModeEnabled = schemaHasSandboxModeEnabled(project.schema, docLink);
+ const directiveMap = collectDirectivesByTypeNames(project.schema);
+ const hasApiKey = authConfig.defaultAuthentication.authenticationType === 'API_KEY'
+ || authConfig.additionalAuthenticationProviders.some(a => a.authenticationType === 'API_KEY');
+ const showSandboxModeMessage = sandboxModeEnabled && hasApiKey;
+
+ if (showSandboxModeMessage) {
+ showGlobalSandboxModeWarning(docLink);
+ } else {
+ warnOnAuth(directiveMap.types, docLink);
+ }
+
+ searchablePushChecks(context, directiveMap.types, parameters[ResourceConstants.PARAMETERS.AppSyncApiName]);
+
+ const transformerListFactory = await getTransformerFactory(context, resourceDir);
+
+ if (sandboxModeEnabled && options.promptApiKeyCreation) {
+ const apiKeyConfig = await showSandboxModePrompts(context);
+ if (apiKeyConfig) authConfig.additionalAuthenticationProviders.push(apiKeyConfig);
+ }
+
+ let searchableTransformerFlag = false;
+
+ if (directiveMap.directives.includes('searchable')) {
+ searchableTransformerFlag = true;
+ }
+
+ // construct sanityCheckRules
+ const ff = new AmplifyCLIFeatureFlagAdapter();
+ const isNewAppSyncAPI: boolean = resourcesToBeCreated.some(resource => resource.service === 'AppSync');
+ const allowDestructiveUpdates = context?.input?.options?.[DESTRUCTIVE_UPDATES_FLAG] || context?.input?.options?.force;
+ const sanityCheckRules = getSanityCheckRules(isNewAppSyncAPI, ff, allowDestructiveUpdates);
+ let resolverConfig = {};
+ if (!_.isEmpty(resources)) {
+ resolverConfig = await context.amplify.invokePluginMethod(
+ context,
+ AmplifyCategories.API,
+ AmplifySupportedService.APPSYNC,
+ 'getResolverConfig',
+ [context, resources[0].resourceName],
+ );
+ }
+
+ /**
+ * if Auth is not migrated , we need to fetch resolver Config from transformer.conf.json
+ * since above function will return empty object
+ */
+ if (_.isEmpty(resolverConfig)) {
+ resolverConfig = project.config.ResolverConfig;
+ }
+
+ const buildConfig: ProjectOptions = {
+ ...options,
+ buildParameters,
+ projectDirectory: resourceDir,
+ transformersFactory: transformerListFactory,
+ transformersFactoryArgs: {
+ addSearchableTransformer: searchableTransformerFlag,
+ storageConfig,
+ authConfig,
+ adminRoles,
+ identityPoolId,
+ },
+ rootStackFileName: 'cloudformation-template.json',
+ currentCloudBackendDirectory: previouslyDeployedBackendDir,
+ minify: options.minify,
+ projectConfig: project,
+ lastDeployedProjectConfig,
+ authConfig,
+ sandboxModeEnabled,
+ sanityCheckRules,
+ resolverConfig,
+ };
+
+ const transformerOutput = await buildAPIProject(context, buildConfig);
+
+ printer.success(`GraphQL schema compiled successfully.\n\nEdit your schema at ${schemaFilePath} or \
+place .graphql files in a directory at ${schemaDirPath}`);
+
+ if (isAuthModeUpdated(options)) {
+ parameters.AuthModeLastUpdated = new Date();
+ }
+ if (!options.dryRun) {
+ JSONUtilities.writeJson(parametersFilePath, parameters);
+ }
+
+ return transformerOutput;
+};
+
+const getProjectBucket = (): string => {
+ const meta: $TSMeta = stateManager.getMeta(undefined, { throwIfNotExist: false });
+ const projectBucket = meta?.providers ? meta.providers[PROVIDER_NAME].DeploymentBucketName : '';
+ return projectBucket;
+};
+
+const getPreviousDeploymentRootKey = async (previouslyDeployedBackendDir: string): Promise => {
+ // this is the function
+ let parameters;
+ try {
+ const parametersPath = path.join(previouslyDeployedBackendDir, `build/${PARAMETERS_FILENAME}`);
+ const parametersExists = fs.existsSync(parametersPath);
+ if (parametersExists) {
+ const parametersString = await fs.readFile(parametersPath);
+ parameters = JSON.parse(parametersString.toString());
+ }
+ return parameters.S3DeploymentRootKey;
+ } catch (err) {
+ return undefined;
+ }
+};
+
+/**
+ * Check if storage exists in the project if not return undefined
+ */
+const s3ResourceAlreadyExists = (): string | undefined => {
+ try {
+ let resourceName: string;
+ const amplifyMeta: $TSMeta = stateManager.getMeta(undefined, { throwIfNotExist: false });
+ if (amplifyMeta?.[AmplifyCategories.STORAGE]) {
+ const categoryResources = amplifyMeta[AmplifyCategories.STORAGE];
+ Object.keys(categoryResources).forEach(resource => {
+ if (categoryResources[resource].service === AmplifySupportedService.S3) {
+ resourceName = resource;
+ }
+ });
+ }
+ return resourceName;
+ } catch (error) {
+ if (error.name === 'UndeterminedEnvironmentError') {
+ return undefined;
+ }
+ throw error;
+ }
+};
+
+const getBucketName = (s3ResourceName: string): { bucketName: string } => {
+ const amplifyMeta = stateManager.getMeta();
+ const stackName = amplifyMeta.providers.awscloudformation.StackName;
+ const s3ResourcePath = pathManager.getResourceDirectoryPath(undefined, AmplifyCategories.STORAGE, s3ResourceName);
+ const cliInputsPath = path.join(s3ResourcePath, 'cli-inputs.json');
+ let bucketParameters: $TSObject;
+ // get bucketParameters 1st from cli-inputs , if not present, then parameters.json
+ if (fs.existsSync(cliInputsPath)) {
+ bucketParameters = JSONUtilities.readJson(cliInputsPath);
+ } else {
+ bucketParameters = stateManager.getResourceParametersJson(undefined, AmplifyCategories.STORAGE, s3ResourceName);
+ }
+ const bucketName = stackName.startsWith('amplify-')
+ ? `${bucketParameters.bucketName}\${hash}-\${env}`
+ : `${bucketParameters.bucketName}${s3ResourceName}-\${env}`;
+ return { bucketName };
+};
+
+type TransformerFactoryArgs = {
+ addSearchableTransformer: boolean;
+ authConfig: $TSAny;
+ storageConfig?: $TSAny;
+ adminRoles?: Array;
+ identityPoolId?: string;
+};
+
+/**
+ * ProjectOptions Type Definition
+ */
+type ProjectOptions = {
+ buildParameters: {
+ S3DeploymentBucket: string;
+ S3DeploymentRootKey: string;
+ };
+ projectDirectory: string;
+ transformersFactory: (options: T) => Promise;
+ transformersFactoryArgs: T;
+ rootStackFileName: 'cloudformation-template.json';
+ currentCloudBackendDirectory?: string;
+ minify: boolean;
+ lastDeployedProjectConfig?: TransformerProjectConfig;
+ projectConfig: TransformerProjectConfig;
+ resolverConfig?: ResolverConfig;
+ dryRun?: boolean;
+ authConfig?: AppSyncAuthConfiguration;
+ stacks: Record;
+ sandboxModeEnabled?: boolean;
+ sanityCheckRules: SanityCheckRules;
+ overrideConfig: OverrideConfig;
+};
+
+/**
+ * buildAPIProject
+ */
+const buildAPIProject = async (
+ context: $TSContext,
+ opts: ProjectOptions,
+): Promise => {
+ const schema = opts.projectConfig.schema.toString();
+ // Skip building the project if the schema is blank
+ if (!schema) {
+ return undefined;
+ }
+
+ const builtProject = await _buildProject(opts);
+
+ const buildLocation = path.join(opts.projectDirectory, 'build');
+ const currentCloudLocation = opts.currentCloudBackendDirectory ? path.join(opts.currentCloudBackendDirectory, 'build') : undefined;
+
+ if (opts.projectDirectory && !opts.dryRun) {
+ await writeDeploymentToDisk(context, builtProject, buildLocation, opts.rootStackFileName, opts.buildParameters, opts.minify);
+ await sanityCheckProject(
+ currentCloudLocation,
+ buildLocation,
+ opts.rootStackFileName,
+ opts.sanityCheckRules.diffRules,
+ opts.sanityCheckRules.projectRules,
+ );
+ }
+
+ // TODO: update local env on api compile
+ // await _updateCurrentMeta(opts);
+
+ return builtProject;
+};
+
+const _buildProject = async (opts: ProjectOptions): Promise => {
+ const userProjectConfig = opts.projectConfig;
+ const stackMapping = userProjectConfig.config.StackMapping;
+ const userDefinedSlots = {
+ ...parseUserDefinedSlots(userProjectConfig.pipelineFunctions),
+ ...parseUserDefinedSlots(userProjectConfig.resolvers),
+ };
+
+ // Create the transformer instances, we've to make sure we're not reusing them within the same CLI command
+ // because the StackMapping feature already builds the project once.
+ const transformers = await opts.transformersFactory(opts.transformersFactoryArgs);
+ const transform = new GraphQLTransform({
+ transformers,
+ stackMapping,
+ transformConfig: userProjectConfig.config,
+ authConfig: opts.authConfig,
+ buildParameters: opts.buildParameters,
+ stacks: opts.projectConfig.stacks || {},
+ featureFlags: new AmplifyCLIFeatureFlagAdapter(),
+ sandboxModeEnabled: opts.sandboxModeEnabled,
+ userDefinedSlots,
+ resolverConfig: opts.resolverConfig,
+ overrideConfig: opts.overrideConfig,
+ });
+
+ const schema = userProjectConfig.schema.toString();
+ const transformOutput = transform.transform(schema);
+
+ return mergeUserConfigWithTransformOutput(userProjectConfig, transformOutput, opts);
+};
diff --git a/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema.ts b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema.ts
new file mode 100644
index 0000000000..3c23a4ebff
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/transform-graphql-schema.ts
@@ -0,0 +1,18 @@
+import { DeploymentResources as DeploymentResourcesV2 } from '@aws-amplify/graphql-transformer-core';
+import { DeploymentResources as DeploymentResourcesV1 } from 'graphql-transformer-core';
+import { $TSAny, $TSContext, ApiCategoryFacade } from 'amplify-cli-core';
+import { transformGraphQLSchemaV1 } from './transform-graphql-schema-v1';
+import { transformGraphQLSchemaV2 } from './transform-graphql-schema-v2';
+
+/**
+ * Determine which transformer version is in effect, and execute the appropriate transformation.
+ */
+export const transformGraphQLSchema = async (
+ context: $TSContext,
+ options: $TSAny,
+): Promise => {
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ return transformerVersion === 2
+ ? transformGraphQLSchemaV2(context, options)
+ : transformGraphQLSchemaV1(context, options);
+};
diff --git a/packages/amplify-category-api/src/graphql-transformer/transformer-factory.ts b/packages/amplify-category-api/src/graphql-transformer/transformer-factory.ts
new file mode 100644
index 0000000000..f817b2f688
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/transformer-factory.ts
@@ -0,0 +1,233 @@
+import { AuthTransformer as AuthTransformerV2 } from '@aws-amplify/graphql-auth-transformer';
+import { DefaultValueTransformer as DefaultValueTransformerV2 } from '@aws-amplify/graphql-default-value-transformer';
+import { FunctionTransformer as FunctionTransformerV2 } from '@aws-amplify/graphql-function-transformer';
+import { HttpTransformer as HttpTransformerV2 } from '@aws-amplify/graphql-http-transformer';
+import {
+ IndexTransformer as IndexTransformerV2,
+ PrimaryKeyTransformer as PrimaryKeyTransformerV2,
+} from '@aws-amplify/graphql-index-transformer';
+import { MapsToTransformer as MapsToTransformerV2 } from '@aws-amplify/graphql-maps-to-transformer';
+import { ModelTransformer as ModelTransformerV2 } from '@aws-amplify/graphql-model-transformer';
+import { PredictionsTransformer as PredictionsTransformerV2 } from '@aws-amplify/graphql-predictions-transformer';
+import {
+ BelongsToTransformer as BelongsToTransformerV2,
+ HasManyTransformer as HasManyTransformerV2,
+ HasOneTransformer as HasOneTransformerV2,
+ ManyToManyTransformer as ManyToManyTransformerV2,
+} from '@aws-amplify/graphql-relational-transformer';
+import { SearchableModelTransformer as SearchableModelTransformerV2 } from '@aws-amplify/graphql-searchable-transformer';
+import { TransformerPluginProvider as TransformerPluginProviderV2 } from '@aws-amplify/graphql-transformer-interfaces';
+import { DynamoDBModelTransformer as DynamoDBModelTransformerV1 } from 'graphql-dynamodb-transformer';
+import { ModelAuthTransformer as ModelAuthTransformerV1 } from 'graphql-auth-transformer';
+import { ModelConnectionTransformer as ModelConnectionTransformerV1 } from 'graphql-connection-transformer';
+import { SearchableModelTransformer as SearchableModelTransformerV1 } from 'graphql-elasticsearch-transformer';
+import { VersionedModelTransformer as VersionedModelTransformerV1 } from 'graphql-versioned-transformer';
+import { FunctionTransformer as FunctionTransformerV1 } from 'graphql-function-transformer';
+import { HttpTransformer as HttpTransformerV1 } from 'graphql-http-transformer';
+import { PredictionsTransformer as PredictionsTransformerV1 } from 'graphql-predictions-transformer';
+import { KeyTransformer as KeyTransformerV1 } from 'graphql-key-transformer';
+import {
+ $TSAny,
+ $TSContext,
+ pathManager,
+ stateManager,
+ ApiCategoryFacade,
+ CloudformationProviderFacade,
+} from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import {
+ loadProject,
+ readTransformerConfiguration,
+ TRANSFORM_CONFIG_FILE_NAME,
+ ITransformer,
+ TransformConfig,
+} from 'graphql-transformer-core';
+import importFrom from 'import-from';
+import importGlobal from 'import-global';
+import path from 'path';
+
+const PROVIDER_NAME = 'awscloudformation';
+
+type TransformerFactoryArgs = {
+ addSearchableTransformer: boolean;
+ authConfig: $TSAny;
+ storageConfig?: $TSAny;
+ adminRoles?: Array;
+ identityPoolId?: string;
+ };
+
+/**
+ * Return the graphql transformer factory based on the projects current transformer version.
+ */
+export const getTransformerFactory = async (
+ context: $TSContext,
+ resourceDir: string,
+ authConfig?: $TSAny,
+): Promise<(options: $TSAny) => Promise<(TransformerPluginProviderV2 | ITransformer)[]>> => {
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ return transformerVersion === 2
+ ? getTransformerFactoryV2(resourceDir)
+ : getTransformerFactoryV1(context, resourceDir, authConfig);
+};
+
+const getTransformerFactoryV2 = (
+ resourceDir: string,
+): (options: TransformerFactoryArgs) => Promise => async (options?: TransformerFactoryArgs) => {
+ const modelTransformer = new ModelTransformerV2();
+ const indexTransformer = new IndexTransformerV2();
+ const hasOneTransformer = new HasOneTransformerV2();
+ const authTransformer = new AuthTransformerV2({
+ adminRoles: options.adminRoles ?? [],
+ identityPoolId: options.identityPoolId,
+ });
+ const transformerList: TransformerPluginProviderV2[] = [
+ modelTransformer,
+ new FunctionTransformerV2(),
+ new HttpTransformerV2(),
+ new PredictionsTransformerV2(options?.storageConfig),
+ new PrimaryKeyTransformerV2(),
+ indexTransformer,
+ new HasManyTransformerV2(),
+ hasOneTransformer,
+ new ManyToManyTransformerV2(modelTransformer, indexTransformer, hasOneTransformer, authTransformer),
+ new BelongsToTransformerV2(),
+ new DefaultValueTransformerV2(),
+ authTransformer,
+ new MapsToTransformerV2(),
+ // TODO: initialize transformer plugins
+ ];
+
+ if (options?.addSearchableTransformer) {
+ transformerList.push(new SearchableModelTransformerV2());
+ }
+
+ const customTransformersConfig = await loadProject(resourceDir);
+ const customTransformerList = customTransformersConfig?.config?.transformers;
+ const customTransformers = (Array.isArray(customTransformerList) ? customTransformerList : [])
+ .map(importTransformerModule)
+ .map(imported => {
+ const CustomTransformer = imported.default;
+
+ if (typeof CustomTransformer === 'function') {
+ return new CustomTransformer();
+ } if (typeof CustomTransformer === 'object') {
+ // Todo: Use a shim to ensure that it adheres to TransformerProvider interface. For now throw error
+ // return CustomTransformer;
+ throw new Error("Custom Transformers' should implement TransformerProvider interface");
+ }
+
+ throw new Error("Custom Transformers' default export must be a function or an object");
+ })
+ .filter(customTransformer => customTransformer);
+
+ if (customTransformers.length > 0) {
+ transformerList.push(...customTransformers);
+ }
+
+ return transformerList;
+};
+
+function getTransformerFactoryV1(context: $TSContext, resourceDir: string, authConfig?: $TSAny) {
+ return async (addSearchableTransformer: boolean, storageConfig?: $TSAny) => {
+ const transformerList: ITransformer[] = [
+ // TODO: Removing until further discussion. `getTransformerOptions(project, '@model')`
+ new DynamoDBModelTransformerV1(),
+ new VersionedModelTransformerV1(),
+ new FunctionTransformerV1(),
+ new HttpTransformerV1(),
+ new KeyTransformerV1(),
+ new ModelConnectionTransformerV1(),
+ new PredictionsTransformerV1(storageConfig),
+ ];
+
+ if (addSearchableTransformer) {
+ transformerList.push(new SearchableModelTransformerV1());
+ }
+
+ const customTransformersConfig: TransformConfig = await readTransformerConfiguration(resourceDir);
+ const customTransformers = (
+ customTransformersConfig && customTransformersConfig.transformers ? customTransformersConfig.transformers : []
+ )
+ .map(importTransformerModule)
+ .map(imported => {
+ const CustomTransformer = imported.default;
+
+ if (typeof CustomTransformer === 'function') {
+ return new CustomTransformer();
+ } else if (typeof CustomTransformer === 'object') {
+ return CustomTransformer;
+ }
+
+ throw new Error("Custom Transformers' default export must be a function or an object");
+ })
+ .filter(customTransformer => customTransformer);
+
+ if (customTransformers.length > 0) {
+ transformerList.push(...customTransformers);
+ }
+
+ // TODO: Build dependency mechanism into transformers. Auth runs last
+ // so any resolvers that need to be protected will already be created.
+
+ let amplifyAdminEnabled: boolean = false;
+
+ try {
+ const amplifyMeta = stateManager.getMeta();
+ const appId = amplifyMeta?.providers?.[PROVIDER_NAME]?.AmplifyAppId;
+ const res = await CloudformationProviderFacade.isAmplifyAdminApp(context, appId);
+ amplifyAdminEnabled = res.isAdminApp;
+ } catch (err) {
+ // if it is not an AmplifyAdmin app, do nothing
+ }
+
+ transformerList.push(new ModelAuthTransformerV1({ authConfig, addAwsIamAuthInOutputSchema: amplifyAdminEnabled }));
+ return transformerList;
+ };
+}
+
+/**
+ * Attempt to load the module from a transformer name using the following priority order
+ * - modulePath is an absolute path to an NPM package
+ * - modulePath is a package name, then it will be loaded from the project's root's node_modules with createRequireFromPath.
+ * - modulePath is a name of a globally installed package
+ */
+const importTransformerModule = (transformerName: string) => {
+ const fileUrlMatch = /^file:\/\/(.*)\s*$/m.exec(transformerName);
+ const modulePath = fileUrlMatch ? fileUrlMatch[1] : transformerName;
+
+ if (!modulePath) {
+ throw new Error(`Invalid value specified for transformer: '${transformerName}'`);
+ }
+
+ let importedModule;
+ const tempModulePath = modulePath.toString();
+
+ try {
+ if (path.isAbsolute(tempModulePath)) {
+ // Load it by absolute path
+ /* eslint-disable-next-line global-require, import/no-dynamic-require */
+ importedModule = require(modulePath);
+ } else {
+ const projectRootPath = pathManager.findProjectRoot();
+ const projectNodeModules = path.join(projectRootPath, 'node_modules');
+
+ try {
+ importedModule = importFrom(projectNodeModules, modulePath);
+ } catch {
+ // Intentionally left blank to try global
+ }
+
+ // Try global package install
+ if (!importedModule) {
+ importedModule = importGlobal(modulePath);
+ }
+ }
+
+ // At this point we've to have an imported module, otherwise module loader, threw an error.
+ return importedModule;
+ } catch (error) {
+ printer.error(`Unable to import custom transformer module(${modulePath}).`);
+ printer.error(`You may fix this error by editing transformers at ${path.join(transformerName, TRANSFORM_CONFIG_FILE_NAME)}`);
+ throw error;
+ }
+};
diff --git a/packages/amplify-category-api/src/graphql-transformer/transformer-version.ts b/packages/amplify-category-api/src/graphql-transformer/transformer-version.ts
new file mode 100644
index 0000000000..144441cf6d
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/transformer-version.ts
@@ -0,0 +1,57 @@
+import {
+ $TSContext,
+ pathManager,
+ stateManager,
+ FeatureFlags,
+} from 'amplify-cli-core';
+
+/**
+ * Shorthand for Feature flag retrieval.
+ */
+const useExperimentalPipelinedTransformerFF = (): boolean => FeatureFlags.getBoolean('graphQLTransformer.useExperimentalPipelinedTransformer');
+const transformerVersionFF = (): number => FeatureFlags.getNumber('graphQLTransformer.transformerVersion');
+
+/**
+ * Inspect feature flags for the project and determine if this particular project should be using graphql v2 or v1.
+ * Coerces feature flags into a sane state if they are out of, and throws error on invalid configuration.
+ */
+export const getTransformerVersion = async (context): Promise => {
+ if (useExperimentalPipelinedTransformerFF() === false) {
+ return 1;
+ }
+
+ if (isLegacyFeatureFlagConfiguration()) {
+ await migrateToTransformerVersionFeatureFlag(context);
+ }
+
+ const transformerVersion = transformerVersionFF();
+ if (transformerVersion !== 1 && transformerVersion !== 2) {
+ throw new Error(`Invalid value specified for transformerVersion: '${transformerVersion}'`);
+ }
+
+ return transformerVersion;
+};
+
+/**
+ * Return whether or not the project is configured with legacy pipelined transformer feature flags, and may need to be updated.
+ */
+const isLegacyFeatureFlagConfiguration = (): boolean => useExperimentalPipelinedTransformerFF() && transformerVersionFF() === 1;
+
+/**
+ * Update project feature flags and alert the user if they have a deprecated set of feature flags.
+ */
+const migrateToTransformerVersionFeatureFlag = async (context: $TSContext): Promise => {
+ const projectPath = pathManager.findProjectRoot() ?? process.cwd();
+
+ const config = stateManager.getCLIJSON(projectPath, undefined, {
+ throwIfNotExist: false,
+ preserveComments: true,
+ });
+
+ // eslint-disable-next-line spellcheck/spell-checker
+ config.features.graphqltransformer.transformerversion = 2;
+ stateManager.setCLIJSON(projectPath, config);
+ await FeatureFlags.reloadValues();
+ // eslint-disable-next-line spellcheck/spell-checker
+ context.print.warning(`\nThe project is configured with 'transformerVersion': ${transformerVersionFF()}, but 'useExperimentalPipelinedTransformer': ${useExperimentalPipelinedTransformerFF()}. Setting the 'transformerVersion': ${config.features.graphqltransformer.transformerversion}. 'useExperimentalPipelinedTransformer' is deprecated.`);
+};
diff --git a/packages/amplify-category-api/src/graphql-transformer/user-defined-slots.ts b/packages/amplify-category-api/src/graphql-transformer/user-defined-slots.ts
new file mode 100644
index 0000000000..4d55860888
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/user-defined-slots.ts
@@ -0,0 +1,71 @@
+import { UserDefinedSlot, UserDefinedResolver } from '@aws-amplify/graphql-transformer-core';
+import _ from 'lodash';
+
+export const SLOT_NAMES = new Set([
+ 'init',
+ 'preAuth',
+ 'auth',
+ 'postAuth',
+ 'preDataLoad',
+ 'preUpdate',
+ 'preSubscribe',
+ 'postDataLoad',
+ 'postUpdate',
+ 'finish',
+]);
+
+const EXCLUDE_FILES = new Set(['README.md']);
+
+export function parseUserDefinedSlots(userDefinedTemplates: Record): Record {
+ type ResolverKey = string;
+ type ResolverOrder = number;
+ const groupedResolversMap: Record> = {};
+
+ Object.entries(userDefinedTemplates)
+ // filter out non-resolver files
+ .filter(([fileName]) => !EXCLUDE_FILES.has(fileName))
+ .forEach(([fileName, template]) => {
+ const slicedSlotName = fileName.split('.');
+ const isSlot = SLOT_NAMES.has(slicedSlotName[2]);
+
+ if (!isSlot) {
+ return;
+ }
+ const resolverType = slicedSlotName[slicedSlotName.length - 2] === 'res' ? 'responseResolver' : 'requestResolver';
+ const resolverName = [slicedSlotName[0], slicedSlotName[1]].join('.');
+ const slotName = slicedSlotName[2];
+ const resolverOrder = `order${Number(slicedSlotName[3]) || 0}`;
+ const resolver: UserDefinedResolver = {
+ fileName,
+ template,
+ };
+ // because a slot can have a request and response resolver, we need to group corresponding request and response resolvers
+ if (_.has(groupedResolversMap, [`${resolverName}#${slotName}`, resolverOrder])) {
+ _.set(groupedResolversMap, [`${resolverName}#${slotName}`, resolverOrder, resolverType], resolver);
+ } else {
+ const slot = {
+ resolverTypeName: slicedSlotName[0],
+ resolverFieldName: slicedSlotName[1],
+ slotName,
+ [resolverType]: resolver,
+ };
+ _.set(groupedResolversMap, [`${resolverName}#${slotName}`, resolverOrder], slot);
+ }
+ });
+
+ return Object.entries(groupedResolversMap)
+ .map(([resolverNameKey, numberedSlots]) => ({
+ orderedSlots: Object.entries(numberedSlots)
+ .sort(([i], [j]) => i.localeCompare(j))
+ .map(([_, slot]) => slot),
+ resolverName: resolverNameKey.split('#')[0],
+ }))
+ .reduce((acc, { orderedSlots, resolverName }) => {
+ if (acc[resolverName]) {
+ acc[resolverName].push(...orderedSlots);
+ } else {
+ acc[resolverName] = orderedSlots;
+ }
+ return acc;
+ }, {} as Record);
+}
diff --git a/packages/amplify-category-api/src/graphql-transformer/utils.ts b/packages/amplify-category-api/src/graphql-transformer/utils.ts
new file mode 100644
index 0000000000..4de9bcd9f2
--- /dev/null
+++ b/packages/amplify-category-api/src/graphql-transformer/utils.ts
@@ -0,0 +1,321 @@
+import fs from 'fs-extra';
+import * as path from 'path';
+import { TransformerProjectConfig, DeploymentResources } from '@aws-amplify/graphql-transformer-core';
+import rimraf from 'rimraf';
+import { $TSContext, AmplifyCategories, CloudformationProviderFacade, JSONUtilities, pathManager, stateManager } from 'amplify-cli-core';
+import { CloudFormation, Fn } from 'cloudform';
+import { ResourceConstants } from 'graphql-transformer-common';
+import { pullAllBy, find } from 'lodash';
+import { printer } from 'amplify-prompts';
+
+const PARAMETERS_FILE_NAME = 'parameters.json';
+const CUSTOM_ROLES_FILE_NAME = 'custom-roles.json';
+const AMPLIFY_ADMIN_ROLE = '_Full-access/CognitoIdentityCredentials';
+const AMPLIFY_MANAGE_ROLE = '_Manage-only/CognitoIdentityCredentials';
+const PROVIDER_NAME = 'awscloudformation';
+
+interface CustomRolesConfig {
+ adminRoleNames?: Array;
+}
+
+export const getIdentityPoolId = async (ctx: $TSContext): Promise => {
+ const { allResources, resourcesToBeDeleted } = await ctx.amplify.getResourceStatus('auth');
+ const authResources = pullAllBy(allResources, resourcesToBeDeleted, 'resourceName');
+ const authResource = find(authResources, { service: 'Cognito', providerPlugin: PROVIDER_NAME }) as any;
+ return authResource?.output?.IdentityPoolId;
+};
+
+export const getAdminRoles = async (ctx: $TSContext, apiResourceName: string | undefined): Promise> => {
+ let currentEnv;
+ const adminRoles = new Array();
+
+ try {
+ currentEnv = ctx.amplify.getEnvInfo().envName;
+ } catch (err) {
+ // When there is no environment info, return [] - This is required for sandbox pull
+ return [];
+ }
+
+ //admin ui roles
+ try {
+ const amplifyMeta = stateManager.getMeta();
+ const appId = amplifyMeta?.providers?.[PROVIDER_NAME]?.AmplifyAppId;
+ const res = await CloudformationProviderFacade.isAmplifyAdminApp(ctx, appId);
+ if (res.userPoolID) {
+ adminRoles.push(`${res.userPoolID}${AMPLIFY_ADMIN_ROLE}`, `${res.userPoolID}${AMPLIFY_MANAGE_ROLE}`);
+ }
+ } catch (err) {
+ // no need to error if not admin ui app
+ }
+
+ // additonal admin role checks
+ if (apiResourceName) {
+ // lambda functions which have access to the api
+ const { allResources, resourcesToBeDeleted } = await ctx.amplify.getResourceStatus('function');
+ const resources = pullAllBy(allResources, resourcesToBeDeleted, 'resourceName')
+ .filter((r: any) => r.dependsOn?.some((d: any) => d?.resourceName === apiResourceName))
+ .map((r: any) => `${r.resourceName}-${currentEnv}`);
+ adminRoles.push(...resources);
+
+ // check for custom iam admin roles
+ const customRoleFile = path.join(
+ pathManager.getResourceDirectoryPath(undefined, AmplifyCategories.API, apiResourceName),
+ CUSTOM_ROLES_FILE_NAME,
+ );
+ if (fs.existsSync(customRoleFile)) {
+ const customRoleConfig = JSONUtilities.readJson(customRoleFile);
+ if (customRoleConfig && customRoleConfig.adminRoleNames) {
+ adminRoles.push(...customRoleConfig.adminRoleNames);
+ }
+ }
+ }
+ return adminRoles;
+};
+
+export function mergeUserConfigWithTransformOutput(
+ userConfig: TransformerProjectConfig,
+ transformOutput: DeploymentResources,
+ opts?: any,
+): DeploymentResources {
+ const userFunctions = userConfig.functions || {};
+ const userResolvers = userConfig.resolvers || {};
+ const userPipelineFunctions = userConfig.pipelineFunctions || {};
+ const functions = transformOutput.functions;
+ const resolvers = transformOutput.resolvers;
+ const pipelineFunctions = transformOutput.pipelineFunctions;
+
+ if (!opts?.disableFunctionOverrides) {
+ for (const userFunction of Object.keys(userFunctions)) {
+ functions[userFunction] = userFunctions[userFunction];
+ }
+ }
+
+ if (!opts?.disablePipelineFunctionOverrides) {
+ const pipelineFunctionKeys = Object.keys(userPipelineFunctions);
+
+ if (pipelineFunctionKeys.length > 0) {
+ printer.warn(
+ ' You are using the "pipelineFunctions" directory for overridden and custom resolvers. ' +
+ 'Please use the "resolvers" directory as "pipelineFunctions" will be deprecated.\n',
+ );
+ }
+
+ for (const userPipelineFunction of pipelineFunctionKeys) resolvers[userPipelineFunction] = userPipelineFunctions[userPipelineFunction];
+ }
+
+ if (!opts?.disableResolverOverrides) {
+ for (const userResolver of Object.keys(userResolvers)) {
+ if (userResolver !== 'README.md') {
+ resolvers[userResolver] = userResolvers[userResolver].toString();
+ }
+ }
+ }
+
+ const stacks = overrideUserDefinedStacks(userConfig, transformOutput);
+
+ return {
+ ...transformOutput,
+ functions,
+ resolvers,
+ pipelineFunctions,
+ stacks,
+ };
+}
+
+function overrideUserDefinedStacks(userConfig: TransformerProjectConfig, transformOutput: DeploymentResources) {
+ const userStacks = userConfig.stacks || {};
+ const { stacks, rootStack } = transformOutput;
+
+ const resourceTypesToDependOn = {
+ 'AWS::CloudFormation::Stack': true,
+ 'AWS::AppSync::GraphQLApi': true,
+ 'AWS::AppSync::GraphQLSchema': true,
+ };
+
+ const allResourceIds = Object.keys(rootStack.Resources).filter((k: string) => {
+ const resource = rootStack.Resources[k];
+ return resourceTypesToDependOn[resource.Type];
+ });
+
+ const parametersKeys = Object.keys(rootStack.Parameters);
+ const customStackParams = parametersKeys.reduce(
+ (acc: any, k: string) => ({
+ ...acc,
+ [k]: Fn.Ref(k),
+ }),
+ {},
+ );
+
+ customStackParams[ResourceConstants.PARAMETERS.AppSyncApiId] = Fn.GetAtt(ResourceConstants.RESOURCES.GraphQLAPILogicalID, 'ApiId');
+
+ let updatedParameters = rootStack.Parameters;
+
+ for (const userStack of Object.keys(userStacks)) {
+ if (stacks[userStack]) {
+ throw new Error(`You cannot provide a stack named ${userStack} as it \
+ will be overwritten by a stack generated by the GraphQL Transform.`);
+ }
+ const userDefinedStack = userStacks[userStack];
+
+ for (const key of Object.keys(userDefinedStack.Parameters)) {
+ if (customStackParams[key] == null) {
+ customStackParams[key] = Fn.Ref(key);
+
+ if (updatedParameters[key]) throw new Error(`Cannot redefine CloudFormation parameter ${key} in stack ${userStack}.`);
+ else updatedParameters[key] = userDefinedStack.Parameters[key];
+ }
+ }
+
+ const parametersForStack = Object.keys(userDefinedStack.Parameters).reduce(
+ (acc, k) => ({
+ ...acc,
+ [k]: customStackParams[k],
+ }),
+ {},
+ );
+
+ stacks[userStack] = userDefinedStack;
+
+ const stackResourceId = userStack.split(/[^A-Za-z]/).join('');
+ const customNestedStack = new CloudFormation.Stack({
+ Parameters: parametersForStack,
+ TemplateURL: Fn.Join('/', [
+ 'https://s3.amazonaws.com',
+ Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentBucket),
+ Fn.Ref(ResourceConstants.PARAMETERS.S3DeploymentRootKey),
+ 'stacks',
+ userStack,
+ ]),
+ }).dependsOn(allResourceIds);
+ rootStack.Resources[stackResourceId] = customNestedStack;
+ }
+
+ rootStack.Parameters = updatedParameters;
+
+ return stacks;
+}
+
+/**
+ * Writes a deployment to disk at a path.
+ */
+export async function writeDeploymentToDisk(
+ context: $TSContext,
+ deployment: DeploymentResources,
+ directory: string,
+ rootStackFileName: string = 'rootStack.json',
+ buildParameters: Object,
+ minify = false,
+) {
+ fs.ensureDirSync(directory);
+ // Delete the last deployments resources except for tsconfig if present
+ emptyBuildDirPreserveTsconfig(directory);
+
+ // Write the schema to disk
+ const schema = deployment.schema;
+ const fullSchemaPath = path.normalize(directory + `/schema.graphql`);
+ fs.writeFileSync(fullSchemaPath, schema);
+
+ // Setup the directories if they do not exist.
+ initStacksAndResolversDirectories(directory);
+
+ // Write resolvers to disk
+ const resolverFileNames = Object.keys(deployment.resolvers);
+ const resolverRootPath = resolverDirectoryPath(directory);
+ for (const resolverFileName of resolverFileNames) {
+ const fullResolverPath = path.normalize(resolverRootPath + '/' + resolverFileName);
+ fs.writeFileSync(fullResolverPath, deployment.resolvers[resolverFileName]);
+ }
+
+ // Write pipeline resolvers to disk
+ const pipelineFunctions = Object.keys(deployment.pipelineFunctions);
+ const pipelineFunctionRootPath = pipelineFunctionDirectoryPath(directory);
+ for (const functionFileName of pipelineFunctions) {
+ const fullTemplatePath = path.normalize(pipelineFunctionRootPath + '/' + functionFileName);
+ fs.writeFileSync(fullTemplatePath, deployment.pipelineFunctions[functionFileName]);
+ }
+
+ // Write the stacks to disk
+ const stackNames = Object.keys(deployment.stacks);
+ const stackRootPath = stacksDirectoryPath(directory);
+ for (const stackFileName of stackNames) {
+ const fileNameParts = stackFileName.split('.');
+ if (fileNameParts.length === 1) {
+ fileNameParts.push('json');
+ }
+ const fullFileName = fileNameParts.join('.');
+ throwIfNotJSONExt(fullFileName);
+ const fullStackPath = path.normalize(stackRootPath + '/' + fullFileName);
+ let stackContent = deployment.stacks[stackFileName];
+ if (typeof stackContent === 'string') {
+ stackContent = JSON.parse(stackContent);
+ }
+ await CloudformationProviderFacade.prePushCfnTemplateModifier(context, stackContent);
+ fs.writeFileSync(fullStackPath, JSONUtilities.stringify(stackContent, { minify }));
+ }
+
+ // Write any functions to disk
+ const functionNames = Object.keys(deployment.functions);
+ const functionRootPath = path.normalize(directory + `/functions`);
+ if (!fs.existsSync(functionRootPath)) {
+ fs.mkdirSync(functionRootPath);
+ }
+ for (const functionName of functionNames) {
+ const fullFunctionPath = path.normalize(functionRootPath + '/' + functionName);
+ const zipContents = fs.readFileSync(deployment.functions[functionName]);
+ fs.writeFileSync(fullFunctionPath, zipContents);
+ }
+ const rootStack = deployment.rootStack;
+ const rootStackPath = path.normalize(directory + `/${rootStackFileName}`);
+ const rootStackString = minify ? JSON.stringify(rootStack) : JSON.stringify(rootStack, null, 4);
+ fs.writeFileSync(rootStackPath, rootStackString);
+
+ // Write params to disk
+ const jsonString = JSON.stringify(buildParameters, null, 4);
+ const parametersOutputFilePath = path.join(directory, PARAMETERS_FILE_NAME);
+ fs.writeFileSync(parametersOutputFilePath, jsonString);
+}
+
+function initStacksAndResolversDirectories(directory: string) {
+ const resolverRootPath = resolverDirectoryPath(directory);
+ if (!fs.existsSync(resolverRootPath)) {
+ fs.mkdirSync(resolverRootPath);
+ }
+ const stackRootPath = stacksDirectoryPath(directory);
+ if (!fs.existsSync(stackRootPath)) {
+ fs.mkdirSync(stackRootPath);
+ }
+}
+
+function pipelineFunctionDirectoryPath(rootPath: string) {
+ return path.normalize(path.join(rootPath, 'pipelineFunctions'));
+}
+
+function resolverDirectoryPath(rootPath: string) {
+ return path.normalize(rootPath + `/resolvers`);
+}
+
+function stacksDirectoryPath(rootPath: string) {
+ return path.normalize(rootPath + `/stacks`);
+}
+
+function throwIfNotJSONExt(stackFile: string) {
+ const extension = path.extname(stackFile);
+ if (extension === '.yaml' || extension === '.yml') {
+ throw new Error(`Yaml is not yet supported. Please convert the CloudFormation stack ${stackFile} to json.`);
+ }
+ if (extension !== '.json') {
+ throw new Error(`Invalid extension ${extension} for stack ${stackFile}`);
+ }
+}
+
+const emptyBuildDirPreserveTsconfig = (directory: string) => {
+ const files = fs.readdirSync(directory);
+ files.forEach(file => {
+ const fileDir = path.join(directory, file);
+ if (fs.lstatSync(fileDir).isDirectory()) {
+ rimraf.sync(fileDir);
+ } else if (!file.endsWith('tsconfig.resource.json')) {
+ fs.unlinkSync(fileDir);
+ }
+ });
+};
diff --git a/packages/amplify-category-api/src/index.ts b/packages/amplify-category-api/src/index.ts
new file mode 100644
index 0000000000..8e492388b0
--- /dev/null
+++ b/packages/amplify-category-api/src/index.ts
@@ -0,0 +1,359 @@
+import {
+ $TSAny,
+ $TSContext,
+ $TSObject,
+ AmplifyCategories,
+ AmplifySupportedService,
+ buildOverrideDir,
+ pathManager,
+ stateManager,
+} from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import { validateAddApiRequest, validateUpdateApiRequest } from 'amplify-util-headless-input';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { run } from './commands/api/console';
+import { getAppSyncAuthConfig, getAppSyncResourceName } from './provider-utils/awscloudformation/utils/amplify-meta-utils';
+import { provider } from './provider-utils/awscloudformation/aws-constants';
+import { ApigwStackTransform } from './provider-utils/awscloudformation/cdk-stack-builder';
+import { getCfnApiArtifactHandler } from './provider-utils/awscloudformation/cfn-api-artifact-handler';
+import { askAuthQuestions } from './provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough';
+import { authConfigToAppSyncAuthType } from './provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper';
+import { checkAppsyncApiResourceMigration } from './provider-utils/awscloudformation/utils/check-appsync-api-migration';
+import { getAppSyncApiResourceName } from './provider-utils/awscloudformation/utils/getAppSyncApiName';
+
+export { NETWORK_STACK_LOGICAL_ID } from './category-constants';
+export { addAdminQueriesApi, updateAdminQueriesApi } from './provider-utils/awscloudformation';
+export { DEPLOYMENT_MECHANISM } from './provider-utils/awscloudformation/base-api-stack';
+// eslint-disable-next-line spellcheck/spell-checker
+export { convertDeperecatedRestApiPaths } from './provider-utils/awscloudformation/convert-deprecated-apigw-paths';
+export { getContainers } from './provider-utils/awscloudformation/docker-compose';
+export { EcsAlbStack } from './provider-utils/awscloudformation/ecs-alb-stack';
+export { EcsStack } from './provider-utils/awscloudformation/ecs-apigw-stack';
+export { promptToAddApiKey } from './provider-utils/awscloudformation/prompt-to-add-api-key';
+export {
+ ApiResource,
+ generateContainersArtifacts,
+ processDockerConfig,
+} from './provider-utils/awscloudformation/utils/containers-artifacts';
+export { getAuthConfig } from './provider-utils/awscloudformation/utils/get-appsync-auth-config';
+export { getResolverConfig } from './provider-utils/awscloudformation/utils/get-appsync-resolver-config';
+export { getGitHubOwnerRepoFromPath } from './provider-utils/awscloudformation/utils/github';
+export * from './graphql-transformer';
+
+const category = AmplifyCategories.API;
+const categories = 'categories';
+
+/**
+ * Open the AppSync/API Gateway AWS console
+ */
+export const console = async (context: $TSContext): Promise => {
+ await run(context);
+};
+
+/**
+ * Migrate from original API config
+ */
+export const migrate = async (context: $TSContext, serviceName?: string): Promise => {
+ const { projectPath } = context?.migrationInfo ?? { projectPath: pathManager.findProjectRoot() };
+ const amplifyMeta = stateManager.getMeta(projectPath);
+ const migrateResourcePromises = [];
+ for (const categoryName of Object.keys(amplifyMeta)) {
+ if (categoryName !== category) {
+ // eslint-disable-next-line no-continue
+ continue;
+ }
+ for (const resourceName of Object.keys(amplifyMeta[category])) {
+ try {
+ if (amplifyMeta[category][resourceName].providerPlugin) {
+ const providerController = await import(
+ path.join(__dirname, 'provider-utils', amplifyMeta[category][resourceName].providerPlugin, 'index')
+ );
+ // eslint-disable-next-line max-depth
+ if (!providerController) {
+ // eslint-disable-next-line no-continue
+ continue;
+ }
+ // eslint-disable-next-line max-depth
+ if (!serviceName || serviceName === amplifyMeta[category][resourceName].service) {
+ migrateResourcePromises.push(
+ providerController.migrateResource(context, projectPath, amplifyMeta[category][resourceName].service, resourceName),
+ );
+ }
+ } else {
+ printer.error(`Provider not configured for ${category}: ${resourceName}`);
+ }
+ } catch (e) {
+ printer.warn(`Could not run migration for ${category}: ${resourceName}`);
+ throw e;
+ }
+ }
+ }
+ for (const migrateResourcePromise of migrateResourcePromises) {
+ await migrateResourcePromise;
+ }
+};
+
+/**
+ * Setup new environment with rds datasource
+ */
+export const initEnv = async (context: $TSContext): Promise => {
+ const datasource = 'Aurora Serverless';
+ const service = 'service';
+ const rdsInit = 'rdsInit';
+ const rdsRegion = 'rdsRegion';
+ const rdsClusterIdentifier = 'rdsClusterIdentifier';
+ const rdsSecretStoreArn = 'rdsSecretStoreArn';
+ const rdsDatabaseName = 'rdsDatabaseName';
+
+ const { amplify } = context;
+
+ /**
+ * Check if we need to do the walkthrough, by looking to see if previous environments have
+ * configured an RDS datasource
+ */
+ const backendConfigFilePath = pathManager.getBackendConfigFilePath();
+
+ // If this is a mobile hub migrated project without locally added resources then there is no
+ // backend config exists yet.
+ if (!fs.existsSync(backendConfigFilePath)) {
+ return;
+ }
+
+ const backendConfig = stateManager.getBackendConfig();
+
+ if (!backendConfig[category]) {
+ return;
+ }
+
+ let resourceName;
+ const apis = Object.keys(backendConfig[category]);
+ for (const api of apis) {
+ if (backendConfig[category][api][service] === AmplifySupportedService.APPSYNC) {
+ resourceName = api;
+ break;
+ }
+ }
+
+ // If an AppSync API does not exist, no need to prompt for rds datasource
+ if (!resourceName) {
+ return;
+ }
+
+ // If an AppSync API has not been initialized with RDS, no need to prompt
+ if (!backendConfig[category][resourceName][rdsInit]) {
+ return;
+ }
+
+ const providerController = await import(path.join(__dirname, 'provider-utils', provider, 'index'));
+
+ if (!providerController) {
+ printer.error('Provider not configured for this category');
+ return;
+ }
+
+ /**
+ * Check team provider info to ensure it hasn't already been created for current env
+ */
+ const currentEnv = amplify.getEnvInfo().envName;
+ const teamProviderInfo = stateManager.getTeamProviderInfo();
+ if (
+ teamProviderInfo[currentEnv][categories]
+ && teamProviderInfo[currentEnv][categories][category]
+ && teamProviderInfo[currentEnv][categories][category][resourceName]
+ && teamProviderInfo[currentEnv][categories][category][resourceName]
+ && teamProviderInfo[currentEnv][categories][category][resourceName][rdsRegion]
+ ) {
+ return;
+ }
+
+ // execute the walkthrough
+ await providerController
+ .addDatasource(context, category, datasource)
+ .then(answers => {
+ /**
+ * Write the new answers to the team provider info
+ */
+ if (!teamProviderInfo[currentEnv][categories]) {
+ teamProviderInfo[currentEnv][categories] = {};
+ }
+ if (!teamProviderInfo[currentEnv][categories][category]) {
+ teamProviderInfo[currentEnv][categories][category] = {};
+ }
+ if (!teamProviderInfo[currentEnv][categories][category][resourceName]) {
+ teamProviderInfo[currentEnv][categories][category][resourceName] = {};
+ }
+
+ teamProviderInfo[currentEnv][categories][category][resourceName][rdsRegion] = answers.region;
+ teamProviderInfo[currentEnv][categories][category][resourceName][rdsClusterIdentifier] = answers.dbClusterArn;
+ teamProviderInfo[currentEnv][categories][category][resourceName][rdsSecretStoreArn] = answers.secretStoreArn;
+ teamProviderInfo[currentEnv][categories][category][resourceName][rdsDatabaseName] = answers.databaseName;
+
+ stateManager.setTeamProviderInfo(undefined, teamProviderInfo);
+ })
+ .then(() => {
+ context.amplify.executeProviderUtils(context, 'awscloudformation', 'compileSchema', { forceCompile: true });
+ });
+};
+
+/**
+ * Get permissions for depending on this resource
+ */
+export const getPermissionPolicies = async (
+ context: $TSContext,
+ resourceOpsMapping: $TSObject,
+): Promise<{ permissionPolicies: $TSAny[]; resourceAttributes: $TSAny[]; }> => {
+ const amplifyMeta = stateManager.getMeta();
+ const permissionPolicies = [];
+ const resourceAttributes = [];
+
+ await Promise.all(
+ Object.keys(resourceOpsMapping).map(async resourceName => {
+ try {
+ const providerName = amplifyMeta[category][resourceName].providerPlugin;
+ if (providerName) {
+ const providerController = await import(path.join(__dirname, 'provider-utils', providerName, 'index'));
+ const { policy, attributes } = await providerController.getPermissionPolicies(
+ context,
+ amplifyMeta[category][resourceName].service,
+ resourceName,
+ resourceOpsMapping[resourceName],
+ );
+ permissionPolicies.push(policy);
+ resourceAttributes.push({ resourceName, attributes, category });
+ } else {
+ printer.error(`Provider not configured for ${category}: ${resourceName}`);
+ }
+ } catch (e) {
+ printer.warn(`Could not get policies for ${category}: ${resourceName}`);
+ throw e;
+ }
+ }),
+ );
+ return { permissionPolicies, resourceAttributes };
+};
+
+/**
+ * Main entry point for executing an api subcommand
+ */
+export const executeAmplifyCommand = async (context: $TSContext): Promise => {
+ let commandPath = path.normalize(path.join(__dirname, 'commands'));
+ if (context.input.command === 'help') {
+ commandPath = path.join(commandPath, category);
+ } else {
+ commandPath = path.join(commandPath, category, context.input.command);
+ }
+
+ const commandModule = await import(commandPath);
+ try {
+ await commandModule.run(context);
+ } catch (error) {
+ if (error) {
+ printer.error(error.message || error);
+ if (error.stack) {
+ printer.debug(error.stack);
+ }
+ await context.usageData.emitError(error);
+ }
+ process.exitCode = 1;
+ }
+};
+
+/**
+ * Main entry point for executing a headless api command
+ */
+export const executeAmplifyHeadlessCommand = async (context: $TSContext, headlessPayload: string): Promise => {
+ context.usageData.pushHeadlessFlow(headlessPayload, context.input);
+ switch (context.input.command) {
+ case 'add':
+ await getCfnApiArtifactHandler(context).createArtifacts(await validateAddApiRequest(headlessPayload));
+ break;
+ case 'update': {
+ const resourceName = await getAppSyncApiResourceName(context);
+ await checkAppsyncApiResourceMigration(context, resourceName, true);
+ await getCfnApiArtifactHandler(context).updateArtifacts(await validateUpdateApiRequest(headlessPayload));
+ break;
+ }
+ default:
+ printer.error(`Headless mode for ${context.input.command} api is not implemented yet`);
+ }
+};
+
+/**
+ * Not yet implemented
+ */
+export const handleAmplifyEvent = async (_: $TSContext, args): Promise => {
+ printer.info(`${category} handleAmplifyEvent to be implemented`);
+ printer.info(`Received event args ${args}`);
+};
+
+/**
+ * Add a new auth mode to the API
+ */
+// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+export const addGraphQLAuthorizationMode = async (context: $TSContext, args: $TSObject) => {
+ const { authType, printLeadText, authSettings } = args;
+ const meta = stateManager.getMeta();
+ const apiName = getAppSyncResourceName(meta);
+ if (!apiName) {
+ return undefined;
+ }
+
+ const authConfig = getAppSyncAuthConfig(meta);
+ const addAuthConfig = await askAuthQuestions(authType, context, printLeadText, authSettings);
+ authConfig.additionalAuthenticationProviders.push(addAuthConfig);
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, apiName, 'output', { authConfig });
+ await context.amplify.updateBackendConfigAfterResourceUpdate(category, apiName, 'output', { authConfig });
+
+ await getCfnApiArtifactHandler(context).updateArtifacts(
+ {
+ version: 1,
+ serviceModification: {
+ serviceName: 'AppSync',
+ additionalAuthTypes: authConfig.additionalAuthenticationProviders.map(authConfigToAppSyncAuthType),
+ },
+ },
+ {
+ skipCompile: false,
+ },
+ );
+
+ return addAuthConfig;
+};
+
+/**
+ * Synthesize the CFN template for the API
+ */
+export const transformCategoryStack = async (context: $TSContext, resource: $TSObject): Promise => {
+ if (resource.service === AmplifySupportedService.APPSYNC) {
+ if (canResourceBeTransformed(resource.resourceName)) {
+ const backendDir = pathManager.getBackendDirPath();
+ const overrideDir = path.join(backendDir, resource.category, resource.resourceName);
+ const isBuild = await buildOverrideDir(backendDir, overrideDir).catch(error => {
+ printer.error(`Build error : ${error.message}`);
+ throw new Error(error);
+ });
+ await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'compileSchema', [
+ context,
+ {
+ forceCompile: true,
+ overrideConfig: {
+ overrideFlag: isBuild,
+ overrideDir,
+ resourceName: resource.resourceName,
+ },
+ },
+ ]);
+ }
+ } else if (resource.service === AmplifySupportedService.APIGW) {
+ if (canResourceBeTransformed(resource.resourceName)) {
+ // Rebuild CFN
+ const apigwStack = new ApigwStackTransform(context, resource.resourceName);
+ apigwStack.transform();
+ }
+ }
+};
+
+const canResourceBeTransformed = (
+ resourceName: string,
+): boolean => stateManager.resourceInputsJsonExists(undefined, AmplifyCategories.API, resourceName);
diff --git a/packages/amplify-category-api/src/provider-utils/api-artifact-handler.ts b/packages/amplify-category-api/src/provider-utils/api-artifact-handler.ts
new file mode 100644
index 0000000000..7dbab41d77
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/api-artifact-handler.ts
@@ -0,0 +1,10 @@
+import { AddApiRequest, UpdateApiRequest } from 'amplify-headless-interface';
+
+export interface ApiArtifactHandlerOptions {
+ skipCompile?: boolean;
+}
+
+export interface ApiArtifactHandler {
+ createArtifacts(request: AddApiRequest): Promise;
+ updateArtifacts(request: UpdateApiRequest, opts?: ApiArtifactHandlerOptions): Promise;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/api-input-manager/appsync-api-input-state.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/api-input-manager/appsync-api-input-state.ts
new file mode 100644
index 0000000000..4c930016a0
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/api-input-manager/appsync-api-input-state.ts
@@ -0,0 +1,49 @@
+import {
+ $TSContext,
+ AmplifyCategories,
+ AmplifySupportedService,
+ CLIInputSchemaValidator,
+ JSONUtilities,
+ pathManager,
+} from 'amplify-cli-core';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { AppSyncCLIInputs } from '../service-walkthrough-types/appsync-user-input-types';
+
+export class AppsyncApiInputState {
+ #cliInputsFilePath: string; //cli-inputs.json (output) filepath
+ #resourceName: string; //user friendly name provided by user
+ #category: string; //category of the resource
+ #service: string; //AWS service for the resource
+ #buildFilePath: string;
+
+ constructor(private readonly context: $TSContext, resourceName: string) {
+ this.#category = AmplifyCategories.API;
+ this.#service = AmplifySupportedService.APPSYNC;
+ this.#resourceName = resourceName;
+
+ const projectBackendDirPath = pathManager.getBackendDirPath();
+ this.#cliInputsFilePath = path.resolve(path.join(projectBackendDirPath, this.#category, this.#resourceName, 'cli-inputs.json'));
+ this.#buildFilePath = path.resolve(path.join(projectBackendDirPath, this.#category, this.#resourceName, 'build'));
+ }
+
+ public async isCLIInputsValid(cliInputs: AppSyncCLIInputs = this.getCLIInputPayload()): Promise {
+ const schemaValidator = new CLIInputSchemaValidator(this.context, 'appsync', this.#category, 'AppSyncCLIInputs');
+ return schemaValidator.validateInput(JSON.stringify(cliInputs));
+ }
+
+ public getCLIInputPayload(): AppSyncCLIInputs {
+ return JSONUtilities.readJson(this.#cliInputsFilePath, { throwIfNotExist: true })!;
+ }
+
+ public cliInputFileExists(): boolean {
+ return fs.existsSync(this.#cliInputsFilePath);
+ }
+
+ public async saveCLIInputPayload(cliInputs: AppSyncCLIInputs): Promise {
+ if (await this.isCLIInputsValid(cliInputs)) {
+ fs.ensureDirSync(path.join(pathManager.getBackendDirPath(), this.#category, this.#resourceName));
+ JSONUtilities.writeJson(this.#cliInputsFilePath, cliInputs);
+ }
+ }
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/apigw-input-state.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/apigw-input-state.ts
new file mode 100644
index 0000000000..6a886d4c30
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/apigw-input-state.ts
@@ -0,0 +1,178 @@
+import {
+ $TSContext,
+ $TSObject,
+ AmplifyCategories,
+ AmplifySupportedService,
+ CLIInputSchemaValidator,
+ getMigrateResourceMessageForOverride,
+ isResourceNameUnique,
+ JSONUtilities,
+ PathConstants,
+ pathManager,
+ stateManager,
+} from 'amplify-cli-core';
+import { prompter } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import { join } from 'path';
+import { ApigwInputs, ApigwStackTransform, CrudOperation, Path, PermissionSetting } from './cdk-stack-builder';
+import { convertDeperecatedRestApiPaths } from './convert-deprecated-apigw-paths';
+import { ApigwWalkthroughReturnPromise } from './service-walkthrough-types/apigw-types';
+
+export class ApigwInputState {
+ projectRootPath: string;
+ resourceName: string;
+ paths: { [pathName: string]: Path };
+
+ constructor(private readonly context: $TSContext, resourceName?: string) {
+ this.projectRootPath = pathManager.findProjectRoot();
+ this.resourceName = resourceName;
+ }
+
+ public addAdminQueriesResource = async (adminQueriesProps: AdminQueriesProps) => {
+ this.resourceName = adminQueriesProps.apiName;
+ this.paths = {
+ '/{proxy+}': {
+ lambdaFunction: adminQueriesProps.functionName,
+ permissions: {
+ setting: PermissionSetting.PRIVATE,
+ auth: [CrudOperation.CREATE, CrudOperation.READ, CrudOperation.UPDATE, CrudOperation.DELETE],
+ },
+ },
+ };
+
+ await this.createApigwArtifacts();
+
+ // Update amplify-meta and backend-config
+ const backendConfigs = {
+ service: AmplifySupportedService.APIGW,
+ providerPlugin: 'awscloudformation',
+ authorizationType: 'AMAZON_COGNITO_USER_POOLS',
+ dependsOn: adminQueriesProps.dependsOn,
+ };
+
+ this.context.amplify.updateamplifyMetaAfterResourceAdd(AmplifyCategories.API, adminQueriesProps.apiName, backendConfigs);
+ };
+
+ public updateAdminQueriesResource = async (adminQueriesProps: AdminQueriesProps) => {
+ this.resourceName = adminQueriesProps.apiName;
+ this.paths = {
+ '/{proxy+}': {
+ lambdaFunction: adminQueriesProps.functionName,
+ permissions: {
+ setting: PermissionSetting.PRIVATE,
+ auth: [CrudOperation.CREATE, CrudOperation.READ, CrudOperation.UPDATE, CrudOperation.DELETE],
+ },
+ },
+ };
+
+ await this.createApigwArtifacts();
+
+ this.context.amplify.updateamplifyMetaAfterResourceUpdate(
+ AmplifyCategories.API,
+ adminQueriesProps.apiName,
+ 'dependsOn',
+ adminQueriesProps.dependsOn,
+ );
+ };
+
+ public addApigwResource = async (serviceWalkthroughPromise: ApigwWalkthroughReturnPromise, options: $TSObject) => {
+ const { answers } = await serviceWalkthroughPromise;
+
+ this.resourceName = answers.resourceName;
+ this.paths = answers.paths;
+ options.dependsOn = answers.dependsOn;
+
+ isResourceNameUnique(AmplifyCategories.API, this.resourceName);
+
+ await this.createApigwArtifacts();
+
+ this.context.amplify.updateamplifyMetaAfterResourceAdd(AmplifyCategories.API, this.resourceName, options);
+ return this.resourceName;
+ };
+
+ public updateApigwResource = async (updateWalkthroughPromise: Promise<$TSObject>) => {
+ const { answers } = await updateWalkthroughPromise;
+
+ this.resourceName = answers.resourceName;
+ this.paths = answers.paths;
+
+ await this.createApigwArtifacts();
+
+ this.context.amplify.updateamplifyMetaAfterResourceUpdate(AmplifyCategories.API, this.resourceName, 'dependsOn', answers.dependsOn);
+ return this.resourceName;
+ };
+
+ public migrateAdminQueries = async (adminQueriesProps: AdminQueriesProps) => {
+ this.resourceName = this.resourceName ?? adminQueriesProps.apiName;
+ if (!(await prompter.yesOrNo(getMigrateResourceMessageForOverride(AmplifyCategories.API, this.resourceName, true), true))) {
+ return;
+ }
+ const resourceDirPath = pathManager.getResourceDirectoryPath(this.projectRootPath, AmplifyCategories.API, this.resourceName);
+
+ fs.removeSync(join(resourceDirPath, PathConstants.ParametersJsonFileName));
+ fs.removeSync(join(resourceDirPath, 'admin-queries-cloudformation-template.json'));
+
+ return this.updateAdminQueriesResource(adminQueriesProps);
+ };
+
+ public migrateApigwResource = async (resourceName: string) => {
+ this.resourceName = this.resourceName ?? resourceName;
+ if (!(await prompter.yesOrNo(getMigrateResourceMessageForOverride(AmplifyCategories.API, this.resourceName, true), true))) {
+ return;
+ }
+ const deprecatedParametersFileName = 'api-params.json';
+ const resourceDirPath = pathManager.getResourceDirectoryPath(this.projectRootPath, AmplifyCategories.API, this.resourceName);
+ const deprecatedParametersFilePath = join(resourceDirPath, deprecatedParametersFileName);
+ this.paths = convertDeperecatedRestApiPaths(deprecatedParametersFileName, deprecatedParametersFilePath, this.resourceName);
+
+ fs.removeSync(deprecatedParametersFilePath);
+ fs.removeSync(join(resourceDirPath, PathConstants.ParametersJsonFileName));
+ fs.removeSync(join(resourceDirPath, `${this.resourceName}-cloudformation-template.json`));
+
+ await this.createApigwArtifacts();
+ };
+
+ public cliInputsFileExists() {
+ return stateManager.resourceInputsJsonExists(this.projectRootPath, AmplifyCategories.API, this.resourceName);
+ }
+
+ public getCliInputPayload() {
+ return stateManager.getResourceInputsJson(this.projectRootPath, AmplifyCategories.API, this.resourceName);
+ }
+
+ public isCLIInputsValid(cliInputs?: ApigwInputs) {
+ if (!cliInputs) {
+ cliInputs = this.getCliInputPayload();
+ }
+
+ const schemaValidator = new CLIInputSchemaValidator(
+ this.context,
+ AmplifySupportedService.APIGW,
+ AmplifyCategories.API,
+ 'APIGatewayCLIInputs',
+ );
+ schemaValidator.validateInput(JSONUtilities.stringify(cliInputs));
+ }
+
+ private async createApigwArtifacts() {
+ const resourceDirPath = pathManager.getResourceDirectoryPath(this.projectRootPath, AmplifyCategories.API, this.resourceName);
+ fs.ensureDirSync(resourceDirPath);
+
+ const buildDirPath = join(resourceDirPath, PathConstants.BuildDirName);
+ fs.ensureDirSync(buildDirPath);
+
+ stateManager.setResourceInputsJson(this.projectRootPath, AmplifyCategories.API, this.resourceName, { version: 1, paths: this.paths });
+
+ stateManager.setResourceParametersJson(this.projectRootPath, AmplifyCategories.API, this.resourceName, {});
+
+ const stack = new ApigwStackTransform(this.context, this.resourceName, this);
+ await stack.transform();
+ }
+}
+
+export type AdminQueriesProps = {
+ apiName: string;
+ functionName: string;
+ authResourceName: string;
+ dependsOn: $TSObject[];
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/aws-constants.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/aws-constants.ts
new file mode 100644
index 0000000000..418b53b47f
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/aws-constants.ts
@@ -0,0 +1,8 @@
+import * as path from 'path';
+
+export const provider = 'awscloudformation';
+export const parametersFileName = 'api-params.json';
+export const cfnParametersFilename = 'parameters.json';
+export const gqlSchemaFilename = 'schema.graphql';
+
+export const rootAssetDir = path.resolve(path.join(__dirname, '..', '..', '..', 'resources', 'awscloudformation'));
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/base-api-stack.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/base-api-stack.ts
new file mode 100644
index 0000000000..a3b4bca35a
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/base-api-stack.ts
@@ -0,0 +1,550 @@
+import * as ec2 from '@aws-cdk/aws-ec2';
+import * as ecr from '@aws-cdk/aws-ecr';
+import * as ecs from '@aws-cdk/aws-ecs';
+import * as iam from '@aws-cdk/aws-iam';
+import { CfnFunction } from '@aws-cdk/aws-lambda';
+import * as logs from '@aws-cdk/aws-logs';
+import * as s3 from '@aws-cdk/aws-s3';
+import * as ssm from '@aws-cdk/aws-secretsmanager';
+import * as cloudmap from '@aws-cdk/aws-servicediscovery';
+import * as cdk from '@aws-cdk/core';
+import { prepareApp } from '@aws-cdk/core/lib/private/prepare-app';
+import { NETWORK_STACK_LOGICAL_ID } from '../../category-constants';
+import Container from './docker-compose/ecs-objects/container';
+import { GitHubSourceActionInfo, PipelineWithAwaiter } from './pipeline-with-awaiter';
+
+const PIPELINE_AWAITER_ZIP = 'custom-resource-pipeline-awaiter.zip';
+
+export enum DEPLOYMENT_MECHANISM {
+ /**
+ * on every amplify push
+ */
+ FULLY_MANAGED = 'FULLY_MANAGED',
+ /**
+ * on every github push
+ */
+ INDENPENDENTLY_MANAGED = 'INDENPENDENTLY_MANAGED',
+ /**
+ * manually push by the customer to ECR
+ */
+ SELF_MANAGED = 'SELF_MANAGED',
+}
+
+export type ContainersStackProps = Readonly<{
+ skipWait?: boolean;
+ categoryName: string;
+ apiName: string;
+ dependsOn: ReadonlyArray<{
+ category: string;
+ resourceName: string;
+ attributes: string[];
+ }>;
+ taskEnvironmentVariables?: Record;
+ deploymentMechanism: DEPLOYMENT_MECHANISM;
+ restrictAccess: boolean;
+ policies?: ReadonlyArray>;
+ containers: ReadonlyArray;
+ secretsArns?: ReadonlyMap;
+ exposedContainer: { name: string; port: number };
+ taskPorts: number[];
+ isInitialDeploy: boolean;
+ desiredCount: number;
+ createCloudMapService?: boolean;
+ gitHubSourceActionInfo?: GitHubSourceActionInfo;
+ existingEcrRepositories: Set;
+ currentStackName: string;
+}>;
+export abstract class ContainersStack extends cdk.Stack {
+ protected readonly vpcId: string;
+ private readonly vpcCidrBlock: string;
+ protected readonly subnets: ReadonlyArray;
+ private readonly clusterName: string;
+ private readonly zipPath: string;
+ private readonly cloudMapNamespaceId: string;
+ protected readonly vpcLinkId: string;
+ private readonly pipelineWithAwaiter: PipelineWithAwaiter;
+ protected readonly cloudMapService: cloudmap.CfnService | undefined;
+ protected readonly ecsService: ecs.CfnService;
+ protected readonly isAuthCondition: cdk.CfnCondition;
+ protected readonly appClientId: string | undefined;
+ protected readonly userPoolId: string | undefined;
+ protected readonly ecsServiceSecurityGroup: ec2.CfnSecurityGroup;
+ protected readonly parameters: ReadonlyMap;
+ protected readonly envName: string;
+ protected readonly deploymentBucketName: string;
+ protected readonly awaiterS3Key: string;
+
+ constructor(scope: cdk.Construct, id: string, private readonly props: ContainersStackProps) {
+ super(scope, id);
+
+ const {
+ parameters,
+ vpcId,
+ vpcCidrBlock,
+ subnets,
+ clusterName,
+ zipPath,
+ cloudMapNamespaceId,
+ vpcLinkId,
+ isAuthCondition,
+ appClientId,
+ userPoolId,
+ envName,
+ deploymentBucketName,
+ awaiterS3Key,
+ } = this.init();
+
+ this.parameters = parameters;
+
+ this.vpcId = vpcId;
+ this.vpcCidrBlock = vpcCidrBlock;
+ this.subnets = subnets;
+ this.clusterName = clusterName;
+ this.zipPath = zipPath;
+ this.cloudMapNamespaceId = cloudMapNamespaceId;
+ this.vpcLinkId = vpcLinkId;
+ this.isAuthCondition = isAuthCondition;
+ this.appClientId = appClientId;
+ this.userPoolId = userPoolId;
+ this.envName = envName;
+ this.deploymentBucketName = deploymentBucketName;
+ this.awaiterS3Key = awaiterS3Key;
+ const { service, serviceSecurityGroup, containersInfo, cloudMapService } = this.ecs();
+
+ this.cloudMapService = cloudMapService;
+ this.ecsService = service;
+ this.ecsServiceSecurityGroup = serviceSecurityGroup;
+
+ const { gitHubSourceActionInfo, skipWait } = this.props;
+
+ const { pipelineWithAwaiter } = this.pipeline({
+ skipWait,
+ service,
+ containersInfo: containersInfo.filter(container => container.repository),
+ gitHubSourceActionInfo,
+ });
+
+ this.pipelineWithAwaiter = pipelineWithAwaiter;
+
+ new cdk.CfnOutput(this, 'ContainerNames', {
+ value: cdk.Fn.join(
+ ',',
+ containersInfo.map(({ container: { containerName } }) => containerName),
+ ),
+ });
+ }
+
+ private init() {
+ const { restrictAccess, dependsOn, deploymentMechanism } = this.props;
+
+ // Unused in this stack, but required by the root stack
+ new cdk.CfnParameter(this, 'env', { type: 'String' });
+
+ const paramDomain = new cdk.CfnParameter(this, 'domain', { type: 'String', default: '' });
+ const paramRestrictAccess = new cdk.CfnParameter(this, 'restrictAccess', {
+ type: 'String',
+ allowedValues: ['true', 'false'],
+ default: 'false',
+ });
+
+ const paramZipPath = new cdk.CfnParameter(this, 'ParamZipPath', {
+ type: 'String',
+ // Required only for FULLY_MANAGED
+ default: deploymentMechanism === DEPLOYMENT_MECHANISM.FULLY_MANAGED ? undefined : '',
+ });
+
+ const parameters: Map = new Map();
+
+ parameters.set('ParamZipPath', paramZipPath);
+ parameters.set('domain', paramDomain);
+ parameters.set('restrictAccess', paramRestrictAccess);
+
+ const authParams: {
+ UserPoolId?: cdk.CfnParameter;
+ AppClientIDWeb?: cdk.CfnParameter;
+ } = {};
+
+ const paramTypes: Record = {
+ NetworkStackSubnetIds: 'CommaDelimitedList',
+ };
+
+ dependsOn.forEach(({ category, resourceName, attributes }) => {
+ attributes.forEach(attrib => {
+ const paramName = [category, resourceName, attrib].join('');
+
+ const type = paramTypes[paramName] ?? 'String';
+ const param = new cdk.CfnParameter(this, paramName, { type });
+
+ parameters.set(paramName, param);
+
+ if (category === 'auth') {
+ authParams[attrib as keyof typeof authParams] = param;
+ }
+ });
+ });
+
+ const paramVpcId = parameters.get(`${NETWORK_STACK_LOGICAL_ID}VpcId`);
+ const paramVpcCidrBlock = parameters.get(`${NETWORK_STACK_LOGICAL_ID}VpcCidrBlock`);
+ const paramSubnetIds = parameters.get(`${NETWORK_STACK_LOGICAL_ID}SubnetIds`);
+ const paramClusterName = parameters.get(`${NETWORK_STACK_LOGICAL_ID}ClusterName`);
+ const paramCloudMapNamespaceId = parameters.get(`${NETWORK_STACK_LOGICAL_ID}CloudMapNamespaceId`);
+ const paramVpcLinkId = parameters.get(`${NETWORK_STACK_LOGICAL_ID}VpcLinkId`);
+
+ const { UserPoolId: paramUserPoolId, AppClientIDWeb: paramAppClientIdWeb } = authParams;
+
+ const isAuthCondition = new cdk.CfnCondition(this, 'isAuthCondition', {
+ expression: cdk.Fn.conditionAnd(
+ cdk.Fn.conditionEquals(restrictAccess, true),
+ cdk.Fn.conditionNot(cdk.Fn.conditionEquals(paramUserPoolId ?? '', '')),
+ cdk.Fn.conditionNot(cdk.Fn.conditionEquals(paramAppClientIdWeb ?? '', '')),
+ ),
+ });
+
+ const stackNameParameter = new cdk.CfnParameter(this, 'rootStackName', {
+ type: 'String',
+ });
+
+ const deploymentBucketName = new cdk.CfnParameter(this, 'deploymentBucketName', {
+ type: 'String',
+ });
+ const awaiterS3Key = new cdk.CfnParameter(this, 'awaiterS3Key', {
+ type: 'String',
+ default: PIPELINE_AWAITER_ZIP,
+ });
+ return {
+ parameters,
+ vpcId: paramVpcId.valueAsString,
+ vpcCidrBlock: paramVpcCidrBlock.valueAsString,
+ subnets: paramSubnetIds.valueAsList,
+ clusterName: paramClusterName.valueAsString,
+ zipPath: paramZipPath.valueAsString,
+ cloudMapNamespaceId: paramCloudMapNamespaceId.valueAsString,
+ vpcLinkId: paramVpcLinkId.valueAsString,
+ isAuthCondition,
+ userPoolId: paramUserPoolId && paramUserPoolId.valueAsString,
+ appClientId: paramAppClientIdWeb && paramAppClientIdWeb.valueAsString,
+ envName: stackNameParameter.valueAsString,
+ deploymentBucketName: deploymentBucketName.valueAsString,
+ awaiterS3Key: awaiterS3Key.valueAsString,
+ };
+ }
+
+ private ecs() {
+ const {
+ categoryName,
+ apiName,
+ policies,
+ containers,
+ secretsArns,
+ taskEnvironmentVariables,
+ exposedContainer,
+ taskPorts,
+ isInitialDeploy,
+ desiredCount,
+ currentStackName,
+ createCloudMapService,
+ } = this.props;
+
+ let cloudMapService: cloudmap.CfnService = undefined;
+
+ if (createCloudMapService) {
+ cloudMapService = new cloudmap.CfnService(this, 'CloudmapService', {
+ name: apiName,
+ dnsConfig: {
+ dnsRecords: [
+ {
+ ttl: 60,
+ type: cloudmap.DnsRecordType.SRV,
+ },
+ ],
+ namespaceId: this.cloudMapNamespaceId,
+ routingPolicy: cloudmap.RoutingPolicy.MULTIVALUE,
+ },
+ });
+ }
+
+ const task = new ecs.TaskDefinition(this, 'TaskDefinition', {
+ compatibility: ecs.Compatibility.FARGATE,
+ memoryMiB: '1024',
+ cpu: '512',
+ family: `${this.envName}-${apiName}`,
+ });
+ (task.node.defaultChild as ecs.CfnTaskDefinition).overrideLogicalId('TaskDefinition');
+ policies.forEach(policy => {
+ const statement = isPolicyStatement(policy) ? policy : wrapJsonPoliciesInCdkPolicies(policy);
+
+ task.addToTaskRolePolicy(statement);
+ });
+
+ const containersInfo: {
+ container: ecs.ContainerDefinition;
+ repository: ecr.IRepository;
+ }[] = [];
+
+ containers.forEach(
+ ({
+ name,
+ image,
+ build,
+ portMappings,
+ logConfiguration,
+ environment,
+ entrypoint: entryPoint,
+ command,
+ working_dir: workingDirectory,
+ healthcheck: healthCheck,
+ secrets: containerSecrets,
+ }) => {
+ const logGroup = new logs.LogGroup(this, `${name}ContainerLogGroup`, {
+ logGroupName: `/ecs/${this.envName}-${apiName}-${name}`,
+ retention: logs.RetentionDays.ONE_MONTH,
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
+ });
+
+ const { logDriver, options: { 'awslogs-stream-prefix': streamPrefix } = {} } = logConfiguration;
+
+ const logging: ecs.LogDriver =
+ logDriver === 'awslogs'
+ ? ecs.LogDriver.awsLogs({
+ streamPrefix,
+ logGroup: logs.LogGroup.fromLogGroupName(this, `${name}logGroup`, logGroup.logGroupName),
+ })
+ : undefined;
+
+ let repository: ecr.IRepository;
+ if (build) {
+ const logicalId = `${name}Repository`;
+
+ const repositoryName = `${currentStackName}-${categoryName}-${apiName}-${name}`;
+
+ if (this.props.existingEcrRepositories.has(repositoryName)) {
+ repository = ecr.Repository.fromRepositoryName(this, logicalId, repositoryName);
+ } else {
+ repository = new ecr.Repository(this, logicalId, {
+ repositoryName: `${this.envName}-${categoryName}-${apiName}-${name}`,
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
+ lifecycleRules: [
+ {
+ rulePriority: 10,
+ maxImageCount: 1,
+ tagPrefixList: ['latest'],
+ tagStatus: ecr.TagStatus.TAGGED,
+ },
+ {
+ rulePriority: 100,
+ maxImageAge: cdk.Duration.days(7),
+ tagStatus: ecr.TagStatus.ANY,
+ },
+ ],
+ });
+ (repository.node.defaultChild as ecr.CfnRepository).overrideLogicalId(logicalId);
+ }
+
+ // Needed because the image will be pulled from ecr repository later
+ repository.grantPull(task.obtainExecutionRole());
+ }
+
+ const secrets: ecs.ContainerDefinitionOptions['secrets'] = {};
+ const environmentWithoutSecrets = environment || {};
+
+ containerSecrets.forEach((s, i) => {
+ if (secretsArns.has(s)) {
+ secrets[s] = ecs.Secret.fromSecretsManager(ssm.Secret.fromSecretPartialArn(this, `${name}secret${i + 1}`, secretsArns.get(s)));
+ }
+
+ delete environmentWithoutSecrets[s];
+ });
+
+ const container = task.addContainer(name, {
+ image: repository ? ecs.ContainerImage.fromEcrRepository(repository) : ecs.ContainerImage.fromRegistry(image),
+ logging,
+ environment: {
+ ...taskEnvironmentVariables,
+ ...environmentWithoutSecrets,
+ },
+ entryPoint,
+ command,
+ workingDirectory,
+ healthCheck: healthCheck && {
+ command: healthCheck.command,
+ interval: cdk.Duration.seconds(healthCheck.interval ?? 30),
+ retries: healthCheck.retries,
+ timeout: cdk.Duration.seconds(healthCheck.timeout ?? 5),
+ startPeriod: cdk.Duration.seconds(healthCheck.start_period ?? 0),
+ },
+ secrets,
+ });
+
+ containersInfo.push({
+ container,
+ repository,
+ });
+
+ // TODO: should we use hostPort too? check network mode
+ portMappings?.forEach(({ containerPort, protocol, hostPort }) => {
+ container.addPortMappings({
+ containerPort,
+ protocol: ecs.Protocol.TCP,
+ });
+ });
+ },
+ );
+
+ const serviceSecurityGroup = new ec2.CfnSecurityGroup(this, 'ServiceSG', {
+ vpcId: this.vpcId,
+ groupDescription: 'Service SecurityGroup',
+ securityGroupEgress: [
+ {
+ description: 'Allow all outbound traffic by default',
+ cidrIp: '0.0.0.0/0',
+ ipProtocol: '-1',
+ },
+ ],
+ securityGroupIngress: taskPorts.map(servicePort => ({
+ ipProtocol: 'tcp',
+ fromPort: servicePort,
+ toPort: servicePort,
+ cidrIp: this.vpcCidrBlock,
+ })),
+ });
+
+ let serviceRegistries: ecs.CfnService.ServiceRegistryProperty[] = undefined;
+
+ if (cloudMapService) {
+ serviceRegistries = [
+ {
+ containerName: exposedContainer.name,
+ containerPort: exposedContainer.port,
+ registryArn: cloudMapService.attrArn,
+ },
+ ];
+ }
+
+ const service = new ecs.CfnService(this, 'Service', {
+ serviceName: `${apiName}-service-${exposedContainer.name}-${exposedContainer.port}`,
+ cluster: this.clusterName,
+ launchType: 'FARGATE',
+ desiredCount: isInitialDeploy ? 0 : desiredCount, // This is later adjusted by the Predeploy action in the codepipeline
+ networkConfiguration: {
+ awsvpcConfiguration: {
+ assignPublicIp: 'ENABLED',
+ securityGroups: [serviceSecurityGroup.attrGroupId],
+ subnets: this.subnets,
+ },
+ },
+ taskDefinition: task.taskDefinitionArn,
+ serviceRegistries,
+ });
+
+ new cdk.CfnOutput(this, 'ServiceName', {
+ value: service.serviceName,
+ });
+
+ new cdk.CfnOutput(this, 'ClusterName', {
+ value: this.clusterName,
+ });
+
+ return {
+ service,
+ serviceSecurityGroup,
+ containersInfo,
+ cloudMapService,
+ };
+ }
+
+ private pipeline({
+ skipWait = false,
+ service,
+ containersInfo,
+ gitHubSourceActionInfo,
+ }: {
+ skipWait?: boolean;
+ service: ecs.CfnService;
+ containersInfo: {
+ container: ecs.ContainerDefinition;
+ repository: ecr.IRepository;
+ }[];
+ gitHubSourceActionInfo?: GitHubSourceActionInfo;
+ }) {
+ const { deploymentMechanism, desiredCount } = this.props;
+
+ const s3SourceActionKey = this.zipPath;
+
+ const bucket = s3.Bucket.fromBucketName(this, 'Bucket', this.deploymentBucketName);
+
+ const pipelineWithAwaiter = new PipelineWithAwaiter(this, 'ApiPipeline', {
+ skipWait,
+ envName: this.envName,
+ containersInfo,
+ service,
+ bucket,
+ s3SourceActionKey,
+ deploymentMechanism,
+ gitHubSourceActionInfo,
+ desiredCount,
+ });
+
+ pipelineWithAwaiter.node.addDependency(service);
+
+ return { pipelineWithAwaiter };
+ }
+
+ protected getPipelineName() {
+ return this.pipelineWithAwaiter.getPipelineName();
+ }
+
+ getPipelineConsoleUrl(region: string) {
+ const pipelineName = this.getPipelineName();
+ return `https://${region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${pipelineName}/view`;
+ }
+ toCloudFormation() {
+ this.node
+ .findAll()
+ .filter(construct => construct instanceof CfnFunction)
+ .map(construct => construct as CfnFunction)
+ .forEach(lambdaFunction => {
+ if (lambdaFunction.logicalId.includes('AwaiterMyProvider')) {
+ lambdaFunction.code = {
+ s3Bucket: this.deploymentBucketName,
+ s3Key: this.awaiterS3Key,
+ };
+ }
+ });
+
+ prepareApp(this);
+
+ const cfn = this._toCloudFormation();
+
+ Object.keys(cfn.Parameters).forEach(k => {
+ if (k.startsWith('AssetParameters')) {
+ delete cfn.Parameters[k];
+ }
+ });
+ return cfn;
+ }
+}
+
+/**
+ * Wraps an array of JSON IAM statements in a {iam.PolicyStatement} array.
+ * This allow us tu pass the statements in a way that CDK can use when synthesizing
+ *
+ * CDK looks for a toStatementJson function
+ *
+ * @param policy JSON object with IAM statements
+ * @returns {iam.PolicyStatement} CDK compatible policy statement
+ */
+function wrapJsonPoliciesInCdkPolicies(policy: Record): iam.PolicyStatement {
+ return {
+ toStatementJson() {
+ return policy;
+ },
+ } as iam.PolicyStatement;
+}
+
+function isPolicyStatement(obj: any): obj is iam.PolicyStatement {
+ if (obj && typeof (obj).toStatementJson === 'function') {
+ return true;
+ }
+
+ return false;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-builder.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-builder.ts
new file mode 100644
index 0000000000..305d8405c7
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-builder.ts
@@ -0,0 +1,516 @@
+import * as apigw from '@aws-cdk/aws-apigateway';
+import * as iam from '@aws-cdk/aws-iam';
+import * as lambda from '@aws-cdk/aws-lambda';
+import * as cdk from '@aws-cdk/core';
+import { $TSObject, JSONUtilities } from 'amplify-cli-core';
+import _ from 'lodash';
+import { v4 as uuid } from 'uuid';
+import { ADMIN_QUERIES_NAME } from '../../../category-constants';
+import { AmplifyApigwResourceTemplate, ApigwInputs, ApigwPathPolicy, Path, PermissionSetting } from './types';
+
+const CFN_TEMPLATE_FORMAT_VERSION = '2010-09-09';
+const ROOT_CFN_DESCRIPTION = 'API Gateway Resource for AWS Amplify CLI';
+
+export class AmplifyApigwResourceStack extends cdk.Stack implements AmplifyApigwResourceTemplate {
+ restApi: apigw.CfnRestApi;
+ deploymentResource: apigw.CfnDeployment;
+ paths: $TSObject;
+ policies: { [pathName: string]: ApigwPathPolicy };
+ private _scope: cdk.Construct;
+ private _props: ApigwInputs;
+ private _cfnParameterMap: Map = new Map();
+ private _cfnParameterValues: $TSObject;
+ private _seenLogicalIds: Set;
+
+ constructor(scope: cdk.Construct, id: string, props: ApigwInputs) {
+ super(scope, id, undefined);
+ this._scope = scope;
+ this._props = props;
+ this.paths = {};
+ this._seenLogicalIds = new Set();
+ this._cfnParameterValues = {};
+ this.policies = {};
+ this.templateOptions.templateFormatVersion = CFN_TEMPLATE_FORMAT_VERSION;
+ this.templateOptions.description = ROOT_CFN_DESCRIPTION;
+ }
+
+ /**
+ *
+ * @param props
+ * @param logicalId
+ */
+ addCfnOutput(props: cdk.CfnOutputProps, logicalId: string): void {
+ this.validateLogicalId(logicalId);
+ new cdk.CfnOutput(this, logicalId, props);
+ }
+
+ /**
+ *
+ * @param props
+ * @param logicalId
+ */
+ addCfnMapping(props: cdk.CfnMappingProps, logicalId: string): void {
+ this.validateLogicalId(logicalId);
+ new cdk.CfnMapping(this, logicalId, props);
+ }
+
+ /**
+ *
+ * @param props
+ * @param logicalId
+ */
+ addCfnCondition(props: cdk.CfnConditionProps, logicalId: string): void {
+ this.validateLogicalId(logicalId);
+ new cdk.CfnCondition(this, logicalId, props);
+ }
+
+ /**
+ *
+ * @param props
+ * @param logicalId
+ */
+ addCfnResource(props: cdk.CfnResourceProps, logicalId: string): void {
+ this.validateLogicalId(logicalId);
+ new cdk.CfnResource(this, logicalId, props);
+ }
+
+ /**
+ *
+ * @param props
+ * @param logicalId
+ */
+ addCfnLambdaPermissionResource(props: lambda.CfnPermissionProps, logicalId: string): void {
+ this.validateLogicalId(logicalId);
+ new lambda.CfnPermission(this, logicalId, props);
+ }
+
+ /**
+ *
+ * @param props
+ * @param logicalId
+ * @param value optional value which will be stored in parameters.json
+ */
+ addCfnParameter(props: cdk.CfnParameterProps, logicalId: string, value?: string | $TSObject): void {
+ this.validateLogicalId(logicalId);
+ this._cfnParameterMap.set(logicalId, new cdk.CfnParameter(this, logicalId, props));
+ if (value !== undefined) {
+ this._cfnParameterValues[logicalId] = value;
+ }
+ }
+
+ getCfnParameterValues() {
+ return this._cfnParameterValues;
+ }
+
+ private validateLogicalId(logicalId: string): void {
+ if (this._seenLogicalIds.has(logicalId)) {
+ throw new Error(`logical id "${logicalId}" already exists`);
+ }
+ this._seenLogicalIds.add(logicalId);
+ }
+
+ private _craftPolicyDocument(apiResourceName: string, pathName: string, supportedOperations: string[]) {
+ const paths = [pathName, appendToUrlPath(pathName, '*')];
+ const resources = paths.flatMap(path =>
+ supportedOperations.map(op =>
+ cdk.Fn.join('', [
+ 'arn:aws:execute-api:',
+ cdk.Fn.ref('AWS::Region'),
+ ':',
+ cdk.Fn.ref('AWS::AccountId'),
+ ':',
+ cdk.Fn.ref(apiResourceName),
+ '/',
+ cdk.Fn.conditionIf('ShouldNotCreateEnvResources', 'Prod', cdk.Fn.ref('env')).toString(),
+ op,
+ path,
+ ]),
+ ),
+ );
+
+ return new iam.PolicyDocument({
+ statements: [
+ new iam.PolicyStatement({
+ actions: ['execute-api:Invoke'],
+ effect: iam.Effect.ALLOW,
+ resources,
+ }),
+ ],
+ });
+ }
+
+ addIamPolicyResourceForUserPoolGroup(
+ apiResourceName: string,
+ authRoleLogicalId: string,
+ groupName: string,
+ pathName: string,
+ supportedOperations: string[],
+ ): void {
+ const alphanumericPathName = pathName.replace(/[^-a-z0-9]/g, '');
+
+ const policyName = [apiResourceName, alphanumericPathName, groupName, 'group', 'policy'].join('-');
+
+ const iamPolicy = new iam.CfnPolicy(this, `${groupName}Group${alphanumericPathName}Policy`, {
+ policyDocument: this._craftPolicyDocument(apiResourceName, pathName, supportedOperations),
+ policyName,
+ roles: [cdk.Fn.join('-', [cdk.Fn.ref(authRoleLogicalId), `${groupName}GroupRole`])],
+ });
+ _.set(this.policies, [pathName, 'groups', groupName], iamPolicy);
+ }
+
+ renderCloudFormationTemplate = (): string => {
+ return JSONUtilities.stringify(this._toCloudFormation());
+ };
+
+ generateAdminQueriesStack = (resourceName: string, authResourceName: string) => {
+ this._constructCfnPaths(resourceName);
+
+ this.restApi = new apigw.CfnRestApi(this, resourceName, {
+ description: '',
+ name: resourceName,
+ body: {
+ swagger: '2.0',
+ info: {
+ version: '2018-05-24T17:52:00Z',
+ title: resourceName,
+ },
+ host: cdk.Fn.join('', ['apigateway.', cdk.Fn.ref('AWS::Region'), '.amazonaws.com']),
+ basePath: cdk.Fn.conditionIf('ShouldNotCreateEnvResources', '/Prod', cdk.Fn.join('', ['/', cdk.Fn.ref('env')])),
+ schemes: ['https'],
+ paths: this.paths,
+ securityDefinitions: {
+ Cognito: {
+ type: 'apiKey',
+ name: 'Authorization',
+ in: 'header',
+ 'x-amazon-apigateway-authtype': 'cognito_user_pools',
+ 'x-amazon-apigateway-authorizer': {
+ providerARNs: [
+ cdk.Fn.join('', [
+ 'arn:aws:cognito-idp:',
+ cdk.Fn.ref('AWS::Region'),
+ ':',
+ cdk.Fn.ref('AWS::AccountId'),
+ ':userpool/',
+ cdk.Fn.ref(`auth${authResourceName}UserPoolId`),
+ ]),
+ ],
+ type: 'cognito_user_pools',
+ },
+ },
+ },
+ definitions: {
+ Empty: {
+ type: 'object',
+ title: 'Empty Schema',
+ },
+ },
+ 'x-amazon-apigateway-request-validators': {
+ 'Validate query string parameters and headers': {
+ validateRequestParameters: true,
+ validateRequestBody: false,
+ },
+ },
+ },
+ });
+
+ this._setDeploymentResource(resourceName);
+ };
+
+ generateStackResources = (resourceName: string) => {
+ this._constructCfnPaths(resourceName);
+
+ this.restApi = new apigw.CfnRestApi(this, resourceName, {
+ description: '',
+ failOnWarnings: true,
+ name: resourceName,
+ body: {
+ swagger: '2.0',
+ info: {
+ version: '2018-05-24T17:52:00Z',
+ title: resourceName,
+ },
+ host: cdk.Fn.join('', ['apigateway.', cdk.Fn.ref('AWS::Region'), '.amazonaws.com']),
+ basePath: cdk.Fn.conditionIf('ShouldNotCreateEnvResources', '/Prod', cdk.Fn.join('', ['/', cdk.Fn.ref('env')])),
+ schemes: ['https'],
+ paths: this.paths,
+ securityDefinitions: {
+ sigv4: {
+ type: 'apiKey',
+ name: 'Authorization',
+ in: 'header',
+ 'x-amazon-apigateway-authtype': 'awsSigv4',
+ },
+ },
+ definitions: {
+ RequestSchema: {
+ type: 'object',
+ required: ['request'],
+ properties: {
+ request: {
+ type: 'string',
+ },
+ },
+ title: 'Request Schema',
+ },
+ ResponseSchema: {
+ type: 'object',
+ required: ['response'],
+ properties: {
+ response: {
+ type: 'string',
+ },
+ },
+ title: 'Response Schema',
+ },
+ },
+ },
+ });
+ new apigw.CfnGatewayResponse(this, `${resourceName}Default4XXResponse`, {
+ responseType: 'DEFAULT_4XX',
+ restApiId: cdk.Fn.ref(resourceName),
+ responseParameters: defaultCorsGatewayResponseParams,
+ });
+ new apigw.CfnGatewayResponse(this, `${resourceName}Default5XXResponse`, {
+ responseType: 'DEFAULT_5XX',
+ restApiId: cdk.Fn.ref(resourceName),
+ responseParameters: defaultCorsGatewayResponseParams,
+ });
+
+ this._setDeploymentResource(resourceName);
+ };
+
+ private _constructCfnPaths(resourceName: string) {
+ const addedFunctionPermissions = new Set();
+ for (const [pathName, path] of Object.entries(this._props.paths)) {
+ let lambdaPermissionLogicalId: string;
+ if (resourceName === ADMIN_QUERIES_NAME) {
+ this.paths[`/{proxy+}`] = getAdminQueriesPathObject(path.lambdaFunction);
+ lambdaPermissionLogicalId = `${ADMIN_QUERIES_NAME}APIGWPolicyForLambda`;
+ } else {
+ this.paths[pathName] = createPathObject(path);
+ this.paths[appendToUrlPath(pathName, '{proxy+}')] = createPathObject(path);
+ lambdaPermissionLogicalId = `function${path.lambdaFunction}Permission${resourceName}`;
+ }
+
+ if (!addedFunctionPermissions.has(path.lambdaFunction)) {
+ addedFunctionPermissions.add(path.lambdaFunction);
+ this.addCfnLambdaPermissionResource(
+ {
+ functionName: cdk.Fn.ref(`function${path.lambdaFunction}Name`),
+ action: 'lambda:InvokeFunction',
+ principal: 'apigateway.amazonaws.com',
+ sourceArn: cdk.Fn.join('', [
+ 'arn:aws:execute-api:',
+ cdk.Fn.ref('AWS::Region'),
+ ':',
+ cdk.Fn.ref('AWS::AccountId'),
+ ':',
+ cdk.Fn.ref(resourceName),
+ '/*/*/*',
+ ]),
+ },
+ lambdaPermissionLogicalId,
+ );
+ }
+ }
+ }
+
+ private _setDeploymentResource = (resourceName: string) => {
+ const [shortId] = uuid().split('-');
+ this.deploymentResource = new apigw.CfnDeployment(this, `DeploymentAPIGW${resourceName}${shortId}`, {
+ description: 'The Development stage deployment of your API.',
+ stageName: cdk.Fn.conditionIf('ShouldNotCreateEnvResources', 'Prod', cdk.Fn.ref('env')).toString(),
+ restApiId: cdk.Fn.ref(resourceName),
+ });
+ };
+}
+
+const appendToUrlPath = (path: string, postfix: string) => {
+ return path.charAt(path.length - 1) === '/' ? `${path}${postfix}` : `${path}/${postfix}`;
+};
+
+const getAdminQueriesPathObject = (lambdaFunctionName: string) => ({
+ options: {
+ consumes: ['application/json'],
+ produces: ['application/json'],
+ responses: {
+ '200': {
+ description: '200 response',
+ schema: {
+ $ref: '#/definitions/Empty',
+ },
+ headers: {
+ 'Access-Control-Allow-Origin': {
+ type: 'string',
+ },
+ 'Access-Control-Allow-Methods': {
+ type: 'string',
+ },
+ 'Access-Control-Allow-Headers': {
+ type: 'string',
+ },
+ },
+ },
+ },
+ 'x-amazon-apigateway-integration': {
+ responses: {
+ default: {
+ statusCode: '200',
+ responseParameters: {
+ 'method.response.header.Access-Control-Allow-Methods': "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'",
+ 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
+ 'method.response.header.Access-Control-Allow-Origin': "'*'",
+ },
+ },
+ },
+ passthroughBehavior: 'when_no_match',
+ requestTemplates: {
+ 'application/json': '{"statusCode": 200}',
+ },
+ type: 'mock',
+ },
+ },
+ 'x-amazon-apigateway-any-method': {
+ produces: ['application/json'],
+ parameters: [
+ {
+ name: 'proxy',
+ in: 'path',
+ required: true,
+ type: 'string',
+ },
+ {
+ name: 'Authorization',
+ in: 'header',
+ required: false,
+ type: 'string',
+ },
+ ],
+ responses: {},
+ security: [
+ {
+ Cognito: ['aws.cognito.signin.user.admin'],
+ },
+ ],
+ 'x-amazon-apigateway-request-validator': 'Validate query string parameters and headers',
+ 'x-amazon-apigateway-integration': {
+ uri: cdk.Fn.join('', [
+ 'arn:aws:apigateway:',
+ cdk.Fn.ref('AWS::Region'),
+ ':lambda:path/2015-03-31/functions/',
+ cdk.Fn.ref(`function${lambdaFunctionName}Arn`),
+ '/invocations',
+ ]),
+ passthroughBehavior: 'when_no_match',
+ httpMethod: 'POST',
+ cacheNamespace: 'n40eb9',
+ cacheKeyParameters: ['method.request.path.proxy'],
+ contentHandling: 'CONVERT_TO_TEXT',
+ type: 'aws_proxy',
+ },
+ },
+});
+
+const createPathObject = (path: Path) => {
+ const defaultPathObject: $TSObject = {
+ options: {
+ consumes: ['application/json'],
+ produces: ['application/json'],
+ responses: {
+ '200': response200,
+ },
+ 'x-amazon-apigateway-integration': {
+ responses: {
+ default: defaultCorsResponseObject,
+ },
+ requestTemplates: {
+ 'application/json': '{"statusCode": 200}',
+ },
+ passthroughBehavior: 'when_no_match',
+ type: 'mock',
+ },
+ },
+ 'x-amazon-apigateway-any-method': {
+ consumes: ['application/json'],
+ produces: ['application/json'],
+ parameters: [
+ {
+ in: 'body',
+ name: 'RequestSchema',
+ required: false,
+ schema: {
+ $ref: '#/definitions/RequestSchema',
+ },
+ },
+ ],
+ responses: {
+ '200': {
+ description: '200 response',
+ schema: {
+ $ref: '#/definitions/ResponseSchema',
+ },
+ },
+ },
+ 'x-amazon-apigateway-integration': {
+ responses: {
+ default: {
+ statusCode: '200',
+ },
+ },
+ uri: cdk.Fn.join('', [
+ 'arn:aws:apigateway:',
+ cdk.Fn.ref('AWS::Region'),
+ ':lambda:path/2015-03-31/functions/',
+ cdk.Fn.ref(`function${path.lambdaFunction}Arn`),
+ '/invocations',
+ ]),
+ passthroughBehavior: 'when_no_match',
+ httpMethod: 'POST',
+ type: 'aws_proxy',
+ },
+ },
+ };
+
+ if (path.permissions.setting !== PermissionSetting.OPEN) {
+ defaultPathObject['x-amazon-apigateway-any-method'].security = [
+ {
+ sigv4: [],
+ },
+ ];
+ }
+
+ return defaultPathObject;
+};
+
+const defaultCorsResponseObject = {
+ statusCode: '200',
+ responseParameters: {
+ 'method.response.header.Access-Control-Allow-Methods': "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'",
+ 'method.response.header.Access-Control-Allow-Headers':
+ "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
+ 'method.response.header.Access-Control-Allow-Origin': "'*'",
+ },
+};
+
+const defaultCorsGatewayResponseParams = {
+ 'gatewayresponse.header.Access-Control-Allow-Origin': "'*'",
+ 'gatewayresponse.header.Access-Control-Allow-Headers': "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
+ 'gatewayresponse.header.Access-Control-Allow-Methods': "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'",
+ 'gatewayresponse.header.Access-Control-Expose-Headers': "'Date,X-Amzn-ErrorType'",
+};
+
+const response200 = {
+ description: '200 response',
+ headers: {
+ 'Access-Control-Allow-Origin': {
+ type: 'string',
+ },
+ 'Access-Control-Allow-Methods': {
+ type: 'string',
+ },
+ 'Access-Control-Allow-Headers': {
+ type: 'string',
+ },
+ },
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-transform.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-transform.ts
new file mode 100644
index 0000000000..ed58328806
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/apigw-stack-transform.ts
@@ -0,0 +1,252 @@
+import * as cdk from '@aws-cdk/core';
+import {
+ $TSContext,
+ $TSObject,
+ AmplifyCategories,
+ buildOverrideDir,
+ getAmplifyResourceByCategories,
+ JSONUtilities,
+ PathConstants,
+ pathManager,
+ stateManager,
+ Template,
+ writeCFNTemplate,
+} from 'amplify-cli-core';
+import { formatter, printer } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import * as vm from 'vm2';
+import { AmplifyApigwResourceStack, ApigwInputs, CrudOperation, Path } from '.';
+import { ApigwInputState } from '../apigw-input-state';
+import { ADMIN_QUERIES_NAME } from '../../../category-constants';
+export class ApigwStackTransform {
+ cliInputs: ApigwInputs;
+ resourceTemplateObj: AmplifyApigwResourceStack | undefined;
+ cliInputsState: ApigwInputState;
+ cfn: Template;
+ cfnInputParams: $TSObject;
+ resourceName: string;
+ private _app: cdk.App;
+
+ constructor(context: $TSContext, resourceName: string, cliInputState?: ApigwInputState) {
+ this._app = new cdk.App();
+ this.resourceName = resourceName;
+
+ // Validate the cli-inputs.json for the resource
+ this.cliInputsState = cliInputState ?? new ApigwInputState(context, resourceName);
+ this.cliInputs = this.cliInputsState.getCliInputPayload();
+ this.cliInputsState.isCLIInputsValid();
+ }
+
+ async transform() {
+ let authResourceName: string;
+
+ const pathsWithUserPoolGroups = Object.entries(this.cliInputs.paths).filter(([_, path]) => !!path?.permissions?.groups);
+
+ if (this.resourceName === ADMIN_QUERIES_NAME || pathsWithUserPoolGroups.length > 0) {
+ [authResourceName] = getAmplifyResourceByCategories(AmplifyCategories.AUTH).filter(resourceName => resourceName !== 'userPoolGroups');
+ }
+
+ // Generate cloudformation stack from cli-inputs.json
+ this.generateStack(authResourceName, pathsWithUserPoolGroups);
+
+ try {
+ // Modify cloudformation files based on overrides
+ await this.applyOverrides();
+ } catch (error) {
+ printer.error(`Failed to override ${this.resourceName} due to: ${error}.`);
+ return;
+ }
+
+ // Generate cloudformation stack input params from cli-inputs.json
+ this.generateCfnInputParameters();
+
+ // Save generated cloudformation.json and parameters.json files
+ await this.saveBuildFiles();
+ }
+
+ generateCfnInputParameters() {
+ this.cfnInputParams = this.resourceTemplateObj.getCfnParameterValues();
+ }
+
+ generateStack(authResourceName?: string, pathsWithUserPoolGroups: [string, Path][] = []) {
+ this.resourceTemplateObj = new AmplifyApigwResourceStack(this._app, 'AmplifyApigwResourceStack', this.cliInputs);
+
+ if (authResourceName) {
+ const authRoleLogicalId = `auth${authResourceName}UserPoolId`;
+ this.resourceTemplateObj.addCfnParameter(
+ {
+ type: 'String',
+ default: authRoleLogicalId,
+ },
+ authRoleLogicalId,
+ );
+
+ const uniqueUserPoolGroupsList = new Set();
+ for (const [pathName, path] of pathsWithUserPoolGroups) {
+ for (const [groupName, crudOps] of Object.entries(path.permissions.groups)) {
+ uniqueUserPoolGroupsList.add(groupName);
+ this.resourceTemplateObj.addIamPolicyResourceForUserPoolGroup(
+ this.resourceName,
+ authRoleLogicalId,
+ groupName,
+ pathName,
+ convertCrudOperationsToCfnPermissions(crudOps),
+ );
+ }
+ }
+ Array.from(uniqueUserPoolGroupsList).forEach(userPoolGroupName => {
+ this.resourceTemplateObj.addCfnParameter(
+ {
+ type: 'String',
+ default: `authuserPoolGroups${userPoolGroupName}GroupRole`,
+ },
+ `authuserPoolGroups${userPoolGroupName}GroupRole`,
+ );
+ });
+ }
+
+ // Add Parameters
+ const addedFunctions = new Set();
+ for (const path of Object.values(this.cliInputs.paths)) {
+ if (!addedFunctions.has(path.lambdaFunction)) {
+ addedFunctions.add(path.lambdaFunction);
+ this.resourceTemplateObj.addCfnParameter(
+ {
+ type: 'String',
+ default: `function${path.lambdaFunction}Name`,
+ },
+ `function${path.lambdaFunction}Name`,
+ );
+ this.resourceTemplateObj.addCfnParameter(
+ {
+ type: 'String',
+ default: `function${path.lambdaFunction}Arn`,
+ },
+ `function${path.lambdaFunction}Arn`,
+ );
+ }
+ }
+
+ this.resourceTemplateObj.addCfnParameter(
+ {
+ type: 'String',
+ },
+ 'env',
+ );
+
+ // Add conditions
+ this.resourceTemplateObj.addCfnCondition(
+ {
+ expression: cdk.Fn.conditionEquals(cdk.Fn.ref('env'), 'NONE'),
+ },
+ 'ShouldNotCreateEnvResources',
+ );
+
+ // Add outputs
+ this.resourceTemplateObj.addCfnOutput(
+ {
+ value: cdk.Fn.join('', [
+ 'https://',
+ cdk.Fn.ref(this.cliInputsState.resourceName),
+ '.execute-api.',
+ cdk.Fn.ref('AWS::Region'),
+ '.amazonaws.com/',
+ cdk.Fn.conditionIf('ShouldNotCreateEnvResources', 'Prod', cdk.Fn.ref('env')) as unknown as string,
+ ]),
+ description: 'Root URL of the API gateway',
+ },
+ 'RootUrl',
+ );
+
+ this.resourceTemplateObj.addCfnOutput(
+ {
+ value: this.resourceName,
+ description: 'API Friendly name',
+ },
+ 'ApiName',
+ );
+
+ this.resourceTemplateObj.addCfnOutput(
+ {
+ value: cdk.Fn.ref(this.resourceName),
+ description: 'API ID (prefix of API URL)',
+ },
+ 'ApiId',
+ );
+
+ // Add resources
+ this.resourceName === ADMIN_QUERIES_NAME
+ ? this.resourceTemplateObj.generateAdminQueriesStack(this.resourceName, authResourceName)
+ : this.resourceTemplateObj.generateStackResources(this.resourceName);
+ }
+
+ async applyOverrides() {
+ const backendDir = pathManager.getBackendDirPath();
+ const overrideFilePath = pathManager.getResourceDirectoryPath(undefined, AmplifyCategories.API, this.resourceName);
+ const overrideJSFilePath = path.join(overrideFilePath, 'build', 'override.js');
+
+ const isBuild = await buildOverrideDir(backendDir, overrideFilePath);
+
+ // skip if packageManager or override.ts not found
+ if (isBuild) {
+ let override;
+ try {
+ ({ override } = await import(overrideJSFilePath));
+ } catch {
+ formatter.list(['No override file found', `To override ${this.resourceName} run "amplify override api"`]);
+ override = undefined;
+ }
+
+ if (override && typeof override === 'function') {
+ let overrideCode: string;
+ try {
+ overrideCode = await fs.readFile(overrideJSFilePath, 'utf-8');
+ } catch (error) {
+ formatter.list(['No override file found', `To override ${this.resourceName} run amplify override api`]);
+ return;
+ }
+
+ const sandboxNode = new vm.NodeVM({
+ console: 'inherit',
+ timeout: 5000,
+ sandbox: {},
+ require: {
+ context: 'sandbox',
+ builtin: ['path'],
+ external: true,
+ },
+ });
+
+ await sandboxNode.run(overrideCode, overrideJSFilePath).override(this.resourceTemplateObj as AmplifyApigwResourceStack);
+ }
+ }
+ }
+
+ private async saveBuildFiles() {
+ if (this.resourceTemplateObj) {
+ this.cfn = JSONUtilities.parse(this.resourceTemplateObj.renderCloudFormationTemplate());
+ }
+
+ const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, AmplifyCategories.API, this.resourceName);
+ fs.ensureDirSync(resourceDirPath);
+
+ const buildDirPath = path.join(resourceDirPath, PathConstants.BuildDirName);
+ fs.ensureDirSync(buildDirPath);
+
+ stateManager.setResourceParametersJson(undefined, AmplifyCategories.API, this.resourceName, this.cfnInputParams);
+
+ const cfnFilePath = path.resolve(path.join(buildDirPath, `${this.resourceName}-cloudformation-template.json`));
+ return writeCFNTemplate(this.cfn, cfnFilePath);
+ }
+}
+
+function convertCrudOperationsToCfnPermissions(crudOps: CrudOperation[]) {
+ const opMap: Record = {
+ [CrudOperation.CREATE]: ['/POST'],
+ [CrudOperation.READ]: ['/GET'],
+ [CrudOperation.UPDATE]: ['/PUT', '/PATCH'],
+ [CrudOperation.DELETE]: ['/DELETE'],
+ };
+ return crudOps.flatMap(op => opMap[op]);
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/index.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/index.ts
new file mode 100644
index 0000000000..7b96c26fd5
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/index.ts
@@ -0,0 +1,3 @@
+export * from './apigw-stack-builder';
+export * from './apigw-stack-transform';
+export * from './types';
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/types.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/types.ts
new file mode 100644
index 0000000000..ffd5d459e7
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/cdk-stack-builder/types.ts
@@ -0,0 +1,53 @@
+import * as cdk from '@aws-cdk/core';
+import * as apigwCdk from '@aws-cdk/aws-apigateway';
+import * as iamCdk from '@aws-cdk/aws-iam';
+
+export type ApigwInputs = {
+ version: number;
+ paths: { [pathName: string]: Path };
+};
+
+export type Path = {
+ lambdaFunction: string;
+ permissions: {
+ setting: PermissionSetting;
+ auth?: CrudOperation[];
+ guest?: CrudOperation[];
+ groups?: { [groupName: string]: CrudOperation[] };
+ };
+};
+
+export enum CrudOperation {
+ CREATE = 'create',
+ READ = 'read',
+ UPDATE = 'update',
+ DELETE = 'delete',
+}
+
+export enum PermissionSetting {
+ PRIVATE = 'private',
+ PROTECTED = 'protected',
+ OPEN = 'open',
+}
+
+type AmplifyCDKL1 = {
+ addCfnCondition(props: cdk.CfnConditionProps, logicalId: string): void;
+ addCfnMapping(props: cdk.CfnMappingProps, logicalId: string): void;
+ addCfnOutput(props: cdk.CfnOutputProps, logicalId: string): void;
+ addCfnParameter(props: cdk.CfnParameterProps, logicalId: string, value?: any): void;
+ addCfnResource(props: cdk.CfnResourceProps, logicalId: string): void;
+};
+
+export type AmplifyApigwResourceTemplate = {
+ restApi: apigwCdk.CfnRestApi;
+ deploymentResource: apigwCdk.CfnDeployment;
+ policies?: {
+ [pathName: string]: ApigwPathPolicy;
+ };
+} & AmplifyCDKL1;
+
+export type ApigwPathPolicy = {
+ auth?: iamCdk.CfnPolicy;
+ guest?: iamCdk.CfnPolicy;
+ groups?: { [groupName: string]: iamCdk.CfnPolicy };
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts
new file mode 100644
index 0000000000..e62b084113
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/cfn-api-artifact-handler.ts
@@ -0,0 +1,423 @@
+import {
+ $TSAny, $TSContext, isResourceNameUnique, JSONUtilities, pathManager, stateManager,
+} from 'amplify-cli-core';
+import {
+ AddApiRequest,
+ AppSyncServiceConfiguration,
+ AppSyncServiceModification,
+ ConflictResolution,
+ ResolutionStrategy,
+ UpdateApiRequest,
+} from 'amplify-headless-interface';
+import { printer } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import { readTransformerConfiguration, TRANSFORM_CURRENT_VERSION, writeTransformerConfiguration } from 'graphql-transformer-core';
+import _ from 'lodash';
+import * as path from 'path';
+import { v4 as uuid } from 'uuid';
+import { category } from '../../category-constants';
+import { ApiArtifactHandler, ApiArtifactHandlerOptions } from '../api-artifact-handler';
+import { AppsyncApiInputState } from './api-input-manager/appsync-api-input-state';
+import {
+ cfnParametersFilename, gqlSchemaFilename, provider, rootAssetDir,
+} from './aws-constants';
+import { AppSyncCLIInputs, AppSyncServiceConfig } from './service-walkthrough-types/appsync-user-input-types';
+import {
+ authConfigHasApiKey, checkIfAuthExists, getAppSyncAuthConfig, getAppSyncResourceName,
+} from './utils/amplify-meta-utils';
+import { appSyncAuthTypeToAuthConfig } from './utils/auth-config-to-app-sync-auth-type-bi-di-mapper';
+import { printApiKeyWarnings } from './utils/print-api-key-warnings';
+import { conflictResolutionToResolverConfig } from './utils/resolver-config-to-conflict-resolution-bi-di-mapper';
+
+// keep in sync with ServiceName in amplify-category-function, but probably it will not change
+const FunctionServiceNameLambdaFunction = 'Lambda';
+
+/**
+ * Factory function that returns an ApiArtifactHandler instance
+ */
+export const getCfnApiArtifactHandler = (context: $TSContext): ApiArtifactHandler => new CfnApiArtifactHandler(context);
+
+const resolversDirName = 'resolvers';
+const stacksDirName = 'stacks';
+const defaultStackName = 'CustomResources.json';
+
+// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+const defaultCfnParameters = (apiName: string) => ({
+ AppSyncApiName: apiName,
+ DynamoDBBillingMode: 'PAY_PER_REQUEST',
+ DynamoDBEnableServerSideEncryption: false,
+});
+class CfnApiArtifactHandler implements ApiArtifactHandler {
+ private readonly context: $TSContext;
+
+ constructor(context: $TSContext) {
+ this.context = context;
+ }
+
+ // TODO once the AddApiRequest contains multiple services this class should depend on an ApiArtifactHandler
+ // for each service and delegate to the correct one
+ createArtifacts = async (request: AddApiRequest): Promise => {
+ const meta = stateManager.getMeta();
+ const existingApiName = getAppSyncResourceName(meta);
+ if (existingApiName) {
+ throw new Error(`GraphQL API ${existingApiName} already exists in the project. Use 'amplify update api' to make modifications.`);
+ }
+ const serviceConfig = request.serviceConfiguration;
+
+ isResourceNameUnique('api', serviceConfig.apiName);
+
+ const resourceDir = this.getResourceDir(serviceConfig.apiName);
+
+ // Ensure the project directory exists and create the stacks & resolvers directories.
+ fs.ensureDirSync(resourceDir);
+ const resolverDirectoryPath = path.join(resourceDir, resolversDirName);
+ if (!fs.existsSync(resolverDirectoryPath)) {
+ fs.mkdirSync(resolverDirectoryPath);
+ }
+ const stacksDirectoryPath = path.join(resourceDir, stacksDirName);
+ if (!fs.existsSync(stacksDirectoryPath)) {
+ fs.mkdirSync(stacksDirectoryPath);
+ fs.copyFileSync(path.join(rootAssetDir, 'resolver-readme', 'RESOLVER_README.md'), path.join(resolverDirectoryPath, 'README.md'));
+ }
+
+ // During API add, make sure we're creating a transform.conf.json file with the latest version the CLI supports.
+ await this.updateTransformerConfigVersion(resourceDir);
+
+ serviceConfig.conflictResolution = await this.createResolverResources(serviceConfig.conflictResolution);
+ await writeResolverConfig(serviceConfig.conflictResolution, resourceDir);
+
+ const appsyncCLIInputs = await this.generateAppsyncCLIInputs(serviceConfig, resourceDir);
+
+ // Write the default custom resources stack out to disk.
+ fs.copyFileSync(
+ path.join(rootAssetDir, 'cloudformation-templates', 'defaultCustomResources.json'),
+ path.join(resourceDir, stacksDirName, defaultStackName),
+ );
+
+ // write the template buffer to the project folder
+ this.writeSchema(appsyncCLIInputs.serviceConfiguration.gqlSchemaPath, serviceConfig.transformSchema);
+
+ const authConfig = this.extractAuthConfig(appsyncCLIInputs.serviceConfiguration);
+
+ await this.context.amplify.executeProviderUtils(this.context, 'awscloudformation', 'compileSchema', {
+ resourceDir,
+ parameters: this.getCfnParameters(serviceConfig.apiName, authConfig, resourceDir),
+ authConfig,
+ });
+
+ const dependsOn = amendDependsOnForAuthConfig([], authConfig);
+
+ this.context.amplify.updateamplifyMetaAfterResourceAdd(category, serviceConfig.apiName, this.createAmplifyMeta(authConfig, dependsOn));
+ return serviceConfig.apiName;
+ };
+
+ // TODO once the AddApiRequest contains multiple services this class should depend on an ApiArtifactHandler
+ // for each service and delegate to the correct one
+ updateArtifacts = async (request: UpdateApiRequest, opts?: ApiArtifactHandlerOptions): Promise => {
+ const updates = request.serviceModification;
+ const apiName = getAppSyncResourceName(stateManager.getMeta());
+ if (!apiName) {
+ throw new Error('No AppSync API configured in the project. Use \'amplify add api\' to create an API.');
+ }
+ const resourceDir = this.getResourceDir(apiName);
+ // update appsync cli-inputs
+ const gqlSchemaPath = await this.updateAppsyncCLIInputs(updates, apiName);
+ if (updates.transformSchema) {
+ this.writeSchema(gqlSchemaPath, updates.transformSchema);
+ }
+ if (updates.conflictResolution) {
+ updates.conflictResolution = await this.createResolverResources(updates.conflictResolution);
+ await writeResolverConfig(updates.conflictResolution, resourceDir);
+ }
+
+ const authConfig = getAppSyncAuthConfig(stateManager.getMeta());
+ const previousAuthConfig = _.cloneDeep(authConfig);
+ const oldConfigHadApiKey = authConfigHasApiKey(authConfig);
+ if (updates.defaultAuthType) {
+ authConfig.defaultAuthentication = appSyncAuthTypeToAuthConfig(updates.defaultAuthType);
+ }
+ if (updates.additionalAuthTypes) {
+ authConfig.additionalAuthenticationProviders = updates.additionalAuthTypes.map(appSyncAuthTypeToAuthConfig);
+ }
+
+ if (!opts?.skipCompile) {
+ await this.context.amplify.executeProviderUtils(this.context, 'awscloudformation', 'compileSchema', {
+ resourceDir,
+ parameters: this.getCfnParameters(apiName, authConfig, resourceDir),
+ authConfig,
+ previousAuthConfig,
+ });
+ }
+
+ this.context.amplify.updateamplifyMetaAfterResourceUpdate(category, apiName, 'output', { authConfig });
+ this.context.amplify.updateBackendConfigAfterResourceUpdate(category, apiName, 'output', { authConfig });
+
+ const existingDependsOn = stateManager.getBackendConfig()?.[category]?.[apiName]?.dependsOn || [];
+ const newDependsOn = amendDependsOnForAuthConfig(existingDependsOn, authConfig);
+ this.context.amplify.updateBackendConfigAfterResourceUpdate(category, apiName, 'dependsOn', newDependsOn);
+ this.context.amplify.updateamplifyMetaAfterResourceUpdate(category, apiName, 'dependsOn', newDependsOn);
+
+ printApiKeyWarnings(oldConfigHadApiKey, authConfigHasApiKey(authConfig));
+ };
+
+ private writeSchema = (resourceDir: string, schema: string): void => {
+ fs.writeFileSync(resourceDir, schema);
+ };
+
+ private getResourceDir = (apiName: string): string => pathManager.getResourceDirectoryPath(undefined, category, apiName);
+
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+ private createAmplifyMeta = (authConfig: AuthConfig, dependsOn?: DependsOnEntry[]) => ({
+ service: 'AppSync',
+ providerPlugin: provider,
+ dependsOn,
+ output: {
+ authConfig,
+ },
+ });
+
+ private extractAuthConfig = (config: AppSyncServiceConfig): AuthConfig => ({
+ defaultAuthentication: appSyncAuthTypeToAuthConfig(config.defaultAuthType),
+ additionalAuthenticationProviders: (config.additionalAuthTypes || []).map(appSyncAuthTypeToAuthConfig),
+ });
+
+ private updateTransformerConfigVersion = async (resourceDir: string): Promise => {
+ const localTransformerConfig = await readTransformerConfiguration(resourceDir);
+ localTransformerConfig.Version = TRANSFORM_CURRENT_VERSION;
+ localTransformerConfig.ElasticsearchWarning = true;
+ await writeTransformerConfiguration(resourceDir, localTransformerConfig);
+ };
+
+ private createResolverResources = async (conflictResolution: ConflictResolution = {}): Promise => {
+ const newConflictResolution = _.cloneDeep(conflictResolution);
+
+ // if the strategy is a new lambda, generate the lambda and update the strategy to reference the new lambda
+ const generateLambdaIfNew = async (strategy: ResolutionStrategy): Promise => {
+ if (strategy && strategy.type === 'LAMBDA' && strategy.resolver.type === 'NEW') {
+ // eslint-disable-next-line no-param-reassign
+ strategy.resolver = {
+ type: 'EXISTING',
+ name: await this.createSyncFunction(),
+ };
+ }
+ };
+ await generateLambdaIfNew(newConflictResolution.defaultResolutionStrategy);
+ await Promise.all(
+ (newConflictResolution.perModelResolutionStrategy || [])
+ .map(perModelStrategy => perModelStrategy.resolutionStrategy)
+ .map(generateLambdaIfNew),
+ );
+ return newConflictResolution;
+ };
+
+ private getCfnParameters = (apiName: string, authConfig, resourceDir: string): Record => {
+ const cfnPath = path.join(resourceDir, cfnParametersFilename);
+ const params = JSONUtilities.readJson<$TSAny>(cfnPath, { throwIfNotExist: false }) || defaultCfnParameters(apiName);
+ const cognitoPool = this.getCognitoUserPool(authConfig);
+ if (cognitoPool) {
+ params.AuthCognitoUserPoolId = cognitoPool;
+ } else {
+ delete params.AuthCognitoUserPoolId;
+ }
+ return params;
+ };
+
+ private getCognitoUserPool = (authConfig: AuthConfig): Record | undefined => {
+ const additionalUserPoolProvider = (authConfig.additionalAuthenticationProviders || []).find(
+ aap => aap.authenticationType === 'AMAZON_COGNITO_USER_POOLS',
+ );
+ const defaultAuth = authConfig.defaultAuthentication;
+ if (!(defaultAuth?.authenticationType === 'AMAZON_COGNITO_USER_POOLS') && !additionalUserPoolProvider) {
+ return undefined;
+ }
+ let userPoolId;
+ const configuredUserPoolName = checkIfAuthExists();
+
+ if (authConfig.userPoolConfig) {
+ ({ userPoolId } = authConfig.userPoolConfig);
+ } else if (additionalUserPoolProvider && additionalUserPoolProvider.userPoolConfig) {
+ ({ userPoolId } = additionalUserPoolProvider.userPoolConfig);
+ } else if (configuredUserPoolName) {
+ userPoolId = `auth${configuredUserPoolName}`;
+ } else {
+ throw new Error('Cannot find a configured Cognito User Pool.');
+ }
+
+ return {
+ 'Fn::GetAtt': [userPoolId, 'Outputs.UserPoolId'],
+ };
+ };
+
+ private createSyncFunction = async (): Promise => {
+ const targetDir = pathManager.getBackendDirPath();
+ const assetDir = path.normalize(path.join(rootAssetDir, 'sync-conflict-handler'));
+ const [shortId] = uuid().split('-');
+
+ const functionName = `syncConflictHandler${shortId}`;
+
+ const functionProps = {
+ functionName: `${functionName}`,
+ roleName: `${functionName}LambdaRole`,
+ };
+
+ const copyJobs = [
+ {
+ dir: assetDir,
+ template: 'sync-conflict-handler-index.js.ejs',
+ target: path.join(targetDir, 'function', functionName, 'src', 'index.js'),
+ },
+ {
+ dir: assetDir,
+ template: 'sync-conflict-handler-package.json.ejs',
+ target: path.join(targetDir, 'function', functionName, 'src', 'package.json'),
+ },
+ {
+ dir: assetDir,
+ template: 'sync-conflict-handler-template.json.ejs',
+ target: path.join(targetDir, 'function', functionName, `${functionName}-cloudformation-template.json`),
+ },
+ ];
+
+ // copy over the files
+ await this.context.amplify.copyBatch(this.context, copyJobs, functionProps, true);
+
+ const backendConfigs = {
+ service: FunctionServiceNameLambdaFunction,
+ providerPlugin: provider,
+ build: true,
+ };
+
+ await this.context.amplify.updateamplifyMetaAfterResourceAdd('function', functionName, backendConfigs);
+ printer.success(`Successfully added ${functionName} function locally`);
+
+ return `${functionName}-\${env}`;
+ };
+
+ private generateAppsyncCLIInputs = async (serviceConfig: AppSyncServiceConfiguration, resourceDir: string): Promise => {
+ const appsyncCLIInputs: AppSyncCLIInputs = {
+ version: 1,
+ serviceConfiguration: {
+ apiName: serviceConfig.apiName,
+ serviceName: serviceConfig.serviceName,
+ gqlSchemaPath: path.join(resourceDir, gqlSchemaFilename),
+ defaultAuthType: serviceConfig.defaultAuthType,
+ },
+ };
+ if (!_.isEmpty(serviceConfig.additionalAuthTypes)) {
+ appsyncCLIInputs.serviceConfiguration.additionalAuthTypes = serviceConfig.additionalAuthTypes;
+ }
+
+ if (!_.isEmpty(serviceConfig.conflictResolution)) {
+ appsyncCLIInputs.serviceConfiguration.conflictResolution = {
+ defaultResolutionStrategy: serviceConfig.conflictResolution.defaultResolutionStrategy,
+ perModelResolutionStrategy: serviceConfig.conflictResolution.perModelResolutionStrategy,
+ };
+ }
+ // deploy appsync inputs
+ const cliState = new AppsyncApiInputState(this.context, serviceConfig.apiName);
+ await cliState.saveCLIInputPayload(appsyncCLIInputs);
+ return appsyncCLIInputs;
+ };
+
+ private updateAppsyncCLIInputs = async (updates: AppSyncServiceModification, apiName: string) => {
+ const cliState = new AppsyncApiInputState(this.context, apiName);
+ const gqlSchemaPath = path.join(this.getResourceDir(apiName), gqlSchemaFilename);
+ if (!cliState.cliInputFileExists()) {
+ return gqlSchemaPath;
+ }
+ const prevAppsyncInputs = cliState.getCLIInputPayload();
+
+ const appsyncInputs: AppSyncCLIInputs = prevAppsyncInputs;
+ if (!_.isEmpty(appsyncInputs.serviceConfiguration)) {
+ appsyncInputs.serviceConfiguration.gqlSchemaPath = gqlSchemaPath;
+ }
+ if (updates.conflictResolution) {
+ appsyncInputs.serviceConfiguration.conflictResolution = updates.conflictResolution;
+ }
+ if (updates.defaultAuthType) {
+ appsyncInputs.serviceConfiguration.defaultAuthType = updates.defaultAuthType;
+ }
+ if (updates.additionalAuthTypes) {
+ appsyncInputs.serviceConfiguration.additionalAuthTypes = updates.additionalAuthTypes;
+ }
+ await cliState.saveCLIInputPayload(appsyncInputs);
+ return gqlSchemaPath;
+ };
+}
+
+/**
+ * This function is defined outside of the class because REST API generation uses it outside of the class above
+ * Long-term, the class above should be extended to also include REST API generation
+ *
+ * write to the transformer conf if the resolverConfig is valid
+ */
+export const writeResolverConfig = async (conflictResolution: ConflictResolution, resourceDir: string): Promise => {
+ const localTransformerConfig = await readTransformerConfiguration(resourceDir);
+ localTransformerConfig.ResolverConfig = conflictResolutionToResolverConfig(conflictResolution);
+ await writeTransformerConfiguration(resourceDir, localTransformerConfig);
+};
+
+const amendDependsOnForAuthConfig = (currentDependsOn: DependsOnEntry[], authConfig: AuthConfig): DependsOnEntry[] => {
+ if (hasCognitoAuthMode(authConfig)) {
+ return ensureDependsOnAuth(currentDependsOn);
+ }
+ return ensureNoDependsOnAuth(currentDependsOn);
+};
+
+const hasCognitoAuthMode = (authConfig: AuthConfig): boolean => (
+ authConfig?.defaultAuthentication?.authenticationType === 'AMAZON_COGNITO_USER_POOLS'
+ || authConfig?.additionalAuthenticationProviders?.find(aap => aap.authenticationType === 'AMAZON_COGNITO_USER_POOLS') !== undefined
+);
+
+// returns a new dependsOn array that has a single depends on auth block
+const ensureDependsOnAuth = (currentDependsOn: DependsOnEntry[]): DependsOnEntry[] => {
+ const authResourceName = checkIfAuthExists();
+ if (!authResourceName) {
+ return [];
+ }
+ // if dependency already exists, don't add it again
+ if (currentDependsOn.find(dep => dep.category === 'auth' && dep.resourceName === authResourceName)) {
+ return currentDependsOn;
+ }
+ return currentDependsOn.concat({
+ category: 'auth',
+ resourceName: authResourceName,
+ attributes: ['UserPoolId'],
+ });
+};
+
+// returns a new dependsOn array that does not have a depends on auth block
+const ensureNoDependsOnAuth = (currentDependsOn: DependsOnEntry[]): DependsOnEntry[] => {
+ const authResourceName = checkIfAuthExists();
+ if (!authResourceName) {
+ return currentDependsOn;
+ }
+ const authIdx = currentDependsOn.findIndex(dep => dep.category === 'auth' && dep.resourceName === authResourceName);
+ if (authIdx < 0) {
+ return currentDependsOn;
+ }
+ const newDependsOn = Array.from(currentDependsOn);
+ newDependsOn.splice(authIdx, 1);
+ return newDependsOn;
+};
+
+type DependsOnEntry = {
+ category: string;
+ resourceName: string;
+ attributes: string[];
+};
+
+type AuthConfig = {
+ defaultAuthentication?: AuthType;
+ additionalAuthenticationProviders?: (AuthType & UserPoolConfig)[];
+} & UserPoolConfig
+
+type UserPoolConfig = {
+ userPoolConfig?: {
+ userPoolId: string
+ }
+}
+
+type AuthType = {
+ authenticationType: string;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/containers-handler.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/containers-handler.ts
new file mode 100644
index 0000000000..1bc866ab6c
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/containers-handler.ts
@@ -0,0 +1,260 @@
+import { $TSContext, createDefaultCustomPoliciesFile, pathManager } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { v4 as uuid } from 'uuid';
+import { NETWORK_STACK_LOGICAL_ID } from '../../category-constants';
+import { DEPLOYMENT_MECHANISM } from './base-api-stack';
+import { GitHubSourceActionInfo } from './pipeline-with-awaiter';
+import { API_TYPE, IMAGE_SOURCE_TYPE, ResourceDependency, ServiceConfiguration } from './service-walkthroughs/containers-walkthrough';
+import { ApiResource, generateContainersArtifacts } from './utils/containers-artifacts';
+
+export const addResource = async (
+ serviceWalkthroughPromise: Promise,
+ context: $TSContext,
+ category: string,
+ service,
+ options,
+ apiType: API_TYPE,
+) => {
+ const walkthroughOptions = await serviceWalkthroughPromise;
+
+ const {
+ resourceName,
+ restrictAccess,
+ imageSource,
+ gitHubPath,
+ gitHubToken,
+ deploymentMechanism,
+ categoryPolicies,
+ environmentMap,
+ dependsOn = [],
+ mutableParametersState,
+ } = walkthroughOptions;
+ const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, category, resourceName);
+
+ let [authName, updatedDependsOn] = await getResourceDependencies({ dependsOn, restrictAccess, category, resourceName, context });
+
+ let gitHubInfo: GitHubSourceActionInfo;
+
+ if (deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
+ const { StackName } = context.amplify.getProjectDetails().amplifyMeta.providers['awscloudformation'];
+
+ const secretName = `${StackName}-${category}-${resourceName}-github-token`;
+ const { ARN: secretArn } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'newSecret', {
+ secret: gitHubToken,
+ description: 'GitHub OAuth token',
+ name: secretName,
+ version: secretName,
+ });
+
+ const gitHubTokenSecretArn = secretArn;
+
+ gitHubInfo = {
+ path: gitHubPath,
+ tokenSecretArn: gitHubTokenSecretArn,
+ };
+ }
+
+ const build = deploymentMechanism === DEPLOYMENT_MECHANISM.FULLY_MANAGED;
+
+ options = {
+ resourceName,
+ dependsOn: updatedDependsOn,
+ deploymentMechanism,
+ imageSource,
+ restrictAccess,
+ build,
+ providerPlugin: 'awscloudformation',
+ service: 'ElasticContainer',
+ gitHubInfo,
+ authName,
+ environmentMap,
+ categoryPolicies,
+ mutableParametersState,
+ skipHashing: false,
+ apiType,
+ iamAccessUnavailable: true, // this is because we dont support IAM access to the API yet
+ };
+
+ await context.amplify.updateamplifyMetaAfterResourceAdd(category, resourceName, options);
+
+ const apiResource = (await context.amplify.getProjectMeta().api[resourceName]) as ApiResource;
+ apiResource.category = category;
+
+ fs.ensureDirSync(resourceDirPath);
+
+ fs.ensureDirSync(path.join(resourceDirPath, 'src'));
+
+ if (imageSource.type === IMAGE_SOURCE_TYPE.TEMPLATE) {
+ fs.copySync(
+ path.join(__dirname, '..', '..', '..', 'resources', 'awscloudformation/container-templates', imageSource.template),
+ path.join(resourceDirPath, 'src'),
+ { recursive: true },
+ );
+ const { exposedContainer } = await generateContainersArtifacts(context, apiResource);
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'exposedContainer', exposedContainer);
+ }
+
+ createDefaultCustomPoliciesFile(category, resourceName);
+
+ const customPoliciesPath = pathManager.getCustomPoliciesPath(category, resourceName);
+
+ printer.success(`Successfully added resource ${resourceName} locally.`);
+ printer.info('');
+ printer.success('Next steps:');
+
+ if (deploymentMechanism === DEPLOYMENT_MECHANISM.FULLY_MANAGED) {
+ printer.info(
+ `- Place your Dockerfile, docker-compose.yml and any related container source files in "amplify/backend/api/${resourceName}/src"`,
+ );
+ } else if (deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
+ printer.info(
+ `- Ensure you have the Dockerfile, docker-compose.yml and any related container source files in your Github path: ${gitHubInfo.path}`,
+ );
+ }
+
+ printer.info(
+ `- Amplify CLI infers many configuration settings from the "docker-compose.yaml" file. Learn more: docs.amplify.aws/cli/usage/containers`,
+ );
+ printer.info(`- To access AWS resources outside of this Amplify app, edit the ${customPoliciesPath}`);
+ printer.info('- Run "amplify push" to build and deploy your image');
+
+ return resourceName;
+};
+
+const getResourceDependencies = async ({
+ restrictAccess,
+ dependsOn,
+ context,
+ resourceName,
+ category,
+}: {
+ restrictAccess: boolean;
+ dependsOn: ResourceDependency[];
+ context: $TSContext;
+ category: string;
+ resourceName: string;
+}) => {
+ let authName;
+ const updatedDependsOn: ResourceDependency[] = [].concat(dependsOn);
+
+ updatedDependsOn.push({
+ category: '',
+ resourceName: NETWORK_STACK_LOGICAL_ID,
+ attributes: ['ClusterName', 'VpcId', 'VpcCidrBlock', 'SubnetIds', 'VpcLinkId', 'CloudMapNamespaceId'],
+ });
+
+ if (restrictAccess) {
+ const apiRequirements = { authSelections: 'identityPoolAndUserPool' };
+ // getting requirement satisfaction map
+ const satisfiedRequirements = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'checkRequirements', [
+ apiRequirements,
+ context,
+ 'api',
+ resourceName,
+ ]);
+ // checking to see if any requirements are unsatisfied
+ const foundUnmetRequirements = Object.values(satisfiedRequirements).includes(false);
+
+ if (foundUnmetRequirements) {
+ try {
+ authName = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'externalAuthEnable', [
+ context,
+ 'api',
+ resourceName,
+ apiRequirements,
+ ]);
+ } catch (e) {
+ printer.error(e);
+ throw e;
+ }
+ } else {
+ [authName] = Object.keys(context.amplify.getProjectDetails().amplifyMeta.auth);
+ }
+
+ // get auth dependency if exists to avoid duplication
+ const authDependency = updatedDependsOn.find(dependency => dependency.category === 'auth');
+
+ if (authDependency === undefined) {
+ updatedDependsOn.push({
+ category: 'auth',
+ resourceName: authName,
+ attributes: ['UserPoolId', 'AppClientIDWeb'],
+ });
+ } else {
+ const existingAttributes = authDependency.attributes;
+
+ const newAttributes = new Set([...existingAttributes, 'UserPoolId', 'AppClientIDWeb']);
+
+ authDependency.attributes = Array.from(newAttributes);
+ }
+ }
+ return [authName, updatedDependsOn];
+};
+
+export const updateResource = async (serviceWalkthroughPromise: Promise, context: $TSContext, category: string) => {
+ const options = await serviceWalkthroughPromise;
+
+ const {
+ dependsOn,
+ restrictAccess,
+ resourceName,
+ gitHubPath,
+ gitHubToken,
+ gitHubInfo,
+ mutableParametersState,
+ categoryPolicies,
+ environmentMap,
+ deploymentMechanism,
+ } = options;
+
+ let [authResourceName, updatedDependsOn] = await getResourceDependencies({ dependsOn, restrictAccess, category, resourceName, context });
+
+ let newGithubInfo: GitHubSourceActionInfo = {
+ path: gitHubPath,
+ tokenSecretArn: gitHubInfo && gitHubInfo.tokenSecretArn,
+ };
+ if (gitHubToken) {
+ //#region Add token to secrets manager and get arn
+ const { StackName } = context.amplify.getProjectDetails().amplifyMeta.providers['awscloudformation'];
+
+ const secretName = `${StackName}-${category}-${resourceName}-github-token`;
+ const { ARN: secretArn } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'updateSecret', {
+ secret: gitHubToken,
+ description: 'GitHub OAuth token',
+ name: secretName,
+ version: uuid(),
+ });
+
+ newGithubInfo.tokenSecretArn = secretArn;
+ //#endregion
+ }
+
+ if (deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'gitHubInfo', newGithubInfo);
+ }
+
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'restrictAccess', restrictAccess);
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'authName', authResourceName);
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'environmentMap', environmentMap);
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'dependsOn', updatedDependsOn);
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(
+ category,
+ options.resourceName,
+ 'mutableParametersState',
+ mutableParametersState,
+ );
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'categoryPolicies', categoryPolicies);
+
+ const apiResource = (await context.amplify.getProjectMeta().api[options.resourceName]) as ApiResource;
+ apiResource.category = category;
+
+ try {
+ const askForExposedContainer = true;
+ const { exposedContainer } = await generateContainersArtifacts(context, apiResource, askForExposedContainer);
+ await context.amplify.updateamplifyMetaAfterResourceUpdate(category, options.resourceName, 'exposedContainer', exposedContainer);
+ } catch (err) {
+ // Best effort to create templates
+ }
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/convert-deprecated-apigw-paths.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/convert-deprecated-apigw-paths.ts
new file mode 100644
index 0000000000..8e956d7f77
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/convert-deprecated-apigw-paths.ts
@@ -0,0 +1,92 @@
+import { $TSObject, JSONUtilities } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import { CrudOperation, PermissionSetting } from './cdk-stack-builder/types';
+
+export function convertDeperecatedRestApiPaths(
+ deprecatedParametersFileName: string,
+ deprecatedParametersFilePath: string,
+ resourceName: string,
+) {
+ let deprecatedParameters: $TSObject;
+ try {
+ deprecatedParameters = JSONUtilities.readJson<$TSObject>(deprecatedParametersFilePath);
+ } catch (e) {
+ printer.error(`Error reading ${deprecatedParametersFileName} file for ${resourceName} resource`);
+ throw e;
+ }
+
+ let paths = {};
+
+ if (!Array.isArray(deprecatedParameters.paths) || deprecatedParameters.paths.length < 1) {
+ throw new Error(`Expected paths to be defined in "${deprecatedParametersFilePath}", but none found.`);
+ }
+
+ deprecatedParameters.paths.forEach((path: $TSObject) => {
+ let pathPermissionSetting =
+ path.privacy?.open === true
+ ? PermissionSetting.OPEN
+ : path.privacy?.private === true
+ ? PermissionSetting.PRIVATE
+ : PermissionSetting.PROTECTED;
+
+ let auth;
+ let guest;
+ let groups;
+ // convert deprecated permissions to CRUD structure
+ if (typeof path.privacy?.auth === 'string' && ['r', 'rw'].includes(path.privacy.auth)) {
+ auth = _convertDeprecatedPermissionStringToCRUD(path.privacy.auth);
+ } else if (Array.isArray(path.privacy?.auth)) {
+ auth = _convertDeprecatedPermissionArrayToCRUD(path.privacy.auth);
+ }
+
+ if (typeof path.privacy?.unauth === 'string' && ['r', 'rw'].includes(path.privacy.unauth)) {
+ guest = _convertDeprecatedPermissionStringToCRUD(path.privacy.unauth);
+ } else if (Array.isArray(path.privacy?.unauth)) {
+ guest = _convertDeprecatedPermissionArrayToCRUD(path.privacy.unauth);
+ }
+
+ if (path.privacy?.userPoolGroups) {
+ groups = {};
+ for (const [userPoolGroupName, crudOperations] of Object.entries(path.privacy.userPoolGroups)) {
+ if (typeof crudOperations === 'string' && ['r', 'rw'].includes(crudOperations)) {
+ groups[userPoolGroupName] = _convertDeprecatedPermissionStringToCRUD(crudOperations);
+ } else if (Array.isArray(crudOperations)) {
+ groups[userPoolGroupName] = _convertDeprecatedPermissionArrayToCRUD(crudOperations);
+ }
+ }
+ }
+
+ paths[path.name] = {
+ permissions: {
+ setting: pathPermissionSetting,
+ auth,
+ guest,
+ groups,
+ },
+ lambdaFunction: path.lambdaFunction,
+ };
+ });
+
+ return paths;
+}
+
+function _convertDeprecatedPermissionStringToCRUD(deprecatedPrivacy: string): CrudOperation[] {
+ let privacyList: CrudOperation[];
+ if (deprecatedPrivacy === 'r') {
+ privacyList = [CrudOperation.READ];
+ } else if (deprecatedPrivacy === 'rw') {
+ privacyList = [CrudOperation.CREATE, CrudOperation.READ, CrudOperation.UPDATE, CrudOperation.DELETE];
+ }
+ return privacyList;
+}
+
+function _convertDeprecatedPermissionArrayToCRUD(deprecatedPrivacyArray: string[]): CrudOperation[] {
+ const opMap: Record = {
+ '/POST': CrudOperation.CREATE,
+ '/GET': CrudOperation.READ,
+ '/PUT': CrudOperation.UPDATE,
+ '/PATCH': CrudOperation.UPDATE,
+ '/DELETE': CrudOperation.DELETE,
+ };
+ return Array.from(new Set(deprecatedPrivacyArray.map(op => opMap[op])));
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/apigw-defaults.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/apigw-defaults.ts
new file mode 100644
index 0000000000..60dbcd9da4
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/apigw-defaults.ts
@@ -0,0 +1,13 @@
+import { v4 as uuid } from 'uuid';
+
+export const getAllDefaults = (project: { projectConfig: { projectName: string } }) => {
+ const name = project.projectConfig.projectName.toLowerCase().replace(/[^0-9a-zA-Z]/gi, '');
+ const [shortId] = uuid().split('-');
+ const defaults = {
+ resourceName: `api${shortId}`,
+ apiName: `${name}${shortId}`,
+ paths: [],
+ };
+
+ return defaults;
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/appSync-defaults.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/appSync-defaults.ts
new file mode 100644
index 0000000000..8c48966c22
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/appSync-defaults.ts
@@ -0,0 +1,19 @@
+import { $TSMeta } from 'amplify-cli-core';
+import { v4 as uuid } from 'uuid';
+
+export const getAllDefaults = (project: { amplifyMeta: $TSMeta; projectConfig: { projectName: string } }) => {
+ const name = project.projectConfig.projectName.toLowerCase();
+ const region = project.amplifyMeta.providers.awscloudformation.Region;
+ const [shortId] = uuid().split('-');
+ const defaults = {
+ resourceName: `appsync${shortId}`,
+ apiName: `${name}`,
+ serviceRoleName: `serviceRole${shortId}`,
+ servicePolicyName: `servicePolicy${shortId}`,
+ apiCreationChoice: false,
+ region,
+ defaultTableName: `Posts${shortId}`,
+ };
+
+ return defaults;
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/containers-defaults.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/containers-defaults.ts
new file mode 100644
index 0000000000..0f77dee83e
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/default-values/containers-defaults.ts
@@ -0,0 +1,10 @@
+import { v4 as uuid } from 'uuid';
+
+export const getAllDefaults = () => {
+ const [shortId] = uuid().split('-');
+ const defaults = {
+ resourceName: `container${shortId}`,
+ };
+
+ return defaults;
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/DockerUtils.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/DockerUtils.ts
new file mode 100644
index 0000000000..89b0016feb
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/DockerUtils.ts
@@ -0,0 +1,81 @@
+import * as yaml from 'js-yaml';
+import * as v1Types from './compose-spec/v1';
+import * as v2Types from './compose-spec/v2';
+import { BuildHashMap } from './ecs-objects/types';
+
+export const dockerComposeToObject = (yamlFileContents: string): v2Types.ConfigSchemaV24Json | v1Types.ConfigSchemaV1Json => {
+ try {
+ const doc = yaml.load(yamlFileContents);
+ return doc as v2Types.ConfigSchemaV24Json | v1Types.ConfigSchemaV1Json;
+ } catch (e) {
+ console.log(e);
+
+ throw e;
+ }
+};
+
+export const dockerfileToObject = (dockerfileContents: string): v2Types.ConfigSchemaV24Json | v1Types.ConfigSchemaV1Json => {
+ const lines = dockerfileContents?.split('\n') ?? [];
+ const ports = lines.filter(line => /^\s*EXPOSE\s+/.test(line)).map(line => line.match(/\s+(\d+)/)[1]);
+
+ const composeContents = `version: "3"
+services:
+ api:
+ build: .${
+ ports.length > 0
+ ? `
+ ports: ${ports
+ .map(
+ port => `
+ - '${port}:${port}'`,
+ )
+ .join('')}`
+ : ``
+ }
+`;
+
+ return dockerComposeToObject(composeContents);
+};
+
+export const generateBuildSpec = (containerMap: BuildHashMap) => {
+ return `# Auto-Generated by Amplify. Do not modify
+version: 0.2
+
+phases:
+ install:
+ runtime-versions:
+ docker: 19
+ pre_build:
+ commands:
+ - echo Logging in to Amazon ECR...
+ - aws --version
+ - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
+ - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | md5sum | cut -c 1-7)
+ - IMAGE_TAG=\${COMMIT_HASH:=latest}
+ build:
+ commands:
+ - echo Build started on \`date\`
+ - echo Building the Docker image...${Object.keys(containerMap)
+ .map(
+ item => `
+ - docker build -t $${item}_REPOSITORY_URI:latest ./${containerMap[item]}
+ - docker tag $${item}_REPOSITORY_URI:latest $${item}_REPOSITORY_URI:$IMAGE_TAG`,
+ )
+ .join('\n')}
+ post_build:
+ commands:
+ - echo Build completed on \`date\`
+ - echo Pushing the Docker images..${Object.keys(containerMap)
+ .map(
+ item => `
+ - docker push $${item}_REPOSITORY_URI:latest
+ - docker push $${item}_REPOSITORY_URI:$IMAGE_TAG`,
+ )
+ .join('\n')}
+ - "echo \\"[${Object.keys(containerMap)
+ .map(name => `{\\\\\\\"name\\\\\\\":\\\\\\\"${name}\\\\\\\", \\\\\\\"imageUri\\\\\\\":\\\\\\\"$${name}_REPOSITORY_URI\\\\\\\"}`)
+ .join(',')}]\\" > imagedefinitions.json"
+artifacts:
+ files: imagedefinitions.json
+`;
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v1.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v1.ts
new file mode 100644
index 0000000000..771160d6cd
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v1.ts
@@ -0,0 +1,103 @@
+/**
+ * This file was automatically generated by json-schema-to-typescript.
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
+ * and run json-schema-to-typescript to regenerate this file.
+ */
+
+export type StringOrList = string | ListOfStrings;
+export type ListOfStrings = string[];
+export type ListOrDict =
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` ".+".
+ */
+ [k: string]: string | number | null;
+ }
+ | string[];
+export type Labels =
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` ".+".
+ */
+ [k: string]: string;
+ }
+ | string[];
+
+export interface ConfigSchemaV1Json {
+ [k: string]: DefinitionsService;
+}
+/**
+ * This interface was referenced by `ConfigSchemaV1Json`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export interface DefinitionsService {
+ build?: string;
+ cap_add?: string[];
+ cap_drop?: string[];
+ cgroup_parent?: string;
+ command?: string | string[];
+ container_name?: string;
+ cpu_shares?: number | string;
+ cpu_quota?: number | string;
+ cpuset?: string;
+ devices?: string[];
+ dns?: StringOrList;
+ dns_search?: StringOrList;
+ dockerfile?: string;
+ domainname?: string;
+ entrypoint?: string | string[];
+ env_file?: StringOrList;
+ environment?: ListOrDict;
+ expose?: (string | number)[];
+ extends?:
+ | string
+ | {
+ service: string;
+ file?: string;
+ };
+ extra_hosts?: ListOrDict;
+ external_links?: string[];
+ hostname?: string;
+ image?: string;
+ ipc?: string;
+ labels?: Labels;
+ links?: string[];
+ log_driver?: string;
+ log_opt?: {
+ [k: string]: any;
+ };
+ mac_address?: string;
+ mem_limit?: number | string;
+ memswap_limit?: number | string;
+ mem_swappiness?: number;
+ net?: string;
+ pid?: string | null;
+ ports?: (string | number)[];
+ privileged?: boolean;
+ read_only?: boolean;
+ restart?: string;
+ security_opt?: string[];
+ shm_size?: number | string;
+ stdin_open?: boolean;
+ stop_signal?: string;
+ tty?: boolean;
+ ulimits?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-z]+$".
+ */
+ [k: string]:
+ | number
+ | {
+ hard: number;
+ soft: number;
+ };
+ };
+ user?: string;
+ volumes?: string[];
+ volume_driver?: string;
+ volumes_from?: string[];
+ working_dir?: string;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v2.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v2.ts
new file mode 100644
index 0000000000..a16b147f4d
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v2.ts
@@ -0,0 +1,294 @@
+/**
+ * This file was automatically generated by json-schema-to-typescript.
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
+ * and run json-schema-to-typescript to regenerate this file.
+ */
+
+export type ListOrDict =
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` ".+".
+ */
+ [k: string]: string | number | null;
+ }
+ | string[];
+export type Labels =
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` ".+".
+ */
+ [k: string]: string;
+ }
+ | string[];
+export type ListOfStrings = string[];
+export type StringOrList = string | ListOfStrings;
+/**
+ * This interface was referenced by `PropertiesVolumes`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export type DefinitionsVolume = {
+ [k: string]: any;
+} | null;
+
+export interface ConfigSchemaV24Json {
+ version?: string;
+ services?: PropertiesServices;
+ networks?: PropertiesNetworks;
+ volumes?: PropertiesVolumes;
+ /**
+ * This interface was referenced by `ConfigSchemaV24Json`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: any;
+}
+export interface PropertiesServices {
+ [k: string]: DefinitionsService;
+}
+/**
+ * This interface was referenced by `PropertiesServices`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export interface DefinitionsService {
+ blkio_config?: {
+ device_read_bps?: BlkioLimit[];
+ device_read_iops?: BlkioLimit[];
+ device_write_bps?: BlkioLimit[];
+ device_write_iops?: BlkioLimit[];
+ weight?: number;
+ weight_device?: BlkioWeight[];
+ };
+ build?:
+ | string
+ | {
+ context?: string;
+ dockerfile?: string;
+ args?: ListOrDict;
+ labels?: Labels;
+ cache_from?: ListOfStrings;
+ network?: string;
+ target?: string;
+ shm_size?: number | string;
+ extra_hosts?: ListOrDict;
+ isolation?: string;
+ };
+ cap_add?: ListOfStrings;
+ cap_drop?: ListOfStrings;
+ cgroup_parent?: string;
+ command?: string | string[];
+ container_name?: string;
+ cpu_count?: number;
+ cpu_percent?: number;
+ cpu_shares?: number | string;
+ cpu_quota?: number | string;
+ cpu_period?: number | string;
+ cpu_rt_period?: number | string;
+ cpu_rt_runtime?: number | string;
+ cpus?: number;
+ cpuset?: string;
+ depends_on?:
+ | ListOfStrings
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+ [k: string]: {
+ condition: 'service_started' | 'service_healthy';
+ };
+ };
+ device_cgroup_rules?: ListOfStrings;
+ devices?: ListOfStrings;
+ dns_opt?: string[];
+ dns?: StringOrList;
+ dns_search?: StringOrList;
+ domainname?: string;
+ entrypoint?: string | string[];
+ env_file?: StringOrList;
+ environment?: ListOrDict;
+ expose?: (string | number)[];
+ extends?:
+ | string
+ | {
+ service: string;
+ file?: string;
+ };
+ external_links?: ListOfStrings;
+ extra_hosts?: ListOrDict;
+ group_add?: (string | number)[];
+ healthcheck?: DefinitionsHealthcheck;
+ hostname?: string;
+ image?: string;
+ init?: boolean | string;
+ ipc?: string;
+ isolation?: string;
+ labels?: Labels;
+ links?: ListOfStrings;
+ logging?: {
+ driver?: string;
+ options?: {
+ [k: string]: any;
+ };
+ };
+ mac_address?: string;
+ mem_limit?: number | string;
+ mem_reservation?: string | number;
+ mem_swappiness?: number;
+ memswap_limit?: number | string;
+ network_mode?: string;
+ networks?:
+ | ListOfStrings
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+ [k: string]: {
+ aliases?: ListOfStrings;
+ ipv4_address?: string;
+ ipv6_address?: string;
+ link_local_ips?: ListOfStrings;
+ priority?: number;
+ } | null;
+ };
+ oom_kill_disable?: boolean;
+ oom_score_adj?: number;
+ pid?: string | null;
+ platform?: string;
+ ports?: (string | number)[];
+ privileged?: boolean;
+ read_only?: boolean;
+ restart?: string;
+ runtime?: string;
+ scale?: number;
+ security_opt?: ListOfStrings;
+ shm_size?: number | string;
+ sysctls?: ListOrDict;
+ pids_limit?: number | string;
+ stdin_open?: boolean;
+ stop_grace_period?: string;
+ stop_signal?: string;
+ storage_opt?: {
+ [k: string]: any;
+ };
+ tmpfs?: StringOrList;
+ tty?: boolean;
+ ulimits?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-z]+$".
+ */
+ [k: string]:
+ | number
+ | {
+ hard: number;
+ soft: number;
+ };
+ };
+ user?: string;
+ userns_mode?: string;
+ volumes?: (
+ | string
+ | {
+ type: string;
+ source?: string;
+ target?: string;
+ read_only?: boolean;
+ consistency?: string;
+ bind?: {
+ propagation?: string;
+ [k: string]: any;
+ };
+ volume?: {
+ nocopy?: boolean;
+ [k: string]: any;
+ };
+ tmpfs?: {
+ size?: number | string;
+ [k: string]: any;
+ };
+ }
+ )[];
+ volume_driver?: string;
+ volumes_from?: ListOfStrings;
+ working_dir?: string;
+ /**
+ * This interface was referenced by `DefinitionsService`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: any;
+}
+export interface BlkioLimit {
+ path?: string;
+ rate?: number | string;
+}
+export interface BlkioWeight {
+ path?: string;
+ weight?: number;
+}
+export interface DefinitionsHealthcheck {
+ disable?: boolean;
+ interval?: string;
+ retries?: number;
+ start_period?: string;
+ test?: string | string[];
+ timeout?: string;
+}
+export interface PropertiesNetworks {
+ [k: string]: DefinitionsNetwork;
+}
+/**
+ * This interface was referenced by `PropertiesNetworks`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export interface DefinitionsNetwork {
+ driver?: string;
+ driver_opts?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string | number;
+ };
+ ipam?: {
+ driver?: string;
+ config?: DefinitionsIpamConfig[];
+ options?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string;
+ };
+ };
+ external?:
+ | boolean
+ | {
+ [k: string]: any;
+ };
+ internal?: boolean;
+ enable_ipv6?: boolean;
+ labels?: Labels;
+ name?: string;
+ /**
+ * This interface was referenced by `DefinitionsNetwork`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: any;
+}
+export interface DefinitionsIpamConfig {
+ subnet?: string;
+ ip_range?: string;
+ gateway?: string;
+ aux_addresses?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string;
+ };
+}
+export interface PropertiesVolumes {
+ [k: string]: DefinitionsVolume;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v3.8.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v3.8.ts
new file mode 100644
index 0000000000..81cd2529eb
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v3.8.ts
@@ -0,0 +1,596 @@
+/* tslint:disable */
+/**
+ * This file was automatically generated by json-schema-to-typescript.
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
+ * and run json-schema-to-typescript to regenerate this file.
+ */
+
+export type DefinitionsDeployment2 = DefinitionsDeployment | DefinitionsDeployment1;
+export type ListOrDict =
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` ".+".
+ */
+ [k: string]: string | number | null;
+ }
+ | string[];
+export type DefinitionsGenericResources = {
+ discrete_resource_spec?: {
+ kind?: string;
+ value?: number;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}[];
+export type DefinitionsDeployment1 = null;
+export type ListOfStrings = string[];
+export type StringOrList = string | ListOfStrings;
+/**
+ * This interface was referenced by `PropertiesNetworks`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export type DefinitionsNetwork2 = DefinitionsNetwork | DefinitionsNetwork1;
+export type DefinitionsNetwork1 = null;
+/**
+ * This interface was referenced by `PropertiesVolumes`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export type DefinitionsVolume2 = DefinitionsVolume | DefinitionsVolume1;
+export type DefinitionsVolume1 = null;
+
+/**
+ * The Compose file is a YAML file defining a multi-containers based application.
+ */
+export interface ComposeSpecification {
+ /**
+ * Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file.
+ */
+ version?: string;
+ services?: PropertiesServices;
+ networks?: PropertiesNetworks;
+ volumes?: PropertiesVolumes;
+ secrets?: PropertiesSecrets;
+ configs?: PropertiesConfigs;
+ /**
+ * This interface was referenced by `ComposeSpecification`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
+export interface PropertiesServices {
+ [k: string]: DefinitionsService;
+}
+/**
+ * This interface was referenced by `PropertiesServices`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export interface DefinitionsService {
+ deploy?: DefinitionsDeployment2;
+ build?:
+ | string
+ | {
+ context?: string;
+ dockerfile?: string;
+ args?: ListOrDict;
+ labels?: ListOrDict;
+ cache_from?: ListOfStrings;
+ network?: string;
+ target?: string;
+ shm_size?: number | string;
+ extra_hosts?: ListOrDict;
+ isolation?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ blkio_config?: {
+ device_read_bps?: BlkioLimit[];
+ device_read_iops?: BlkioLimit[];
+ device_write_bps?: BlkioLimit[];
+ device_write_iops?: BlkioLimit[];
+ weight?: number;
+ weight_device?: BlkioWeight[];
+ };
+ cap_add?: string[];
+ cap_drop?: string[];
+ cgroup_parent?: string;
+ command?: string | string[];
+ configs?: (
+ | string
+ | {
+ source?: string;
+ target?: string;
+ uid?: string;
+ gid?: string;
+ mode?: number;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ }
+ )[];
+ container_name?: string;
+ cpu_count?: number;
+ cpu_percent?: number;
+ cpu_shares?: number | string;
+ cpu_quota?: number | string;
+ cpu_period?: number | string;
+ cpu_rt_period?: number | string;
+ cpu_rt_runtime?: number | string;
+ cpus?: number | string;
+ cpuset?: string;
+ credential_spec?: {
+ config?: string;
+ file?: string;
+ registry?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ depends_on?:
+ | ListOfStrings
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+ [k: string]: {
+ condition: 'service_started' | 'service_healthy';
+ };
+ };
+ device_cgroup_rules?: ListOfStrings;
+ devices?: string[];
+ dns?: StringOrList;
+ dns_opt?: string[];
+ dns_search?: StringOrList;
+ domainname?: string;
+ entrypoint?: string | string[];
+ env_file?: StringOrList;
+ environment?: ListOrDict;
+ expose?: (string | number)[];
+ extends?:
+ | string
+ | {
+ service: string;
+ file?: string;
+ };
+ external_links?: string[];
+ extra_hosts?: ListOrDict;
+ group_add?: (string | number)[];
+ healthcheck?: DefinitionsHealthcheck;
+ hostname?: string;
+ image?: string;
+ init?: boolean;
+ ipc?: string;
+ isolation?: string;
+ labels?: ListOrDict;
+ links?: string[];
+ logging?: {
+ driver?: string;
+ options?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string | number | null;
+ };
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ mac_address?: string;
+ mem_limit?: string;
+ mem_reservation?: string | number;
+ mem_swappiness?: number;
+ memswap_limit?: number | string;
+ network_mode?: string;
+ networks?:
+ | ListOfStrings
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+ [k: string]: {
+ aliases?: ListOfStrings;
+ ipv4_address?: string;
+ ipv6_address?: string;
+ link_local_ips?: ListOfStrings;
+ priority?: number;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ } | null;
+ };
+ oom_kill_disable?: boolean;
+ oom_score_adj?: number;
+ pid?: string | null;
+ pids_limit?: number | string;
+ platform?: string;
+ ports?: (
+ | number
+ | string
+ | {
+ mode?: string;
+ target?: number;
+ published?: number;
+ protocol?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ }
+ )[];
+ privileged?: boolean;
+ pull_policy?: 'always' | 'never' | 'if_not_present';
+ read_only?: boolean;
+ restart?: string;
+ runtime?: string;
+ scale?: number;
+ security_opt?: string[];
+ shm_size?: number | string;
+ secrets?: (
+ | string
+ | {
+ source?: string;
+ target?: string;
+ uid?: string;
+ gid?: string;
+ mode?: number;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ }
+ )[];
+ sysctls?: ListOrDict;
+ stdin_open?: boolean;
+ stop_grace_period?: string;
+ stop_signal?: string;
+ tmpfs?: StringOrList;
+ tty?: boolean;
+ ulimits?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-z]+$".
+ */
+ [k: string]:
+ | number
+ | {
+ hard: number;
+ soft: number;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ };
+ user?: string;
+ userns_mode?: string;
+ volumes?: (
+ | string
+ | {
+ type: string;
+ source?: string;
+ target?: string;
+ read_only?: boolean;
+ consistency?: string;
+ bind?: {
+ propagation?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ volume?: {
+ nocopy?: boolean;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ tmpfs?: {
+ size?: number;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ }
+ )[];
+ volumes_from?: string[];
+ working_dir?: string;
+ /**
+ * This interface was referenced by `DefinitionsService`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
+export interface DefinitionsDeployment {
+ mode?: string;
+ endpoint_mode?: string;
+ replicas?: number;
+ labels?: ListOrDict;
+ rollback_config?: {
+ parallelism?: number;
+ delay?: string;
+ failure_action?: string;
+ monitor?: string;
+ max_failure_ratio?: number;
+ order?: 'start-first' | 'stop-first';
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ update_config?: {
+ parallelism?: number;
+ delay?: string;
+ failure_action?: string;
+ monitor?: string;
+ max_failure_ratio?: number;
+ order?: 'start-first' | 'stop-first';
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ resources?: {
+ limits?: {
+ cpus?: number | string;
+ memory?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ reservations?: {
+ cpus?: number | string;
+ memory?: string;
+ generic_resources?: DefinitionsGenericResources;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ restart_policy?: {
+ condition?: string;
+ delay?: string;
+ max_attempts?: number;
+ window?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ placement?: {
+ constraints?: string[];
+ preferences?: {
+ spread?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ }[];
+ max_replicas_per_node?: number;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ /**
+ * This interface was referenced by `DefinitionsDeployment`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
+export interface BlkioLimit {
+ path?: string;
+ rate?: number | string;
+}
+export interface BlkioWeight {
+ path?: string;
+ weight?: number;
+}
+export interface DefinitionsHealthcheck {
+ disable?: boolean;
+ interval?: string;
+ retries?: number;
+ test?: string | string[];
+ timeout?: string;
+ start_period?: string;
+ /**
+ * This interface was referenced by `DefinitionsHealthcheck`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
+export interface PropertiesNetworks {
+ [k: string]: DefinitionsNetwork2;
+}
+export interface DefinitionsNetwork {
+ name?: string;
+ driver?: string;
+ driver_opts?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string | number;
+ };
+ ipam?: {
+ driver?: string;
+ config?: {
+ subnet?: string;
+ ip_range?: string;
+ gateway?: string;
+ aux_addresses?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string;
+ };
+ [k: string]: unknown;
+ }[];
+ options?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string;
+ };
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ external?:
+ | boolean
+ | {
+ name?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ internal?: boolean;
+ enable_ipv6?: boolean;
+ attachable?: boolean;
+ labels?: ListOrDict;
+ /**
+ * This interface was referenced by `DefinitionsNetwork`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
+export interface PropertiesVolumes {
+ [k: string]: DefinitionsVolume2;
+}
+export interface DefinitionsVolume {
+ name?: string;
+ driver?: string;
+ driver_opts?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string | number;
+ };
+ external?:
+ | boolean
+ | {
+ name?: string;
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+ };
+ labels?: ListOrDict;
+ /**
+ * This interface was referenced by `DefinitionsVolume`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
+export interface PropertiesSecrets {
+ [k: string]: DefinitionsSecret;
+}
+/**
+ * This interface was referenced by `PropertiesSecrets`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export interface DefinitionsSecret {
+ name?: string;
+ file?: string;
+ external?:
+ | boolean
+ | {
+ name?: string;
+ [k: string]: unknown;
+ };
+ labels?: ListOrDict;
+ driver?: string;
+ driver_opts?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string | number;
+ };
+ template_driver?: string;
+ /**
+ * This interface was referenced by `DefinitionsSecret`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
+export interface PropertiesConfigs {
+ [k: string]: DefinitionsConfig;
+}
+/**
+ * This interface was referenced by `PropertiesConfigs`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export interface DefinitionsConfig {
+ name?: string;
+ file?: string;
+ external?:
+ | boolean
+ | {
+ name?: string;
+ [k: string]: unknown;
+ };
+ labels?: ListOrDict;
+ template_driver?: string;
+ /**
+ * This interface was referenced by `DefinitionsConfig`'s JSON-Schema definition
+ * via the `patternProperty` "^x-".
+ */
+ [k: string]: unknown;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v3.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v3.ts
new file mode 100644
index 0000000000..605b7e86ec
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/compose-spec/v3.ts
@@ -0,0 +1,156 @@
+/**
+ * This file was automatically generated by json-schema-to-typescript.
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
+ * and run json-schema-to-typescript to regenerate this file.
+ */
+
+export type DefinitionsDeployment = {
+ [k: string]: any;
+} | null;
+export type ListOrDict =
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` ".+".
+ */
+ [k: string]: string | number | null;
+ }
+ | string[];
+export type ListOfStrings = string[];
+export type StringOrList = string | ListOfStrings;
+export type Labels =
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` ".+".
+ */
+ [k: string]: string;
+ }
+ | string[];
+/**
+ * This interface was referenced by `PropertiesNetworks`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export type DefinitionsNetwork = {
+ [k: string]: any;
+} | null;
+/**
+ * This interface was referenced by `PropertiesVolumes`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export type DefinitionsVolume = {
+ [k: string]: any;
+} | null;
+
+export interface ConfigSchemaV30Json {
+ version: string;
+ services?: PropertiesServices;
+ networks?: PropertiesNetworks;
+ volumes?: PropertiesVolumes;
+}
+export interface PropertiesServices {
+ [k: string]: DefinitionsService;
+}
+/**
+ * This interface was referenced by `PropertiesServices`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+export interface DefinitionsService {
+ deploy?: DefinitionsDeployment;
+ build?:
+ | string
+ | {
+ context?: string;
+ dockerfile?: string;
+ args?: ListOrDict;
+ };
+ cap_add?: string[];
+ cap_drop?: string[];
+ cgroup_parent?: string;
+ command?: string | string[];
+ container_name?: string;
+ depends_on?: ListOfStrings;
+ devices?: string[];
+ dns?: StringOrList;
+ dns_search?: StringOrList;
+ domainname?: string;
+ entrypoint?: string | string[];
+ env_file?: StringOrList;
+ environment?: ListOrDict;
+ expose?: (string | number)[];
+ external_links?: string[];
+ extra_hosts?: ListOrDict;
+ healthcheck?: DefinitionsHealthcheck;
+ hostname?: string;
+ image?: string;
+ ipc?: string;
+ labels?: Labels;
+ links?: string[];
+ logging?: {
+ driver?: string;
+ options?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^.+$".
+ */
+ [k: string]: string | number | null;
+ };
+ };
+ mac_address?: string;
+ network_mode?: string;
+ networks?:
+ | ListOfStrings
+ | {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-zA-Z0-9._-]+$".
+ */
+ [k: string]: {
+ aliases?: ListOfStrings;
+ ipv4_address?: string;
+ ipv6_address?: string;
+ } | null;
+ };
+ pid?: string | null;
+ ports?: (string | number)[];
+ privileged?: boolean;
+ read_only?: boolean;
+ restart?: string;
+ security_opt?: string[];
+ shm_size?: number | string;
+ sysctls?: ListOrDict;
+ stdin_open?: boolean;
+ stop_grace_period?: string;
+ stop_signal?: string;
+ tmpfs?: StringOrList;
+ tty?: boolean;
+ ulimits?: {
+ /**
+ * This interface was referenced by `undefined`'s JSON-Schema definition
+ * via the `patternProperty` "^[a-z]+$".
+ */
+ [k: string]:
+ | number
+ | {
+ hard: number;
+ soft: number;
+ };
+ };
+ user?: string;
+ userns_mode?: string;
+ volumes?: string[];
+ working_dir?: string;
+}
+export interface DefinitionsHealthcheck {
+ disable?: boolean;
+ interval?: string;
+ retries?: number;
+ test?: string | string[];
+ timeout?: string;
+}
+export interface PropertiesNetworks {
+ [k: string]: DefinitionsNetwork;
+}
+export interface PropertiesVolumes {
+ [k: string]: DefinitionsVolume;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/converter.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/converter.ts
new file mode 100644
index 0000000000..2bafe72104
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/converter.ts
@@ -0,0 +1,171 @@
+import * as v1Types from './compose-spec/v1';
+import * as v2Types from './compose-spec/v2';
+import * as v38Types from './compose-spec/v3.8';
+import { dockerComposeToObject, dockerfileToObject, generateBuildSpec } from './DockerUtils';
+import Container from './ecs-objects/container';
+import { BuildHashMap, PortMappings } from './ecs-objects/types';
+
+const isv1Schema = (obj: any): obj is v1Types.ConfigSchemaV1Json => {
+ return obj && obj.version === undefined;
+};
+
+const hasHealthCheck = (obj: any): obj is v2Types.DefinitionsHealthcheck => {
+ return obj.healthcheck !== undefined;
+};
+
+function isV38Service(obj: any): obj is v38Types.DefinitionsService {
+ return obj && (obj).secrets !== undefined;
+}
+const mapComposeEntriesToContainer = (record: [string, v1Types.DefinitionsService | v2Types.DefinitionsService]): Container => {
+ const [k, v] = record;
+
+ const { image, ports, build, command, entrypoint, env_file, environment, working_dir, user } = v;
+ const { container_name: name = k } = v;
+
+ var healthcheck: v2Types.DefinitionsHealthcheck = {};
+ if (hasHealthCheck(v)) {
+ Object.entries(v).forEach((item: [string, v2Types.DefinitionsHealthcheck]) => {
+ const [, healthVal] = item;
+ if (healthVal.test !== undefined) {
+ healthcheck = healthVal;
+ }
+ });
+ }
+
+ let portArray: PortMappings = [];
+ ports?.forEach(item => {
+ //For task definitions that use the awsvpc network mode, you should only specify the containerPort.
+ //The hostPort can be left blank or it must be the same value as the containerPort.
+ const [containerPort, hostPort = containerPort] = item.toString().split(':');
+
+ portArray.push({
+ containerPort: parseInt(containerPort, 10),
+ hostPort: parseInt(hostPort, 10),
+ protocol: 'tcp',
+ });
+ });
+
+ const secrets = new Set();
+ if (isV38Service(v)) {
+ v.secrets.filter(s => typeof s === 'string').forEach(s => secrets.add(s));
+ }
+
+ return new Container(
+ build,
+ name,
+ portArray,
+ command,
+ entrypoint,
+ env_file,
+ environment,
+ image,
+ {
+ command: healthcheck.test,
+ ...healthcheck,
+ },
+ working_dir,
+ user,
+ secrets,
+ );
+};
+
+const convertDockerObjectToContainerArray = (yamlObject: v2Types.ConfigSchemaV24Json | v1Types.ConfigSchemaV1Json) => {
+ let containerArr: Container[] = [];
+
+ if (isv1Schema(yamlObject)) {
+ Object.entries(yamlObject).forEach((record: [string, v1Types.DefinitionsService]) => {
+ const container = mapComposeEntriesToContainer(record);
+ containerArr.push(container);
+ });
+ } else {
+ Object.entries(yamlObject.services ?? {}).forEach((record: [string, v2Types.DefinitionsService]) => {
+ const container = mapComposeEntriesToContainer(record);
+ containerArr.push(container);
+ });
+ }
+ return containerArr;
+};
+
+const findServiceDeployment = (
+ yamlObject: v38Types.ComposeSpecification | v2Types.ConfigSchemaV24Json | v1Types.ConfigSchemaV1Json,
+): v38Types.DefinitionsDeployment2 => {
+ let result: v38Types.DefinitionsDeployment2 = {};
+
+ Object.entries(yamlObject.services ?? {}).forEach((record: [string, v38Types.DefinitionsService]) => {
+ const [, v] = record;
+ const { deploy } = v;
+
+ if (deploy !== undefined) {
+ // TODO: This is returning the deploy obj of the last service that had it, it should probably be an array
+ result = deploy!;
+ }
+ });
+
+ return result;
+};
+
+type DockerServiceInfo = {
+ buildspec: string;
+ service: v38Types.DefinitionsDeployment;
+ containers: Container[];
+ secrets: Record;
+};
+export function getContainers(composeContents?: string, dockerfileContents?: string): DockerServiceInfo {
+ //Step 1: Detect if there is a docker-compose or just a Dockerfile.
+ // Just Dockerfile-> create registry using function name, buildspec, zip and put on S3
+ // Compose file -> Begin by parsing it:
+ const dockerCompose = composeContents ? dockerComposeToObject(composeContents) : dockerfileToObject(dockerfileContents);
+
+ const secrets: Record = {};
+
+ const { secrets: composeSecrets = {} }: { secrets?: v38Types.PropertiesSecrets } = dockerCompose;
+
+ for (const secretName of Object.keys(composeSecrets)) {
+ if (composeSecrets[secretName].file) {
+ secrets[composeSecrets[secretName].name ?? secretName] = composeSecrets[secretName].file;
+ }
+ }
+
+ //Step 2: Take compose object and pull all the containers out:
+ const containers = convertDockerObjectToContainerArray(dockerCompose);
+
+ //Step 3: Populate Build mapping for creation of the buildpsec
+ const buildmapping: BuildHashMap = {};
+ containers.forEach(res => {
+ if (typeof res.build === 'object') {
+ //console.log(res.build.args);
+ }
+ if (typeof res.healthcheck === 'object') {
+ //console.log(res.healthcheck.command)
+ }
+ //Step 4: Create ECR Entry if build is specified - TODO with Francisco..... - this will go in registryArn
+ if (res.build != undefined) {
+ let buildContext: string = '';
+
+ if (typeof res.build === 'object') {
+ buildContext = res.build.context!;
+ } else {
+ buildContext = res.build;
+ }
+
+ //Wont need this if statement later, just using for testing
+ /** This will look like this for each container where res.build != undefined:
+ * let registryArn = new ecr.Repository(this, res.name, {});
+ * buildmapping[res.name] = {buildPath: buildContext, registryArn };
+ */
+ buildmapping[res.name] = buildContext;
+ }
+ });
+
+ //Step 5: Generate the buildfiles
+ const buildspec = generateBuildSpec(buildmapping);
+
+ const service = findServiceDeployment(dockerCompose);
+
+ return {
+ buildspec,
+ service,
+ containers,
+ secrets,
+ };
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/container.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/container.ts
new file mode 100644
index 0000000000..df88393757
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/container.ts
@@ -0,0 +1,84 @@
+import { IContainerDefinitions, PortMappings, IBuildConfig, ContainerHealthCheck, IContainerHealthCheckItem } from './types';
+import { ListOrDict } from '../compose-spec/v1';
+
+class Container implements IContainerDefinitions {
+ readonly defaultLogConfiguration = {
+ logDriver: 'awslogs',
+ options: {
+ 'awslogs-stream-prefix': 'ecs', // use cluster name
+ },
+ };
+
+ build: string | IBuildConfig | undefined;
+ name: string;
+ portMappings: PortMappings;
+ logConfiguration = this.defaultLogConfiguration;
+
+ command?: string[];
+ entrypoint?: string[];
+ env_file?: string[];
+ environment?: Record;
+ image?: string;
+ healthcheck?: ContainerHealthCheck;
+ working_dir?: string;
+ user?: string;
+ secrets: Set;
+
+ constructor(
+ build: string | IBuildConfig | undefined, //Really for CodeBuild. Do we need in this class?
+ name: string,
+ portMappings: PortMappings,
+ command?: string | string[] | undefined,
+ entrypoint?: string | string[] | undefined,
+ env_file?: string | string[] | undefined,
+ environment?: ListOrDict | undefined,
+ image?: string | undefined,
+ healthcheck?: IContainerHealthCheckItem | undefined,
+ working_dir?: string | undefined,
+ user?: string | undefined,
+ secrets?: Set | undefined,
+ ) {
+ this.build = build;
+ this.name = name;
+ this.portMappings = portMappings;
+ this.command = [].concat(command);
+ this.entrypoint = [].concat(entrypoint);
+ this.env_file = [].concat(env_file);
+ this.environment = Array.isArray(environment)
+ ? environment.reduce((acc, element) => {
+ const [key, value] = element.split('=');
+
+ acc[key] = value;
+ return acc;
+ }, {} as Record)
+ : (environment as Record);
+ this.image = image;
+
+ this.healthcheck = (({ interval, command, start_period, timeout, retries }) =>
+ command
+ ? {
+ interval: toSeconds(interval),
+ command: [].concat(command),
+ start_period: toSeconds(start_period),
+ timeout: toSeconds(timeout),
+ retries,
+ }
+ : undefined)(healthcheck);
+
+ this.working_dir = working_dir;
+ this.user = user;
+ this.secrets = secrets ?? new Set();
+ }
+}
+
+function toSeconds(str: string | number): number {
+ const [, seconds] = `${str}`.match(/^(\d+)s\s*$/) || [];
+
+ if (seconds === undefined) {
+ return undefined;
+ }
+
+ return parseInt(seconds, 10);
+}
+
+export default Container;
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/service.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/service.ts
new file mode 100644
index 0000000000..1786890422
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/service.ts
@@ -0,0 +1,70 @@
+import { IServiceDefinition, ServiceHealthCheck, DeploymentConfiguration, ContainerConfig, TaskConfig } from './types';
+import Container from './container';
+import * as v38Types from '../compose-spec/v3.8';
+
+//ALB Healthcheck, should be overriden by CLI command
+const DEFAULT_API_HEALTHCHECK = {
+ path: '/',
+ port: 443,
+};
+
+/*This will ensure that there is always at least 1 task running during deployment
+ */
+const DEFAULT_SERVICE_DEPLYMENT_CONFIG = {
+ MaximumPercent: 200,
+ MinimumHealthyPercent: 100,
+};
+const DEFAULT_DESIRED_COUNT = 1; //Should this be 3?
+
+const DEFAULT_TASK_MEMORY_CPU = {
+ memory: 1, //In GB: .5, 1, 2, ...6
+ vCPU: 1, //0.25, .5, 1, 2, ...4
+};
+
+const DEFAULT_CONTAINER_MEMORY_MAX = 1024;
+const DEFAULT_CPU_UNIT_RESERVATION = DEFAULT_CONTAINER_MEMORY_MAX * 0.1; //Do we even need this?
+const DEFAULT_CONTAINER_MEMORY_CPU = {
+ memory: DEFAULT_CONTAINER_MEMORY_MAX,
+ cpu: DEFAULT_CPU_UNIT_RESERVATION,
+};
+
+class Service implements IServiceDefinition {
+ containers: Container[] = [];
+ apiHealthcheck?: ServiceHealthCheck;
+ taskResources: TaskConfig = DEFAULT_TASK_MEMORY_CPU;
+ containerResources: ContainerConfig = DEFAULT_CONTAINER_MEMORY_CPU;
+ deploymentConfiguration: DeploymentConfiguration = DEFAULT_SERVICE_DEPLYMENT_CONFIG;
+ desiredCount: number = DEFAULT_DESIRED_COUNT;
+
+ constructor(
+ containers: Container[],
+ apiHealthcheck: ServiceHealthCheck = DEFAULT_API_HEALTHCHECK,
+ dockerDeploymentConfig?: v38Types.DefinitionsDeployment2,
+ ) {
+ containers.forEach(instance => {
+ this.containers.push(instance);
+ });
+
+ this.apiHealthcheck = apiHealthcheck; //Potentially exposed via CLI inputs, not Docker Compose
+
+ //Docker compose optional inputs//
+
+ /*SERVICE-specific settings*/
+ dockerDeploymentConfig?.replicas !== undefined && (this.desiredCount = dockerDeploymentConfig?.replicas); //Main control, replica ~= task count
+
+ dockerDeploymentConfig?.placement?.max_replicas_per_node !== undefined &&
+ (this.deploymentConfiguration.MaximumPercent = dockerDeploymentConfig?.placement?.max_replicas_per_node);
+
+ /*CONTAINER-specific settings*/
+ //ECS recommends 300-500 MiB as a starting point for web applications.
+ dockerDeploymentConfig?.resources?.limits?.memory !== undefined &&
+ (this.containerResources.memory = (dockerDeploymentConfig?.resources?.limits?.memory.slice(0, -1) as unknown) as number);
+
+ //Note: when you don’t specify any CPU units for a container
+ //ECS intrinsically enforces two Linux CPU shares for the cgroup (which is the minimum allowed).
+ dockerDeploymentConfig?.resources?.limits?.cpus !== undefined &&
+ (this.containerResources.cpu = dockerDeploymentConfig?.resources?.reservations?.cpus as number);
+ }
+}
+
+export default Service;
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/types.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/types.ts
new file mode 100644
index 0000000000..4f844a78e0
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/ecs-objects/types.ts
@@ -0,0 +1,93 @@
+import { ListOrDict } from '../compose-spec/v2';
+
+export interface IServiceDefinition {
+ containers: IContainerDefinitions[];
+ cpu?: number | string;
+ memory?: number | string;
+ tags?: string[];
+}
+
+export type ContainerHealthCheck = {
+ command?: string[];
+ interval?: number;
+ retries?: number;
+ start_period?: number;
+ timeout?: number;
+};
+
+export interface IContainerDefinitions {
+ build: string | IBuildConfig | undefined;
+ image?: string;
+ name: string;
+ // entrypoint?: string | string[] | undefined;
+ logConfiguration: ILogConfiguration;
+ portMappings: PortMappings;
+
+ command?: string[] | undefined;
+ entrypoint?: string[] | undefined;
+ env_file?: string[] | undefined;
+ environment?: Record | undefined;
+ // image?: string | undefined;
+ healthcheck?: ContainerHealthCheck;
+ working_dir?: string;
+ user?: string;
+}
+
+export interface ILogConfiguration {
+ logDriver: string;
+ options: {
+ 'awslogs-stream-prefix': string;
+ };
+}
+
+export interface IBuildConfig {
+ context?: string;
+ dockerfile?: string;
+ args?: ListOrDict;
+}
+
+export type PortMappings = IPortMappingItem[];
+
+interface IPortMappingItem {
+ containerPort: number;
+ hostPort?: number;
+ protocol: string;
+}
+
+export interface IContainerHealthCheckItem {
+ command?: string | string[];
+ interval?: number | string;
+ retries?: number;
+ start_period?: string;
+ timeout?: string;
+}
+
+export type ServiceHealthCheck = IALBHealthCheckItem;
+
+interface IALBHealthCheckItem {
+ path: string;
+ port: number;
+}
+
+export type DeploymentConfiguration = IDeploymentConfiguration;
+
+interface IDeploymentConfiguration {
+ MaximumPercent: number;
+ MinimumHealthyPercent: number;
+}
+
+export type TaskConfig = ITaskConfig;
+
+interface ITaskConfig {
+ memory: number;
+ vCPU: number;
+}
+
+export type ContainerConfig = IContainerConfig;
+
+interface IContainerConfig {
+ memory: number;
+ cpu: number;
+}
+
+export type BuildHashMap = Record;
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/index.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/index.ts
new file mode 100644
index 0000000000..a66729ca10
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/docker-compose/index.ts
@@ -0,0 +1 @@
+export { getContainers } from './converter';
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/ecs-alb-stack.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/ecs-alb-stack.ts
new file mode 100644
index 0000000000..01ca720b38
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/ecs-alb-stack.ts
@@ -0,0 +1,299 @@
+import * as acm from '@aws-cdk/aws-certificatemanager';
+import * as cloudfront from '@aws-cdk/aws-cloudfront';
+import * as cognito from '@aws-cdk/aws-cognito';
+import * as ec2 from '@aws-cdk/aws-ec2';
+import * as ecs from '@aws-cdk/aws-ecs';
+import * as elb2 from '@aws-cdk/aws-elasticloadbalancingv2';
+import * as route53 from '@aws-cdk/aws-route53';
+import * as route53targets from '@aws-cdk/aws-route53-targets';
+import * as cdk from '@aws-cdk/core';
+import { v4 as uuid } from 'uuid';
+import { ContainersStack, ContainersStackProps } from './base-api-stack';
+
+type EcsStackProps = ContainersStackProps &
+ Readonly<{
+ domainName: string;
+ hostedZoneId?: string;
+ authName: string;
+ }>;
+export class EcsAlbStack extends ContainersStack {
+ private readonly userPoolDomain: string;
+
+ constructor(scope: cdk.Construct, id: string, private readonly ecsProps: EcsStackProps) {
+ super(scope, id, {
+ ...ecsProps,
+ skipWait: true,
+ createCloudMapService: false,
+ });
+
+ const { authName, restrictAccess } = ecsProps;
+
+ if (restrictAccess) {
+ const param = this.parameters.get(`auth${authName}HostedUIDomain`);
+
+ this.userPoolDomain = param.valueAsString;
+ }
+
+ this.parameters.get('ParamZipPath').default = 'site.zip';
+
+ this.alb();
+ }
+
+ private alb() {
+ const {
+ domainName,
+ hostedZoneId,
+ exposedContainer: { name: containerName, port },
+ restrictAccess,
+ } = this.ecsProps;
+
+ const sharedSecretHeaderName = 'x-cf-token';
+ const sharedSecretHeader = uuid();
+
+ const userPoolDomain = this.userPoolDomain;
+
+ const vpcId = this.vpcId;
+ const subnets = this.subnets;
+
+ const userPoolArn = cdk.Fn.join('', [
+ 'arn:',
+ cdk.Aws.PARTITION,
+ ':cognito-idp:',
+ cdk.Aws.REGION,
+ ':',
+ cdk.Aws.ACCOUNT_ID,
+ ':userpool/',
+ this.userPoolId,
+ ]);
+
+ const [distributionDomainName, , domainNameSuffix] = domainName.match(/([^\.]+)\.(.*)/);
+ const lbPrefix = `lb-${this.envName}`;
+ const albDomainName = `${lbPrefix}.${domainNameSuffix}`;
+ const wildcardDomainName = `*.${domainNameSuffix}`;
+
+ const wildcardCertificate = new acm.CfnCertificate(this, 'Certificate', {
+ domainName: wildcardDomainName,
+ validationMethod: hostedZoneId ? acm.ValidationMethod.DNS : acm.ValidationMethod.EMAIL,
+ domainValidationOptions: [
+ {
+ domainName: wildcardDomainName,
+ validationDomain: hostedZoneId === undefined ? domainNameSuffix : undefined,
+ hostedZoneId,
+ },
+ ],
+ });
+
+ const userPoolClient = restrictAccess
+ ? new cognito.CfnUserPoolClient(this, 'UserPoolClient', {
+ userPoolId: this.userPoolId,
+ allowedOAuthFlows: [
+ // 'implicit',
+ 'code',
+ ],
+ allowedOAuthFlowsUserPoolClient: true,
+ allowedOAuthScopes: ['profile', 'phone', 'email', 'openid', 'aws.cognito.signin.user.admin'],
+ generateSecret: true,
+ supportedIdentityProviders: ['COGNITO'],
+ callbackUrLs: [`https://${distributionDomainName}/oauth2/idpresponse`],
+ logoutUrLs: [`https://${distributionDomainName}/oauth2/idpresponse`],
+ })
+ : undefined;
+
+ const targetGroup = new elb2.CfnTargetGroup(this, 'TargetGroup', {
+ healthCheckIntervalSeconds: cdk.Duration.seconds(90).toSeconds(),
+ healthCheckPath: '/',
+ healthCheckTimeoutSeconds: cdk.Duration.minutes(1).toSeconds(),
+ healthyThresholdCount: 2,
+ port,
+ protocol: elb2.Protocol.HTTP,
+ targetType: elb2.TargetType.IP,
+ unhealthyThresholdCount: 2,
+ vpcId,
+ });
+
+ const albSecurityGroup = new ec2.CfnSecurityGroup(this, 'AlbSecurityGroup', {
+ vpcId,
+ groupDescription: 'ALB Security Group',
+ securityGroupEgress: [
+ {
+ description: 'Allow all outbound traffic by default',
+ ipProtocol: '-1',
+ cidrIp: '0.0.0.0/0',
+ },
+ ],
+ securityGroupIngress: [
+ {
+ description: 'Allow from anyone on port 443',
+ ipProtocol: ec2.Protocol.TCP,
+ cidrIp: '0.0.0.0/0',
+ fromPort: 443,
+ toPort: 443,
+ },
+ ],
+ });
+
+ const loadBalancer = new elb2.CfnLoadBalancer(this, 'LoadBalancer', {
+ type: 'application',
+ securityGroups: [albSecurityGroup.attrGroupId],
+ loadBalancerAttributes: [
+ {
+ key: 'deletion_protection.enabled',
+ value: 'false',
+ },
+ ],
+ scheme: 'internet-facing',
+ subnets,
+ });
+
+ (this.ecsService.loadBalancers) = [
+ {
+ containerName,
+ containerPort: port,
+ targetGroupArn: targetGroup.ref,
+ },
+ ];
+ (this.ecsServiceSecurityGroup.securityGroupIngress).push({
+ ipProtocol: ec2.Protocol.TCP,
+ fromPort: port,
+ toPort: port,
+ sourceSecurityGroupId: albSecurityGroup.attrGroupId,
+ });
+
+ const listener = new elb2.CfnListener(this, 'AlbListener', {
+ defaultActions: [
+ {
+ fixedResponseConfig: {
+ statusCode: '403',
+ },
+ type: 'fixed-response',
+ },
+ ],
+ loadBalancerArn: loadBalancer.ref,
+ port: 443,
+ protocol: elb2.Protocol.HTTPS,
+ certificates: [{ certificateArn: wildcardCertificate.ref }],
+ });
+
+ this.ecsService.addDependsOn(listener);
+
+ let actionsOrderCounter = 1;
+ const listenerRule = new elb2.CfnListenerRule(this, 'AlbListenerRule', {
+ priority: 1,
+ listenerArn: listener.ref,
+ actions: [].concat(
+ restrictAccess
+ ? {
+ order: actionsOrderCounter++,
+ type: 'authenticate-cognito',
+ authenticateCognitoConfig: {
+ userPoolArn,
+ userPoolClientId: userPoolClient.ref,
+ userPoolDomain,
+ },
+ }
+ : undefined,
+ {
+ order: actionsOrderCounter++,
+ type: 'forward',
+ targetGroupArn: targetGroup.ref,
+ },
+ ),
+ conditions: [
+ {
+ field: 'host-header',
+ hostHeaderConfig: {
+ values: [distributionDomainName],
+ },
+ },
+ {
+ field: 'http-header',
+ httpHeaderConfig: {
+ httpHeaderName: sharedSecretHeaderName,
+ values: [sharedSecretHeader],
+ },
+ },
+ ],
+ });
+
+ this.ecsService.addDependsOn(listenerRule);
+
+ const originId = `${loadBalancer.logicalId}-origin`;
+
+ const distribution = new cloudfront.CfnDistribution(this, 'Distribution', {
+ distributionConfig: {
+ enabled: true,
+ httpVersion: 'http2',
+ ipv6Enabled: true,
+ aliases: [distributionDomainName],
+ defaultCacheBehavior: {
+ forwardedValues: {
+ cookies: { forward: 'all' },
+ headers: ['*'],
+ queryString: true,
+ },
+ targetOriginId: originId,
+ viewerProtocolPolicy: 'redirect-to-https',
+ },
+ origins: [
+ {
+ customOriginConfig: {
+ originProtocolPolicy: 'https-only',
+ },
+ domainName: albDomainName,
+ id: originId,
+ originCustomHeaders: [
+ {
+ headerName: sharedSecretHeaderName,
+ headerValue: sharedSecretHeader,
+ },
+ ],
+ },
+ ],
+ viewerCertificate: {
+ acmCertificateArn: wildcardCertificate.ref,
+ minimumProtocolVersion: 'TLSv1.2_2019',
+ sslSupportMethod: 'sni-only',
+ },
+ },
+ });
+
+ if (hostedZoneId) {
+ new route53.CfnRecordSetGroup(this, 'RecordSetGroup', {
+ hostedZoneId,
+ recordSets: [
+ {
+ name: albDomainName,
+ type: route53.RecordType.A,
+ aliasTarget: {
+ hostedZoneId: loadBalancer.attrCanonicalHostedZoneId,
+ dnsName: loadBalancer.attrDnsName,
+ },
+ },
+ {
+ name: distributionDomainName,
+ type: route53.RecordType.A,
+ aliasTarget: {
+ hostedZoneId: route53targets.CloudFrontTarget.CLOUDFRONT_ZONE_ID,
+ dnsName: distribution.attrDomainName,
+ },
+ },
+ ],
+ });
+ }
+
+ new cdk.CfnOutput(this, 'PipelineUrl', {
+ value: cdk.Fn.join('', [
+ 'https://',
+ cdk.Aws.REGION,
+ '.console.aws.amazon.com/codesuite/codepipeline/pipelines/',
+ this.getPipelineName(),
+ '/view',
+ ]),
+ });
+
+ new cdk.CfnOutput(this, 'LoadBalancerAliasDomainName', { value: loadBalancer.attrDnsName });
+ new cdk.CfnOutput(this, 'LoadBalancerCnameDomainName', { value: albDomainName });
+ new cdk.CfnOutput(this, 'CloudfrontDistributionAliasDomainName', { value: distribution.attrDomainName });
+ new cdk.CfnOutput(this, 'CloudfrontDistributionCnameDomainName', { value: distributionDomainName });
+ }
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/ecs-apigw-stack.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/ecs-apigw-stack.ts
new file mode 100644
index 0000000000..4d740dd29d
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/ecs-apigw-stack.ts
@@ -0,0 +1,97 @@
+import * as apigw2 from '@aws-cdk/aws-apigatewayv2';
+import * as cdk from '@aws-cdk/core';
+import { ContainersStack, ContainersStackProps } from './base-api-stack';
+import { API_TYPE } from './service-walkthroughs/containers-walkthrough';
+
+type EcsStackProps = Readonly<
+ ContainersStackProps & {
+ apiType: API_TYPE;
+ }
+>;
+export class EcsStack extends ContainersStack {
+ constructor(scope: cdk.Construct, id: string, private readonly ecsProps: EcsStackProps) {
+ super(scope, id, {
+ ...ecsProps,
+ createCloudMapService: true,
+ });
+
+ const { apiType } = this.ecsProps;
+
+ const { api } = this.apiGateway();
+
+ switch (apiType) {
+ case API_TYPE.GRAPHQL:
+ new cdk.CfnOutput(this, 'GraphQLAPIEndpointOutput', { value: api.attrApiEndpoint });
+ break;
+ case API_TYPE.REST:
+ new cdk.CfnOutput(this, 'ApiName', { value: ecsProps.apiName });
+ new cdk.CfnOutput(this, 'RootUrl', { value: api.attrApiEndpoint });
+ break;
+ default:
+ const invalidApiType: never = apiType;
+ throw new Error(`Invalid api type ${invalidApiType}`);
+ }
+ }
+
+ private apiGateway() {
+ const { apiName } = this.ecsProps;
+
+ const api = new apigw2.CfnApi(this, 'Api', {
+ name: `${this.envName}-${apiName}`,
+ protocolType: 'HTTP',
+ corsConfiguration: {
+ allowHeaders: ['*'],
+ allowOrigins: ['*'],
+ allowMethods: Object.values(apigw2.HttpMethod).filter(m => m !== apigw2.HttpMethod.ANY),
+ },
+ });
+
+ new apigw2.CfnStage(this, 'Stage', {
+ apiId: cdk.Fn.ref(api.logicalId),
+ stageName: '$default',
+ autoDeploy: true,
+ });
+
+ const integration = new apigw2.CfnIntegration(this, 'ANYIntegration', {
+ apiId: cdk.Fn.ref(api.logicalId),
+ integrationType: apigw2.HttpIntegrationType.HTTP_PROXY,
+ connectionId: this.vpcLinkId,
+ connectionType: apigw2.HttpConnectionType.VPC_LINK,
+ integrationMethod: 'ANY',
+ integrationUri: this.cloudMapService.attrArn,
+ payloadFormatVersion: '1.0',
+ });
+
+ const authorizer = new apigw2.CfnAuthorizer(this, 'Authorizer', {
+ name: `${apiName}Authorizer`,
+ apiId: cdk.Fn.ref(api.logicalId),
+ authorizerType: 'JWT',
+ jwtConfiguration: {
+ audience: [this.appClientId],
+ issuer: cdk.Fn.join('', ['https://cognito-idp.', cdk.Aws.REGION, '.amazonaws.com/', this.userPoolId]),
+ },
+ identitySource: ['$request.header.Authorization'],
+ });
+
+ authorizer.cfnOptions.condition = this.isAuthCondition;
+
+ new apigw2.CfnRoute(this, 'DefaultRoute', {
+ apiId: cdk.Fn.ref(api.logicalId),
+ routeKey: '$default',
+ target: cdk.Fn.join('', ['integrations/', cdk.Fn.ref(integration.logicalId)]),
+ authorizationScopes: [],
+ authorizationType: cdk.Fn.conditionIf(this.isAuthCondition.logicalId, 'JWT', 'NONE'),
+ authorizerId: cdk.Fn.conditionIf(this.isAuthCondition.logicalId, cdk.Fn.ref(authorizer.logicalId), ''),
+ });
+
+ new apigw2.CfnRoute(this, 'OptionsRoute', {
+ apiId: cdk.Fn.ref(api.logicalId),
+ routeKey: 'OPTIONS /{proxy+}',
+ target: cdk.Fn.join('', ['integrations/', cdk.Fn.ref(integration.logicalId)]),
+ });
+
+ return {
+ api,
+ };
+ }
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/index.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/index.ts
new file mode 100644
index 0000000000..65227effd0
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/index.ts
@@ -0,0 +1,328 @@
+import { $TSAny, $TSContext, $TSObject, AmplifySupportedService, exitOnNextTick, NotImplementedError } from 'amplify-cli-core';
+import { UpdateApiRequest } from 'amplify-headless-interface';
+import { printer } from 'amplify-prompts';
+import inquirer from 'inquirer';
+import * as path from 'path';
+import { category } from '../../category-constants';
+import { ApigwInputState } from './apigw-input-state';
+import { getCfnApiArtifactHandler } from './cfn-api-artifact-handler';
+import { addResource as addContainer, updateResource as updateContainer } from './containers-handler';
+import { legacyAddResource } from './legacy-add-resource';
+import {
+ API_TYPE,
+ getPermissionPolicies as getContainerPermissionPolicies,
+ ServiceConfiguration,
+} from './service-walkthroughs/containers-walkthrough';
+import { datasourceMetadataFor, getServiceWalkthrough, serviceMetadataFor } from './utils/dynamic-imports';
+import { editSchemaFlow } from './utils/edit-schema-flow';
+import { serviceWalkthroughResultToAddApiRequest } from './utils/service-walkthrough-result-to-add-api-request';
+
+export async function addAdminQueriesApi(
+ context: $TSContext,
+ apiProps: { apiName: string; functionName: string; authResourceName: string; dependsOn: $TSObject[] },
+) {
+ const apigwInputState = new ApigwInputState(context, apiProps.apiName);
+ return apigwInputState.addAdminQueriesResource(apiProps);
+}
+
+export async function updateAdminQueriesApi(
+ context: $TSContext,
+ apiProps: { apiName: string; functionName: string; authResourceName: string; dependsOn: $TSObject[] },
+) {
+ const apigwInputState = new ApigwInputState(context, apiProps.apiName);
+ // Check for migration
+
+ if (!apigwInputState.cliInputsFileExists()) {
+ await apigwInputState.migrateAdminQueries(apiProps);
+ } else {
+ return apigwInputState.updateAdminQueriesResource(apiProps);
+ }
+}
+
+export async function console(context: $TSContext, service: string) {
+ const { serviceWalkthroughFilename } = await serviceMetadataFor(service);
+ const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename);
+ const { openConsole } = await import(serviceWalkthroughSrc);
+
+ if (!openConsole) {
+ const errMessage = 'Opening console functionality not available for this option';
+ printer.error(errMessage);
+ await context.usageData.emitError(new NotImplementedError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ return openConsole(context);
+}
+
+async function addContainerResource(context: $TSContext, service: string, options, apiType: API_TYPE) {
+ const serviceWalkthroughFilename = 'containers-walkthrough.js';
+
+ const serviceWalkthrough = await getServiceWalkthrough(serviceWalkthroughFilename);
+ const serviceWalkthroughPromise: Promise<$TSAny> = serviceWalkthrough(context, apiType);
+
+ return await addContainer(serviceWalkthroughPromise, context, category, service, options, apiType);
+}
+
+async function addNonContainerResource(context: $TSContext, service: string, options) {
+ const serviceMetadata = await serviceMetadataFor(service);
+ const { serviceWalkthroughFilename, defaultValuesFilename } = serviceMetadata;
+ const serviceWalkthrough = await getServiceWalkthrough(serviceWalkthroughFilename);
+
+ const serviceWalkthroughPromise: Promise<$TSAny> = serviceWalkthrough(context, serviceMetadata);
+ switch (service) {
+ case AmplifySupportedService.APPSYNC:
+ const walkthroughResult = await serviceWalkthroughPromise;
+ const askToEdit = walkthroughResult.askToEdit;
+ const apiName = await getCfnApiArtifactHandler(context).createArtifacts(serviceWalkthroughResultToAddApiRequest(walkthroughResult));
+ if (askToEdit) {
+ await editSchemaFlow(context, apiName);
+ }
+ return apiName;
+ case AmplifySupportedService.APIGW:
+ const apigwInputState = new ApigwInputState(context);
+ return apigwInputState.addApigwResource(serviceWalkthroughPromise, options);
+ default:
+ return legacyAddResource(serviceWalkthroughPromise, context, category, service, options);
+ }
+}
+
+export async function addResource(context: $TSContext, service: string, options) {
+ let useContainerResource = false;
+ let apiType = API_TYPE.GRAPHQL;
+
+ if (isContainersEnabled(context)) {
+ switch (service) {
+ case AmplifySupportedService.APPSYNC:
+ useContainerResource = await isGraphQLContainer();
+ apiType = API_TYPE.GRAPHQL;
+ break;
+ case AmplifySupportedService.APIGW:
+ useContainerResource = await isRestContainer();
+ apiType = API_TYPE.REST;
+ break;
+ default:
+ throw new Error(`${service} not exists`);
+ }
+ }
+
+ return useContainerResource
+ ? addContainerResource(context, service, options, apiType)
+ : addNonContainerResource(context, service, options);
+}
+
+function isContainersEnabled(context: $TSContext) {
+ const { frontend } = context.amplify.getProjectConfig();
+ if (frontend) {
+ const { config: { ServerlessContainers = false } = {} } = context.amplify.getProjectConfig()[frontend] || {};
+
+ return ServerlessContainers;
+ }
+
+ return false;
+}
+
+async function isGraphQLContainer(): Promise {
+ const { graphqlSelection } = await inquirer.prompt({
+ name: 'graphqlSelection',
+ message: 'Which service would you like to use',
+ type: 'list',
+ choices: [
+ {
+ name: AmplifySupportedService.APPSYNC,
+ value: false,
+ },
+ {
+ name: 'AWS Fargate (Container-based)',
+ value: true,
+ },
+ ],
+ });
+
+ return graphqlSelection;
+}
+
+async function isRestContainer() {
+ const { restSelection } = await inquirer.prompt({
+ name: 'restSelection',
+ message: 'Which service would you like to use',
+ type: 'list',
+ choices: [
+ {
+ name: 'API Gateway + Lambda',
+ value: false,
+ },
+ {
+ name: 'API Gateway + AWS Fargate (Container-based)',
+ value: true,
+ },
+ ],
+ });
+
+ return restSelection;
+}
+
+export async function updateResource(context: $TSContext, category: string, service: string, options) {
+ const allowContainers = options?.allowContainers ?? true;
+ let useContainerResource = false;
+ let apiType = API_TYPE.GRAPHQL;
+ if (allowContainers && isContainersEnabled(context)) {
+ const { hasAPIGatewayContainerResource, hasAPIGatewayLambdaResource, hasGraphQLAppSyncResource, hasGraphqlContainerResource } =
+ await describeApiResourcesBySubCategory(context);
+
+ switch (service) {
+ case AmplifySupportedService.APPSYNC:
+ if (hasGraphQLAppSyncResource && hasGraphqlContainerResource) {
+ useContainerResource = await isGraphQLContainer();
+ } else if (hasGraphqlContainerResource) {
+ useContainerResource = true;
+ } else {
+ useContainerResource = false;
+ }
+ apiType = API_TYPE.GRAPHQL;
+ break;
+ case AmplifySupportedService.APIGW:
+ if (hasAPIGatewayContainerResource && hasAPIGatewayLambdaResource) {
+ useContainerResource = await isRestContainer();
+ } else if (hasAPIGatewayContainerResource) {
+ useContainerResource = true;
+ } else {
+ useContainerResource = false;
+ }
+ apiType = API_TYPE.REST;
+ break;
+ default:
+ throw new Error(`${service} not exists`);
+ }
+ }
+
+ return useContainerResource ? updateContainerResource(context, category, service, apiType) : updateNonContainerResource(context, service);
+}
+
+async function describeApiResourcesBySubCategory(context: $TSContext) {
+ const { allResources } = await context.amplify.getResourceStatus();
+ const resources = allResources.filter(resource => resource.category === category && resource.mobileHubMigrated !== true);
+
+ let hasAPIGatewayContainerResource = false;
+ let hasAPIGatewayLambdaResource = false;
+ let hasGraphQLAppSyncResource = false;
+ let hasGraphqlContainerResource = false;
+
+ resources.forEach(resource => {
+ hasAPIGatewayContainerResource =
+ hasAPIGatewayContainerResource || (resource.service === 'ElasticContainer' && resource.apiType === API_TYPE.REST);
+
+ hasAPIGatewayLambdaResource = hasAPIGatewayLambdaResource || resource.service === AmplifySupportedService.APIGW;
+
+ hasGraphQLAppSyncResource = hasGraphQLAppSyncResource || resource.service === AmplifySupportedService.APPSYNC;
+
+ hasGraphqlContainerResource =
+ hasGraphqlContainerResource || (resource.service === 'ElasticContainer' && resource.apiType === API_TYPE.GRAPHQL);
+ });
+
+ return {
+ hasAPIGatewayLambdaResource,
+ hasAPIGatewayContainerResource,
+ hasGraphQLAppSyncResource,
+ hasGraphqlContainerResource,
+ };
+}
+
+async function updateContainerResource(context: $TSContext, category: string, service: string, apiType: API_TYPE) {
+ const serviceWalkthroughFilename = 'containers-walkthrough';
+ const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename);
+ const { updateWalkthrough } = await import(serviceWalkthroughSrc);
+
+ if (!updateWalkthrough) {
+ const errMessage = 'Update functionality not available for this option';
+ printer.error(errMessage);
+ await context.usageData.emitError(new NotImplementedError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ const updateWalkthroughPromise: Promise = updateWalkthrough(context, apiType);
+
+ updateContainer(updateWalkthroughPromise, context, category);
+}
+
+async function updateNonContainerResource(context: $TSContext, service: string) {
+ const serviceMetadata = await serviceMetadataFor(service);
+ const { defaultValuesFilename, serviceWalkthroughFilename } = serviceMetadata;
+ const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename);
+ const { updateWalkthrough } = await import(serviceWalkthroughSrc);
+
+ if (!updateWalkthrough) {
+ const errMessage = 'Update functionality not available for this option';
+ printer.error(errMessage);
+ await context.usageData.emitError(new NotImplementedError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ const updateWalkthroughPromise: Promise = updateWalkthrough(context, defaultValuesFilename, serviceMetadata);
+
+ switch (service) {
+ case AmplifySupportedService.APPSYNC:
+ return updateWalkthroughPromise.then(getCfnApiArtifactHandler(context).updateArtifacts);
+ default:
+ const apigwInputState = new ApigwInputState(context);
+ return apigwInputState.updateApigwResource(updateWalkthroughPromise);
+ }
+}
+
+export async function migrateResource(context: $TSContext, projectPath: string, service: string, resourceName: string) {
+ if (service === 'ElasticContainer') {
+ return migrateResourceContainer(context, projectPath, service, resourceName);
+ } else {
+ return migrateResourceNonContainer(context, projectPath, service, resourceName);
+ }
+}
+
+async function migrateResourceContainer(context: $TSContext, projectPath: string, service: string, resourceName: string) {
+ printer.info(`No migration required for ${resourceName}`);
+ return;
+}
+
+async function migrateResourceNonContainer(context: $TSContext, projectPath: string, service: string, resourceName: string) {
+ const serviceMetadata = await serviceMetadataFor(service);
+ const { serviceWalkthroughFilename } = serviceMetadata;
+ const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename);
+ const { migrate } = await import(serviceWalkthroughSrc);
+
+ if (!migrate) {
+ printer.info(`No migration required for ${resourceName}`);
+ return;
+ }
+
+ return await migrate(context, projectPath, resourceName);
+}
+
+export async function addDatasource(context: $TSContext, category, datasource) {
+ const serviceMetadata = await datasourceMetadataFor(datasource);
+ const { serviceWalkthroughFilename } = serviceMetadata;
+ return (await getServiceWalkthrough(serviceWalkthroughFilename))(context, serviceMetadata);
+}
+
+export async function getPermissionPolicies(context: $TSContext, service: string, resourceName: string, crudOptions) {
+ if (service === 'ElasticContainer') {
+ return getPermissionPoliciesContainer(context, service, resourceName, crudOptions);
+ } else {
+ return getPermissionPoliciesNonContainer(service, resourceName, crudOptions);
+ }
+}
+
+async function getPermissionPoliciesContainer(context: $TSContext, service: string, resourceName: string, crudOptions) {
+ return getContainerPermissionPolicies(context, service, resourceName, crudOptions);
+}
+
+async function getPermissionPoliciesNonContainer(service: string, resourceName: string, crudOptions: string[]) {
+ const serviceMetadata = await serviceMetadataFor(service);
+ const { serviceWalkthroughFilename } = serviceMetadata;
+ const serviceWalkthroughSrc = path.join(__dirname, 'service-walkthroughs', serviceWalkthroughFilename);
+ const { getIAMPolicies } = await import(serviceWalkthroughSrc);
+
+ if (!getIAMPolicies) {
+ printer.info(`No policies found for ${resourceName}`);
+ return;
+ }
+
+ return getIAMPolicies(resourceName, crudOptions);
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-add-resource.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-add-resource.ts
new file mode 100644
index 0000000000..29ced6ba3b
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-add-resource.ts
@@ -0,0 +1,79 @@
+import { $TSAny, $TSContext, $TSObject, isResourceNameUnique, JSONUtilities, pathManager } from 'amplify-cli-core';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { cfnParametersFilename, parametersFileName, rootAssetDir } from './aws-constants';
+import { serviceMetadataFor } from './utils/dynamic-imports';
+
+// this is the old logic for generating resources in the project directory
+// it is still used for adding REST APIs
+export const legacyAddResource = async (
+ serviceWalkthroughPromise: Promise<$TSAny>,
+ context: $TSContext,
+ category: string,
+ service: string,
+ options: $TSObject,
+) => {
+ let answers;
+ let { cfnFilename } = await serviceMetadataFor(service);
+
+ const result = await serviceWalkthroughPromise;
+
+ if (result.answers) {
+ ({ answers } = result);
+ options.dependsOn = result.dependsOn;
+ } else {
+ answers = result;
+ }
+ if (result.output) {
+ options.output = result.output;
+ }
+ if (!result.noCfnFile) {
+ if (answers.customCfnFile) {
+ cfnFilename = answers.customCfnFile;
+ }
+ addPolicyResourceNameToPaths(answers.paths);
+ copyCfnTemplate(context, category, answers, cfnFilename);
+
+ const parameters = { ...answers };
+ const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, category, parameters.resourceName);
+
+ isResourceNameUnique(category, parameters.resourceName);
+
+ fs.ensureDirSync(resourceDirPath);
+
+ const parametersFilePath = path.join(resourceDirPath, parametersFileName);
+ JSONUtilities.writeJson(parametersFilePath, parameters);
+
+ const cfnParametersFilePath = path.join(resourceDirPath, cfnParametersFilename);
+ JSONUtilities.writeJson(cfnParametersFilePath, {});
+ }
+ context.amplify.updateamplifyMetaAfterResourceAdd(category, answers.resourceName, options);
+ return answers.resourceName;
+};
+
+// exported because the update flow still uses this method directly for now
+export const copyCfnTemplate = (context: $TSContext, category: string, options, cfnFilename) => {
+ const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, category, options.resourceName);
+
+ const copyJobs = [
+ {
+ dir: path.join(rootAssetDir, 'cloudformation-templates'),
+ template: cfnFilename,
+ target: path.join(resourceDirPath, `${options.resourceName}-cloudformation-template.json`),
+ },
+ ];
+
+ // copy over the files
+ return context.amplify.copyBatch(context, copyJobs, options, true, false);
+};
+
+export const addPolicyResourceNameToPaths = paths => {
+ if (Array.isArray(paths)) {
+ paths.forEach(p => {
+ const pathName = p.name;
+ if (typeof pathName === 'string') {
+ p.policyResourceName = pathName.replace(/{[a-zA-Z0-9\-]+}/g, '*');
+ }
+ });
+ }
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-update-resource.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-update-resource.ts
new file mode 100644
index 0000000000..930523a24a
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/legacy-update-resource.ts
@@ -0,0 +1,35 @@
+import { $TSAny, $TSContext, JSONUtilities, pathManager } from 'amplify-cli-core';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { parametersFileName } from './aws-constants';
+import { addPolicyResourceNameToPaths, copyCfnTemplate } from './legacy-add-resource';
+import { serviceMetadataFor } from './utils/dynamic-imports';
+
+export const legacyUpdateResource = async (updateWalkthroughPromise: Promise<$TSAny>, context: $TSContext, category: string, service) => {
+ let answers;
+ let { cfnFilename } = await serviceMetadataFor(service);
+ const result = await updateWalkthroughPromise;
+ const options: $TSAny = {};
+ if (result) {
+ if (result.answers) {
+ ({ answers } = result);
+ options.dependsOn = result.dependsOn;
+ } else {
+ answers = result;
+ }
+
+ if (!result.noCfnFile) {
+ if (answers.customCfnFile) {
+ cfnFilename = answers.customCfnFile;
+ }
+ addPolicyResourceNameToPaths(answers.paths);
+ copyCfnTemplate(context, category, answers, cfnFilename);
+ const parameters = { ...answers };
+ const resourceDirPath = pathManager.getResourceDirectoryPath(undefined, category, parameters.resourceName);
+ fs.ensureDirSync(resourceDirPath);
+ const parametersFilePath = path.join(resourceDirPath, parametersFileName);
+ JSONUtilities.writeJson(parametersFilePath, parameters);
+ context.amplify.updateamplifyMetaAfterResourceUpdate(category, answers.resourceName, 'dependsOn', answers.dependsOn);
+ }
+ }
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/pipeline-with-awaiter.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/pipeline-with-awaiter.ts
new file mode 100644
index 0000000000..2711c0f674
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/pipeline-with-awaiter.ts
@@ -0,0 +1,372 @@
+import * as codebuild from '@aws-cdk/aws-codebuild';
+import * as codepipeline from '@aws-cdk/aws-codepipeline';
+import * as codepipelineactions from '@aws-cdk/aws-codepipeline-actions';
+import * as ecr from '@aws-cdk/aws-ecr';
+import * as ecs from '@aws-cdk/aws-ecs';
+import * as iam from '@aws-cdk/aws-iam';
+import * as lambda from '@aws-cdk/aws-lambda';
+import * as s3 from '@aws-cdk/aws-s3';
+import * as cdk from '@aws-cdk/core';
+import * as custom from '@aws-cdk/custom-resources';
+import * as fs from 'fs-extra';
+import * as path from 'path';
+import { DEPLOYMENT_MECHANISM } from './base-api-stack';
+import { getGitHubOwnerRepoFromPath } from './utils/github';
+
+type PipelineAwaiterProps = {
+ pipeline: codepipeline.Pipeline;
+ artifactBucketName?: string;
+ artifactKey?: string;
+ deploymentMechanism: DEPLOYMENT_MECHANISM;
+};
+
+export type GitHubSourceActionInfo = {
+ path: string;
+ tokenSecretArn: string;
+};
+
+// TODO update when CDK is updated to newer version that supports NODEJS_14_X
+const lambdaRuntimeNodeVersion = lambda.Runtime.NODEJS_12_X;
+
+const lambdasDir = path.resolve(__dirname, '../../../resources/awscloudformation/lambdas');
+
+class PipelineAwaiter extends cdk.Construct {
+ constructor(scope: cdk.Construct, id: string, props: PipelineAwaiterProps) {
+ const { pipeline, artifactBucketName, artifactKey, deploymentMechanism } = props;
+
+ const { pipelineArn, pipelineName } = pipeline;
+
+ const pipelineOnEventCodeFilePath = path.join(lambdasDir, 'pipeline-on-event.js');
+ const onEventHandlerCode = fs.readFileSync(pipelineOnEventCodeFilePath, 'utf8');
+
+ const onEventHandler = new lambda.Function(scope, `${id}CustomEventHandler`, {
+ runtime: lambdaRuntimeNodeVersion,
+ handler: 'index.handler',
+ code: lambda.Code.fromInline(onEventHandlerCode),
+ timeout: cdk.Duration.seconds(15),
+ });
+
+ const pipelineCodeFilePath = path.join(lambdasDir, 'pipeline.js');
+ const isCompleteHandlerCode = fs.readFileSync(pipelineCodeFilePath, 'utf8');
+
+ const isCompleteHandler = new lambda.Function(scope, `${id}CustomCompleteHandler`, {
+ runtime: lambdaRuntimeNodeVersion,
+ handler: 'index.handler',
+ timeout: cdk.Duration.seconds(15),
+ code: lambda.Code.fromInline(isCompleteHandlerCode),
+ });
+ isCompleteHandler.addToRolePolicy(
+ new iam.PolicyStatement({
+ effect: iam.Effect.ALLOW,
+ actions: ['codepipeline:GetPipeline', 'codepipeline:ListPipelineExecutions'],
+ resources: [pipelineArn],
+ }),
+ );
+ isCompleteHandler.addToRolePolicy(
+ new iam.PolicyStatement({
+ effect: iam.Effect.ALLOW,
+ actions: ['cloudformation:DescribeStacks'],
+ resources: [cdk.Stack.of(scope).stackId],
+ }),
+ );
+
+ const myProvider = new custom.Provider(scope, `${id}MyProvider`, {
+ onEventHandler,
+ isCompleteHandler,
+ queryInterval: cdk.Duration.seconds(10),
+ });
+
+ new cdk.CustomResource(scope, `Deployment${id}`, {
+ serviceToken: myProvider.serviceToken,
+ properties: {
+ artifactBucketName,
+ artifactKey,
+ pipelineName,
+ deploymentMechanism,
+ },
+ });
+
+ super(scope, id);
+ }
+}
+
+export class PipelineWithAwaiter extends cdk.Construct {
+ pipelineName: string;
+ constructor(
+ scope: cdk.Construct,
+ id: string,
+ {
+ skipWait = false,
+ bucket,
+ s3SourceActionKey,
+ service,
+ deploymentMechanism,
+ gitHubSourceActionInfo,
+ containersInfo,
+ desiredCount,
+ envName,
+ }: {
+ skipWait?: boolean;
+ bucket: s3.IBucket;
+ s3SourceActionKey?: string;
+ deploymentMechanism: DEPLOYMENT_MECHANISM;
+ gitHubSourceActionInfo?: GitHubSourceActionInfo;
+ service: ecs.CfnService;
+ containersInfo: {
+ container: ecs.ContainerDefinition;
+ repository: ecr.IRepository;
+ }[];
+ desiredCount: number;
+ envName: string;
+ },
+ ) {
+ super(scope, id);
+
+ const sourceOutput = new codepipeline.Artifact('SourceArtifact');
+ const buildOutput = new codepipeline.Artifact('BuildArtifact');
+
+ const codeBuildProject = new codebuild.PipelineProject(scope, `${id}CodeBuildProject`, {
+ environment: {
+ buildImage: codebuild.LinuxBuildImage.STANDARD_4_0,
+ // See: https://docs.aws.amazon.com/codebuild/latest/userguide/troubleshooting.html#troubleshooting-cannot-connect-to-docker-daemon
+ privileged: true,
+ },
+ });
+
+ if (gitHubSourceActionInfo && gitHubSourceActionInfo.tokenSecretArn) {
+ codeBuildProject.addToRolePolicy(
+ new iam.PolicyStatement({
+ effect: iam.Effect.ALLOW,
+ actions: [
+ 'secretsmanager:GetRandomPassword',
+ 'secretsmanager:GetResourcePolicy',
+ 'secretsmanager:GetSecretValue',
+ 'secretsmanager:DescribeSecret',
+ 'secretsmanager:ListSecretVersionIds',
+ ],
+ resources: [gitHubSourceActionInfo.tokenSecretArn],
+ }),
+ );
+ }
+
+ codeBuildProject.role.addToPrincipalPolicy(
+ new iam.PolicyStatement({
+ resources: ['*'],
+ actions: [
+ 'ecr:GetAuthorizationToken',
+ 'ecr:BatchGetImage',
+ 'ecr:BatchGetDownloadUrlForLayer',
+ 'ecr:InitiateLayerUpload',
+ 'ecr:BatchCheckLayerAvailability',
+ 'ecr:UploadLayerPart',
+ 'ecr:CompleteLayerUpload',
+ 'ecr:PutImage',
+ ],
+ effect: iam.Effect.ALLOW,
+ }),
+ );
+
+ const prebuildStages = createPreBuildStages(scope, {
+ bucket,
+ s3SourceActionKey,
+ gitHubSourceActionInfo,
+ roleName: 'UpdateSource',
+ sourceOutput,
+ });
+
+ const environmentVariables = containersInfo.reduce(
+ (acc, c) => {
+ acc[`${c.container.containerName}_REPOSITORY_URI`] = {
+ type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
+ value: c.repository.repositoryUri,
+ };
+
+ return acc;
+ },
+ {
+ AWS_ACCOUNT_ID: {
+ type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
+ value: cdk.Aws.ACCOUNT_ID,
+ },
+ } as Record,
+ );
+
+ const stagesWithDeploy = ([] as codepipeline.StageOptions[]).concat(prebuildStages, [
+ {
+ stageName: 'Build',
+ actions: [
+ new codepipelineactions.CodeBuildAction({
+ actionName: 'Build',
+ type: codepipelineactions.CodeBuildActionType.BUILD,
+ project: codeBuildProject,
+ input: sourceOutput,
+ outputs: [buildOutput],
+ environmentVariables,
+ }),
+ ],
+ },
+ {
+ stageName: 'Predeploy',
+ actions: [
+ new codepipelineactions.LambdaInvokeAction({
+ actionName: 'Predeploy',
+ lambda: (() => {
+ const preDeployCodeFilePath = path.join(lambdasDir, 'predeploy.js');
+ const lambdaHandlerCode = fs.readFileSync(preDeployCodeFilePath, 'utf8');
+
+ const action = new lambda.Function(scope, 'PreDeployLambda', {
+ code: lambda.Code.fromInline(lambdaHandlerCode),
+ handler: 'index.handler',
+ runtime: lambdaRuntimeNodeVersion,
+ environment: {
+ DESIRED_COUNT: `${desiredCount}`,
+ CLUSTER_NAME: service.cluster,
+ SERVICE_NAME: service.serviceName,
+ },
+ timeout: cdk.Duration.seconds(15),
+ });
+
+ action.addToRolePolicy(
+ new iam.PolicyStatement({
+ actions: ['ecs:UpdateService'],
+ effect: iam.Effect.ALLOW,
+ resources: [cdk.Fn.ref(service.logicalId)],
+ }),
+ );
+
+ return action;
+ })(),
+ inputs: [],
+ outputs: [],
+ }),
+ ],
+ },
+ {
+ stageName: 'Deploy',
+ actions: [
+ new codepipelineactions.EcsDeployAction({
+ actionName: 'Deploy',
+ service: new (class extends cdk.Construct implements ecs.IBaseService {
+ cluster = {
+ clusterName: service.cluster,
+ env: {},
+ } as ecs.ICluster;
+ serviceArn = cdk.Fn.ref(service.attrServiceArn);
+ serviceName = service.serviceName;
+ stack = cdk.Stack.of(this);
+ env = {} as any;
+ node = service.node;
+ })(this, 'tmpService'),
+ input: buildOutput,
+ }),
+ ],
+ },
+ ]);
+
+ this.pipelineName = `${envName}-${service.serviceName}`;
+
+ const pipeline = new codepipeline.Pipeline(scope, `${id}Pipeline`, {
+ pipelineName: this.pipelineName,
+ crossAccountKeys: false,
+ artifactBucket: bucket,
+ stages: stagesWithDeploy,
+ });
+
+ pipeline.node.addDependency(service);
+
+ if (!skipWait) {
+ new PipelineAwaiter(scope, 'Awaiter', {
+ pipeline,
+ artifactBucketName: bucket.bucketName,
+ artifactKey: s3SourceActionKey,
+ deploymentMechanism,
+ });
+ }
+
+ new cdk.CfnOutput(scope, 'PipelineName', { value: this.pipelineName });
+ }
+
+ getPipelineName(): string {
+ return this.pipelineName;
+ }
+}
+
+function createPreBuildStages(
+ scope: cdk.Construct,
+ {
+ bucket,
+ s3SourceActionKey,
+ gitHubSourceActionInfo,
+ sourceOutput,
+ roleName,
+ }: {
+ bucket: s3.IBucket;
+ s3SourceActionKey: string;
+ gitHubSourceActionInfo?: GitHubSourceActionInfo;
+ sourceOutput: codepipeline.Artifact;
+ roleName: string;
+ },
+) {
+ const stages: codepipeline.StageOptions[] = [];
+
+ const stage = {
+ stageName: 'Source',
+ actions: [],
+ };
+
+ stages.push(stage);
+
+ if (gitHubSourceActionInfo && gitHubSourceActionInfo.path) {
+ const { path, tokenSecretArn } = gitHubSourceActionInfo;
+ const { owner, repo, branch } = getGitHubOwnerRepoFromPath(path);
+
+ const preBuildOutput = new codepipeline.Artifact('PreBuildArtifact');
+
+ stage.actions = [
+ new codepipelineactions.GitHubSourceAction({
+ actionName: 'Source',
+ oauthToken: cdk.SecretValue.secretsManager(tokenSecretArn),
+ owner,
+ repo,
+ branch,
+ output: preBuildOutput,
+ }),
+ ];
+
+ stages.push({
+ stageName: 'PreBuild',
+ actions: [
+ new codepipelineactions.LambdaInvokeAction({
+ actionName: 'PreBuild',
+ lambda: new lambda.Function(scope, 'PreBuildLambda', {
+ code: lambda.S3Code.fromBucket(bucket, 'codepipeline-action-buildspec-generator-lambda.zip'),
+ handler: 'index.handler',
+ runtime: lambdaRuntimeNodeVersion,
+ timeout: cdk.Duration.seconds(15),
+ }),
+ inputs: [preBuildOutput],
+ outputs: [sourceOutput],
+ }),
+ ],
+ });
+ } else {
+ stage.actions = [
+ new codepipelineactions.S3SourceAction({
+ actionName: 'Source',
+ bucket,
+ bucketKey: s3SourceActionKey,
+ output: sourceOutput,
+ }),
+ ];
+ }
+
+ return stages;
+}
+
+export type ContainerStackProps = {
+ deploymentBucket: string;
+ containerPort: number;
+ awaiterZipPath: string;
+ gitHubPath?: string;
+ gitHubTokenSecretsManagerArn: string;
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/prompt-to-add-api-key.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/prompt-to-add-api-key.ts
new file mode 100644
index 0000000000..4e3a92b5ea
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/prompt-to-add-api-key.ts
@@ -0,0 +1,27 @@
+import { $TSContext } from 'amplify-cli-core';
+import { askApiKeyQuestions } from './service-walkthroughs/appSync-walkthrough';
+import { authConfigToAppSyncAuthType } from './utils/auth-config-to-app-sync-auth-type-bi-di-mapper';
+import { getCfnApiArtifactHandler } from './cfn-api-artifact-handler';
+import { prompter } from 'amplify-prompts';
+
+export async function promptToAddApiKey(context: $TSContext): Promise {
+ if (await prompter.confirmContinue('Would you like to create an API Key?')) {
+ const apiKeyConfig = await askApiKeyQuestions();
+ const authConfig = [apiKeyConfig];
+
+ await getCfnApiArtifactHandler(context).updateArtifacts(
+ {
+ version: 1,
+ serviceModification: {
+ serviceName: 'AppSync',
+ additionalAuthTypes: authConfig.map(authConfigToAppSyncAuthType),
+ },
+ },
+ {
+ skipCompile: true,
+ },
+ );
+
+ return apiKeyConfig;
+ }
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/aPIGateway-user-input-types.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/aPIGateway-user-input-types.ts
new file mode 100644
index 0000000000..9844234707
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/aPIGateway-user-input-types.ts
@@ -0,0 +1,37 @@
+/**
+ * Defines the json object expected by the amplify api category
+ */
+export interface APIGatewayCLIInputs {
+ /**
+ * The schema version.
+ */
+ version: 1;
+
+ /**
+ * map of paths in the REST API.
+ */
+ paths: { [pathName: string]: Path };
+}
+
+type Path = {
+ lambdaFunction: string;
+ permissions: {
+ setting: PermissionSetting;
+ auth?: CrudOperation[];
+ guest?: CrudOperation[];
+ groups?: { [groupName: string]: CrudOperation[] };
+ };
+};
+
+enum CrudOperation {
+ CREATE = 'create',
+ READ = 'read',
+ UPDATE = 'update',
+ DELETE = 'delete',
+}
+
+enum PermissionSetting {
+ PRIVATE = 'private',
+ PROTECTED = 'protected',
+ OPEN = 'open',
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/apigw-types.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/apigw-types.ts
new file mode 100644
index 0000000000..380a9c6368
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/apigw-types.ts
@@ -0,0 +1,28 @@
+import { $TSObject } from 'amplify-cli-core';
+import { CrudOperation, PermissionSetting } from '../cdk-stack-builder';
+
+export type ApigwPath = {
+ name: string;
+ permissions: {
+ setting: PermissionSetting;
+ auth?: CrudOperation[];
+ guest?: CrudOperation[];
+ groups?: {
+ [userPoolGroupName: string]: CrudOperation[];
+ };
+ };
+ lambdaFunction: string;
+};
+
+export type ApiRequirements = { authSelections: 'identityPoolAndUserPool'; allowUnauthenticatedIdentities?: boolean };
+
+export type ApigwWalkthroughReturnPromise = Promise<{
+ answers: ApigwAnswers;
+}>;
+
+export type ApigwAnswers = {
+ paths: { [pathName: string]: ApigwPath };
+ resourceName: string;
+ functionArns?: string[];
+ dependsOn?: $TSObject[];
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/appsync-user-input-types.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/appsync-user-input-types.ts
new file mode 100644
index 0000000000..81480d62d0
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthrough-types/appsync-user-input-types.ts
@@ -0,0 +1,175 @@
+/**
+ * Defines the json object expected by `amplify api category
+ */
+export interface AppSyncCLIInputs {
+ /**
+ * The schema version.
+ */
+ version: 1;
+ /**
+ * The service configuration that will be interpreted by Amplify.
+ */
+ serviceConfiguration: AppSyncServiceConfig;
+}
+
+/**
+ * Configuration exposed by AppSync. Currently this is the only API type supported by Amplify headless mode.
+ */
+export interface AppSyncServiceConfig {
+ /**
+ * The service name of the resource provider.
+ */
+ serviceName: 'AppSync';
+ /**
+ * The name of the API that will be created.
+ */
+ apiName: string;
+ /**
+ * Path to GraphQL schema that defines the AppSync API.
+ */
+ gqlSchemaPath: string;
+ /**
+ * The auth type that will be used by default.
+ */
+ defaultAuthType: AppSyncAuthType;
+ /**
+ * Additional methods of authenticating API requests.
+ */
+ additionalAuthTypes?: AppSyncAuthType[];
+ /**
+ * The strategy for resolving API write conflicts.
+ */
+ conflictResolution?: ConflictResolution;
+}
+
+/**
+ * Defines a strategy for resolving API write conflicts.
+ */
+export interface ConflictResolution {
+ /**
+ * The strategy that will be used for all models by default.
+ */
+ defaultResolutionStrategy?: ResolutionStrategy;
+ /**
+ * Strategies that will be used for individual models.
+ */
+ perModelResolutionStrategy?: PerModelResolutionstrategy[];
+}
+
+/**
+ * Defines a resolution strategy for a single model.
+ */
+export interface PerModelResolutionstrategy {
+ /**
+ * The resolution strategy for the model.
+ */
+ resolutionStrategy: ResolutionStrategy;
+ /**
+ * The model name.
+ */
+ entityName: string;
+}
+
+/**
+ * Resolution strategies provided by AppSync. See https://docs.aws.amazon.com/appsync/latest/devguide/conflict-detection-and-sync.html for details.
+ */
+export interface PredefinedResolutionStrategy {
+ type: 'OPTIMISTIC_CONCURRENCY' | 'AUTOMERGE' | 'NONE';
+}
+
+/**
+ * Resolution strategy using a custom lambda function.
+ */
+export interface LambdaResolutionStrategy {
+ type: 'LAMBDA';
+ /**
+ * The lambda function used to resolve conflicts.
+ */
+ resolver: LambdaConflictResolver;
+}
+
+export type LambdaConflictResolver = NewLambdaConflictResolver | ExistingLambdaConflictResolver;
+
+/**
+ * Defines a new lambda conflict resolver. Using this resolver type will create a new lambda function with boilerplate resolver logic.
+ */
+export interface NewLambdaConflictResolver {
+ type: 'NEW';
+}
+
+/**
+ * Defines an lambda conflict resolver that uses an existing lambda function.
+ */
+export interface ExistingLambdaConflictResolver {
+ type: 'EXISTING';
+ /**
+ * The name of the lambda function (this must be a lambda function that exists in the Amplify project).
+ */
+ name: string;
+ /**
+ * The lambda function region.
+ */
+ region?: string;
+ /**
+ * A lambda function ARN. This could be an ARN outside of the Amplify project but in that case extra care must be taken to ensure the AppSync API has access to the Lambda.
+ */
+ arn?: string;
+}
+
+export type ResolutionStrategy = PredefinedResolutionStrategy | LambdaResolutionStrategy;
+
+export type AppSyncAuthType =
+ | AppSyncAPIKeyAuthType
+ | AppSyncAWSIAMAuthType
+ | AppSyncCognitoUserPoolsAuthType
+ | AppSyncOpenIDConnectAuthType
+ | AppSyncLambdaAuthType;
+
+/**
+ * Specifies that the AppSync API should be secured using an API key.
+ */
+export interface AppSyncAPIKeyAuthType {
+ mode: 'API_KEY';
+ expirationTime?: number;
+ apiKeyExpirationDate?: Date;
+ keyDescription?: string;
+}
+
+/**
+ * Specifies that the AppSync API should be secured using AWS IAM.
+ */
+export interface AppSyncAWSIAMAuthType {
+ mode: 'AWS_IAM';
+}
+
+/**
+ * Specifies that the AppSync API should be secured using Cognito.
+ */
+export interface AppSyncCognitoUserPoolsAuthType {
+ mode: 'AMAZON_COGNITO_USER_POOLS';
+ /**
+ * The user pool that will be used to authenticate requests.
+ */
+ cognitoUserPoolId?: string;
+}
+
+/**
+ * Specifies that the AppSync API should be secured using OpenID.
+ */
+export interface AppSyncOpenIDConnectAuthType {
+ mode: 'OPENID_CONNECT';
+ openIDProviderName: string;
+ openIDIssuerURL: string;
+ openIDClientID: string;
+ openIDAuthTTL?: string;
+ openIDIatTTL?: string;
+}
+
+/**
+ * Specifies that the AppSync API should be secured using Lambda.
+ */
+export interface AppSyncLambdaAuthType {
+ mode: 'AWS_LAMBDA';
+ lambdaFunction: string;
+ ttlSeconds?: string;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.ts
new file mode 100644
index 0000000000..88652e6da7
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/apigw-walkthrough.ts
@@ -0,0 +1,734 @@
+import {
+ $TSAny,
+ $TSContext,
+ $TSObject,
+ AmplifyCategories,
+ AmplifySupportedService,
+ exitOnNextTick,
+ isResourceNameUnique,
+ open,
+ pathManager,
+ ResourceDoesNotExistError,
+ stateManager,
+} from 'amplify-cli-core';
+import { byValues, printer, prompter } from 'amplify-prompts';
+import inquirer from 'inquirer';
+import _ from 'lodash';
+import os from 'os';
+import { v4 as uuid } from 'uuid';
+import { ADMIN_QUERIES_NAME } from '../../../category-constants';
+import { ApigwInputState } from '../apigw-input-state';
+import { CrudOperation, PermissionSetting } from '../cdk-stack-builder';
+import { getAllDefaults } from '../default-values/apigw-defaults';
+import { ApigwAnswers, ApigwPath, ApigwWalkthroughReturnPromise, ApiRequirements } from '../service-walkthrough-types/apigw-types';
+import { checkForPathOverlap, formatCFNPathParamsForExpressJs, validatePathName } from '../utils/rest-api-path-utils';
+
+const category = AmplifyCategories.API;
+const serviceName = AmplifySupportedService.APIGW;
+const elasticContainerServiceName = 'ElasticContainer';
+
+export async function serviceWalkthrough(context: $TSContext): ApigwWalkthroughReturnPromise {
+ const allDefaultValues = getAllDefaults(context.amplify.getProjectDetails());
+
+ const resourceName = await askApiName(context, allDefaultValues.resourceName);
+ const answers = { paths: {}, resourceName, dependsOn: undefined };
+
+ return pathFlow(context, answers);
+}
+
+export async function updateWalkthrough(context: $TSContext) {
+ const { allResources } = await context.amplify.getResourceStatus();
+ const allDefaultValues = getAllDefaults(context.amplify.getProjectDetails());
+ const resources = allResources
+ .filter(resource => resource.service === serviceName && resource.mobileHubMigrated !== true)
+ .map(resource => resource.resourceName);
+
+ if (resources.length === 0) {
+ const errMessage = 'No REST API resource to update. Use "amplify add api" command to create a new REST API';
+ printer.error(errMessage);
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+ exitOnNextTick(0);
+ return;
+ }
+
+ let answers: $TSAny = {
+ paths: [],
+ };
+
+ const selectedApiName = await prompter.pick<'one', string>('Select the REST API you want to update:', resources);
+ let updateApiOperation = await prompter.pick<'one', string>('What would you like to do?', [
+ { name: 'Add another path', value: 'add' },
+ { name: 'Update path', value: 'update' },
+ { name: 'Remove path', value: 'remove' },
+ ]);
+
+ // Inquirer does not currently support combining 'when' and 'default', so
+ // manually set the operation if the user ended up here via amplify api add.
+ if (context.input.command === 'add') {
+ updateApiOperation = 'add';
+ }
+
+ if (selectedApiName === ADMIN_QUERIES_NAME) {
+ const errMessage = `The Admin Queries API is maintained through the Auth category and should be updated using 'amplify update auth' command`;
+ printer.warn(errMessage);
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ const projRoot = pathManager.findProjectRoot();
+ if (!stateManager.resourceInputsJsonExists(projRoot, category, selectedApiName)) {
+ // Not yet migrated
+ await migrate(context, projRoot, selectedApiName);
+
+ // chose not to migrate
+ if (!stateManager.resourceInputsJsonExists(projRoot, category, selectedApiName)) {
+ exitOnNextTick(0);
+ }
+ }
+
+ const parameters = stateManager.getResourceInputsJson(projRoot, category, selectedApiName);
+ parameters.resourceName = selectedApiName;
+
+ Object.assign(allDefaultValues, parameters);
+ answers = { ...answers, ...parameters };
+ [answers.uuid] = uuid().split('-');
+ const pathNames = Object.keys(answers.paths);
+ let updatedResult = {};
+
+ switch (updateApiOperation) {
+ case 'add': {
+ updatedResult = pathFlow(context, answers);
+ break;
+ }
+ case 'remove': {
+ const pathToRemove = await inquirer.prompt({
+ name: 'path',
+ message: 'Select the path to remove',
+ type: 'list',
+ choices: pathNames,
+ });
+
+ delete answers.paths[pathToRemove.path];
+
+ const { dependsOn, functionArns } = await findDependsOn(answers.paths);
+ answers.dependsOn = dependsOn;
+ answers.functionArns = functionArns;
+
+ updatedResult = { answers };
+ break;
+ }
+ case 'update': {
+ const pathToEdit = await inquirer.prompt({
+ name: 'pathName',
+ message: 'Select the path to edit',
+ type: 'list',
+ choices: pathNames,
+ });
+
+ // removing path from paths list
+ const currentPath: ApigwPath = answers.paths[pathToEdit.pathName];
+ delete answers.paths[pathToEdit.pathName];
+
+ updatedResult = pathFlow(context, answers, currentPath);
+ break;
+ }
+ default: {
+ throw new Error(`Unrecognized API update operation "${updateApiOperation}"`);
+ }
+ }
+
+ return updatedResult;
+}
+
+async function pathFlow(context: $TSContext, answers: ApigwAnswers, currentPath?: ApigwPath): ApigwWalkthroughReturnPromise {
+ const pathsAnswer = await askPaths(context, answers, currentPath);
+ return { answers: pathsAnswer };
+}
+
+async function askApiName(context: $TSContext, defaultResourceName: string) {
+ const apiNameValidator = (input: string) => {
+ const amplifyValidatorOutput = context.amplify.inputValidation({
+ validation: {
+ operator: 'regex',
+ value: '^[a-zA-Z0-9]+$',
+ onErrorMsg: 'Resource name should be alphanumeric',
+ },
+ required: true,
+ })(input);
+
+ const adminQueriesName = 'AdminQueries';
+ if (input === adminQueriesName) {
+ return `${adminQueriesName} is a reserved name for REST API resources for use by the auth category. Run "amplify update auth" to create an Admin Queries API.`;
+ }
+
+ let uniqueCheck = false;
+ try {
+ uniqueCheck = isResourceNameUnique(category, input);
+ } catch (e) {
+ return e.message || e;
+ }
+ return typeof amplifyValidatorOutput === 'string' ? amplifyValidatorOutput : uniqueCheck;
+ };
+
+ const resourceName = await prompter.input<'one', string>(
+ 'Provide a friendly name for your resource to be used as a label for this category in the project:',
+ { initial: defaultResourceName, validate: apiNameValidator },
+ );
+
+ return resourceName;
+}
+
+async function askPermissions(
+ context: $TSContext,
+ answers: $TSObject,
+ currentPath?: ApigwPath,
+): Promise<{ setting?: PermissionSetting; auth?: CrudOperation[]; open?: boolean; userPoolGroups?: $TSObject; guest?: CrudOperation[] }> {
+ while (true) {
+ const apiAccess = await prompter.yesOrNo('Restrict API access?', currentPath?.permissions?.setting !== PermissionSetting.OPEN);
+
+ if (!apiAccess) {
+ return { setting: PermissionSetting.OPEN };
+ }
+
+ const userPoolGroupList = context.amplify.getUserPoolGroupList();
+
+ let permissionSelected = 'Auth/Guest Users';
+ const permissions: $TSObject = {};
+
+ if (userPoolGroupList.length > 0) {
+ do {
+ if (permissionSelected === 'Learn more') {
+ printer.blankLine();
+ printer.info(
+ 'You can restrict access using CRUD policies for Authenticated Users, Guest Users, or on individual Group that users belong to' +
+ ' in a User Pool. If a user logs into your application and is not a member of any group they will use policy set for ' +
+ '“Authenticated Users”, however if they belong to a group they will only get the policy associated with that specific group.',
+ );
+ printer.blankLine();
+ }
+ const permissionSelection = await prompter.pick<'one', string>('Restrict access by:', [
+ 'Auth/Guest Users',
+ 'Individual Groups',
+ 'Both',
+ 'Learn more',
+ ]);
+
+ permissionSelected = permissionSelection;
+ } while (permissionSelected === 'Learn more');
+ }
+
+ if (permissionSelected === 'Both' || permissionSelected === 'Auth/Guest Users') {
+ const permissionSetting = await prompter.pick<'one', string>(
+ 'Who should have access?',
+ [
+ {
+ name: 'Authenticated users only',
+ value: PermissionSetting.PRIVATE,
+ },
+ {
+ name: 'Authenticated and Guest users',
+ value: PermissionSetting.PROTECTED,
+ },
+ ],
+ { initial: currentPath?.permissions?.setting === PermissionSetting.PROTECTED ? 1 : 0 },
+ );
+
+ permissions.setting = permissionSetting;
+
+ let {
+ permissions: { auth: authPermissions },
+ } = currentPath || { permissions: { auth: [] } };
+ let {
+ permissions: { guest: unauthPermissions },
+ } = currentPath || { permissions: { guest: [] } };
+
+ if (permissionSetting === PermissionSetting.PRIVATE) {
+ permissions.auth = await askCRUD('Authenticated', authPermissions);
+
+ const apiRequirements: ApiRequirements = { authSelections: 'identityPoolAndUserPool' };
+
+ await ensureAuth(context, apiRequirements, answers.resourceName);
+ }
+
+ if (permissionSetting === PermissionSetting.PROTECTED) {
+ permissions.auth = await askCRUD('Authenticated', authPermissions);
+ permissions.guest = await askCRUD('Guest', unauthPermissions);
+ const apiRequirements: ApiRequirements = { authSelections: 'identityPoolAndUserPool', allowUnauthenticatedIdentities: true };
+
+ await ensureAuth(context, apiRequirements, answers.resourceName);
+ }
+ }
+
+ if (permissionSelected === 'Both' || permissionSelected === 'Individual Groups') {
+ // Enable Auth if not enabled
+
+ const apiRequirements: ApiRequirements = { authSelections: 'identityPoolAndUserPool' };
+
+ await ensureAuth(context, apiRequirements, answers.resourceName);
+
+ // Get Auth resource name
+ const authResourceName = getAuthResourceName();
+ answers.authResourceName = authResourceName;
+
+ let defaultSelectedGroups: string[] = [];
+
+ if (currentPath?.permissions?.groups) {
+ defaultSelectedGroups = Object.keys(currentPath.permissions.groups);
+ }
+
+ let selectedUserPoolGroupList = await prompter.pick<'many', string>('Select groups:', userPoolGroupList, {
+ initial: byValues(defaultSelectedGroups)(userPoolGroupList),
+ returnSize: 'many',
+ pickAtLeast: 1,
+ });
+
+ //if single user pool group is selected, convert to array
+ if (selectedUserPoolGroupList && !Array.isArray(selectedUserPoolGroupList)) {
+ selectedUserPoolGroupList = [selectedUserPoolGroupList];
+ }
+
+ for (const selectedUserPoolGroup of selectedUserPoolGroupList) {
+ let defaults = [];
+ if (currentPath?.permissions?.groups?.[selectedUserPoolGroup]) {
+ defaults = currentPath.permissions.groups[selectedUserPoolGroup];
+ }
+ if (!permissions.groups) {
+ permissions.groups = {};
+ }
+ permissions.groups[selectedUserPoolGroup] = await askCRUD(selectedUserPoolGroup, defaults);
+ }
+
+ if (!permissions.setting) {
+ permissions.setting = PermissionSetting.PRIVATE;
+ }
+ }
+ return permissions;
+ }
+}
+
+async function ensureAuth(context: $TSContext, apiRequirements: ApiRequirements, resourceName: string) {
+ const checkResult: $TSAny = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'checkRequirements', [
+ apiRequirements,
+ context,
+ 'api',
+ resourceName,
+ ]);
+
+ // If auth is imported and configured, we have to throw the error instead of printing since there is no way to adjust the auth
+ // configuration.
+ if (checkResult.authImported === true && checkResult.errors && checkResult.errors.length > 0) {
+ throw new Error(checkResult.errors.join(os.EOL));
+ }
+
+ if (checkResult.errors && checkResult.errors.length > 0) {
+ printer.warn(checkResult.errors.join(os.EOL));
+ }
+
+ // If auth is not imported and there were errors, adjust or enable auth configuration
+ if (!checkResult.authEnabled || !checkResult.requirementsMet) {
+ try {
+ await context.amplify.invokePluginMethod(context, 'auth', undefined, 'externalAuthEnable', [
+ context,
+ AmplifyCategories.API,
+ resourceName,
+ apiRequirements,
+ ]);
+ } catch (error) {
+ printer.error(error);
+ throw error;
+ }
+ }
+}
+
+async function askCRUD(userType: string, permissions: CrudOperation[] = []) {
+ const crudOptions = [CrudOperation.CREATE, CrudOperation.READ, CrudOperation.UPDATE, CrudOperation.DELETE];
+ const crudAnswers = await prompter.pick<'many', string>(`What permissions do you want to grant to ${userType} users?`, crudOptions, {
+ returnSize: 'many',
+ initial: byValues(permissions),
+ pickAtLeast: 1,
+ });
+
+ return crudAnswers;
+}
+
+async function askPaths(context: $TSContext, answers: $TSObject, currentPath?: ApigwPath): Promise {
+ const existingFunctions = functionsExist();
+
+ let defaultFunctionType = 'newFunction';
+ const defaultChoice = {
+ name: 'Create a new Lambda function',
+ value: defaultFunctionType,
+ };
+ const choices = [defaultChoice];
+
+ if (existingFunctions) {
+ choices.push({
+ name: 'Use a Lambda function already added in the current Amplify project',
+ value: 'projectFunction',
+ });
+ }
+
+ const paths = answers.paths;
+
+ let addAnotherPath: boolean;
+ do {
+ let pathName: string;
+ let isPathValid: boolean;
+ do {
+ pathName = await prompter.input('Provide a path (e.g., /book/{isbn}):', {
+ initial: currentPath ? currentPath.name : '/items',
+ validate: validatePathName,
+ });
+
+ const overlapCheckResult = checkForPathOverlap(pathName, Object.keys(paths));
+ if (overlapCheckResult === false) {
+ // The path provided by the user is valid, and doesn't overlap with any other endpoints that they've stood up with API Gateway.
+ isPathValid = true;
+ } else {
+ // The path provided by the user overlaps with another endpoint that they've stood up with API Gateway.
+ // Ask them if they're okay with this. If they are, then we'll consider their provided path to be valid.
+ const higherOrderPath = overlapCheckResult.higherOrderPath;
+ const lowerOrderPath = overlapCheckResult.lowerOrderPath;
+
+ isPathValid = await prompter.confirmContinue(
+ `The path ${lowerOrderPath} overlaps with ${higherOrderPath}. Users authorized to access ${higherOrderPath} will also have access` +
+ ` to ${lowerOrderPath}. Are you sure you want to continue?`,
+ );
+ }
+ } while (!isPathValid);
+
+ const functionType = await prompter.pick<'one', string>('Choose a Lambda source', choices, { initial: choices.indexOf(defaultChoice) });
+
+ let path = { name: pathName };
+ let lambda;
+ do {
+ lambda = await askLambdaSource(context, functionType, pathName, currentPath);
+ } while (!lambda);
+ const permissions = await askPermissions(context, answers, currentPath);
+ path = { ...path, ...lambda, permissions };
+ paths[pathName] = path;
+
+ if (currentPath) {
+ break;
+ }
+
+ addAnotherPath = await prompter.confirmContinue('Do you want to add another path?');
+ } while (addAnotherPath);
+
+ const { dependsOn, functionArns } = await findDependsOn(paths);
+
+ return { paths, dependsOn, resourceName: answers.resourceName, functionArns };
+}
+
+async function findDependsOn(paths: $TSObject[]) {
+ // go thru all paths and add lambdaFunctions to dependsOn and functionArns uniquely
+ const dependsOn = [];
+ const functionArns = [];
+
+ for (const path of Object.values(paths)) {
+ if (path.lambdaFunction && !path.lambdaArn) {
+ if (!dependsOn.find(func => func.resourceName === path.lambdaFunction)) {
+ dependsOn.push({
+ category: 'function',
+ resourceName: path.lambdaFunction,
+ attributes: ['Name', 'Arn'],
+ });
+ }
+ }
+
+ if (!functionArns.find(func => func.lambdaFunction === path.lambdaFunction)) {
+ functionArns.push({
+ lambdaFunction: path.lambdaFunction,
+ lambdaArn: path.lambdaArn,
+ });
+ }
+
+ if (path?.permissions?.groups) {
+ const userPoolGroups = Object.keys(path.permissions.groups);
+ if (userPoolGroups.length > 0) {
+ // Get auth resource name
+
+ const authResourceName = getAuthResourceName();
+
+ if (!dependsOn.find(resource => resource.resourceName === authResourceName)) {
+ dependsOn.push({
+ category: 'auth',
+ resourceName: authResourceName,
+ attributes: ['UserPoolId'],
+ });
+ }
+
+ userPoolGroups.forEach(group => {
+ if (!dependsOn.find(resource => resource.attributes[0] === `${group}GroupRole`)) {
+ dependsOn.push({
+ category: 'auth',
+ resourceName: 'userPoolGroups',
+ attributes: [`${group}GroupRole`],
+ });
+ }
+ });
+ }
+ }
+ }
+ return { dependsOn, functionArns };
+}
+
+function getAuthResourceName(): string {
+ const meta = stateManager.getMeta();
+ const authResources = (Object.entries(meta?.auth) || []).filter(
+ ([_, resource]: [key: string, resource: $TSObject]) => resource.service === AmplifySupportedService.COGNITO,
+ );
+ if (authResources.length === 0) {
+ throw new Error('No auth resource found. Add it using amplify add auth');
+ }
+
+ const [authResourceName] = authResources[0];
+ return authResourceName;
+}
+
+function functionsExist() {
+ const meta = stateManager.getMeta();
+ if (!meta.function) {
+ return false;
+ }
+
+ const functionResources = meta.function;
+ const lambdaFunctions = [];
+ Object.keys(functionResources).forEach(resourceName => {
+ if (functionResources[resourceName].service === AmplifySupportedService.LAMBDA) {
+ lambdaFunctions.push(resourceName);
+ }
+ });
+
+ if (lambdaFunctions.length === 0) {
+ return false;
+ }
+
+ return true;
+}
+
+async function askLambdaSource(context: $TSContext, functionType: string, path: string, currentPath?: ApigwPath) {
+ switch (functionType) {
+ case 'arn':
+ return askLambdaArn(context, currentPath);
+ case 'projectFunction':
+ return askLambdaFromProject(currentPath);
+ case 'newFunction':
+ return newLambdaFunction(context as $TSAny, path);
+ default:
+ throw new Error('Type not supported');
+ }
+}
+
+async function newLambdaFunction(context: $TSContext, path: string) {
+ let params = {
+ functionTemplate: {
+ parameters: {
+ path,
+ expressPath: formatCFNPathParamsForExpressJs(path),
+ },
+ },
+ };
+
+ const resourceName = await context.amplify.invokePluginMethod(context, AmplifyCategories.FUNCTION, undefined, 'add', [
+ context,
+ 'awscloudformation',
+ AmplifySupportedService.LAMBDA,
+ params,
+ ]);
+
+ printer.success('Succesfully added the Lambda function locally');
+
+ return { lambdaFunction: resourceName };
+}
+
+async function askLambdaFromProject(currentPath?: ApigwPath) {
+ const meta = stateManager.getMeta();
+ const lambdaFunctions = [];
+ Object.keys(meta?.function || {}).forEach(resourceName => {
+ if (meta.function[resourceName].service === AmplifySupportedService.LAMBDA) {
+ lambdaFunctions.push(resourceName);
+ }
+ });
+
+ const lambdaFunction = await prompter.pick<'one', string>('Choose the Lambda function to invoke by this path', lambdaFunctions, {
+ initial: currentPath ? lambdaFunctions.indexOf(currentPath.lambdaFunction) : 0,
+ });
+
+ return { lambdaFunction };
+}
+
+async function askLambdaArn(context: $TSContext, currentPath?: ApigwPath) {
+ const lambdaFunctions = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'getLambdaFunctions');
+
+ const lambdaOptions = lambdaFunctions.map(lambdaFunction => ({
+ value: lambdaFunction.FunctionArn,
+ name: `${lambdaFunction.FunctionName} (${lambdaFunction.FunctionArn})`,
+ }));
+
+ if (lambdaOptions.length === 0) {
+ printer.error('You do not have any Lambda functions configured for the selected Region');
+ return null;
+ }
+
+ const lambdaCloudOptionQuestion = {
+ type: 'list',
+ name: 'lambdaChoice',
+ message: 'Select a Lambda function',
+ choices: lambdaOptions,
+ default: currentPath && currentPath.lambdaFunction ? `${currentPath.lambdaFunction}` : `${lambdaOptions[0].value}`,
+ };
+
+ let lambdaOption;
+ while (!lambdaOption) {
+ try {
+ lambdaOption = await inquirer.prompt([lambdaCloudOptionQuestion]);
+ } catch (err) {
+ printer.error('Select a Lambda Function');
+ }
+ }
+
+ const lambdaCloudOptionAnswer = lambdaFunctions.find(lambda => lambda.FunctionArn === lambdaOption.lambdaChoice);
+
+ return {
+ lambdaArn: lambdaCloudOptionAnswer.FunctionArn,
+ lambdaFunction: lambdaCloudOptionAnswer.FunctionName,
+ };
+}
+
+export async function migrate(context: $TSContext, projectPath: string, resourceName: string) {
+ const apigwInputState = new ApigwInputState(context, resourceName);
+
+ if (resourceName === ADMIN_QUERIES_NAME) {
+ const meta = stateManager.getMeta();
+ const adminQueriesDependsOn = _.get(meta, [AmplifyCategories.API, ADMIN_QUERIES_NAME, 'dependsOn'], undefined);
+
+ if (!adminQueriesDependsOn) {
+ throw new Error('Failed to migrate Admin Queries API. Could not find expected information in amplify-meta.json.');
+ }
+
+ const functionName = adminQueriesDependsOn.filter(dependency => dependency.category === AmplifyCategories.FUNCTION)?.[0]?.resourceName;
+
+ const adminQueriesProps = {
+ apiName: resourceName,
+ authResourceName: getAuthResourceName(),
+ functionName,
+ dependsOn: adminQueriesDependsOn,
+ };
+
+ return apigwInputState.migrateAdminQueries(adminQueriesProps);
+ }
+
+ return apigwInputState.migrateApigwResource(resourceName);
+}
+
+export function getIAMPolicies(resourceName: string, crudOptions: string[]) {
+ let policy = {};
+ const actions = [];
+
+ crudOptions.forEach(crudOption => {
+ switch (crudOption) {
+ case CrudOperation.CREATE:
+ actions.push('apigateway:POST', 'apigateway:PUT');
+ break;
+ case CrudOperation.UPDATE:
+ actions.push('apigateway:PATCH');
+ break;
+ case CrudOperation.READ:
+ actions.push('apigateway:GET', 'apigateway:HEAD', 'apigateway:OPTIONS');
+ break;
+ case CrudOperation.DELETE:
+ actions.push('apigateway:DELETE');
+ break;
+ default:
+ printer.info(`${crudOption} not supported`);
+ }
+ });
+
+ policy = {
+ Effect: 'Allow',
+ Action: actions,
+ Resource: [
+ {
+ 'Fn::Join': [
+ '',
+ [
+ 'arn:aws:apigateway:',
+ {
+ Ref: 'AWS::Region',
+ },
+ '::/restapis/',
+ {
+ Ref: `${category}${resourceName}ApiName`,
+ },
+ '/*',
+ ],
+ ],
+ },
+ ],
+ };
+
+ const attributes = ['ApiName', 'ApiId'];
+
+ return { policy, attributes };
+}
+
+export const openConsole = async (context?: $TSContext) => {
+ const amplifyMeta = stateManager.getMeta();
+ const categoryAmplifyMeta = amplifyMeta[category];
+ const { Region } = amplifyMeta.providers.awscloudformation;
+
+ const restApis = Object.keys(categoryAmplifyMeta).filter(resourceName => {
+ const resource = categoryAmplifyMeta[resourceName];
+ return (
+ resource.output &&
+ (resource.service === serviceName || (resource.service === elasticContainerServiceName && resource.apiType === 'REST'))
+ );
+ });
+
+ if (restApis) {
+ let url;
+
+ const selectedApi = await prompter.pick<'one', string>('Select the API', restApis);
+ const selectedResource = categoryAmplifyMeta[selectedApi];
+
+ if (selectedResource.service === serviceName) {
+ const {
+ output: { ApiId },
+ } = selectedResource;
+
+ url = `https://${Region}.console.aws.amazon.com/apigateway/home?region=${Region}#/apis/${ApiId}/resources/`;
+ } else {
+ // Elastic Container API
+ const {
+ output: { PipelineName, ServiceName, ClusterName },
+ } = selectedResource;
+ const codePipeline = 'CodePipeline';
+ const elasticContainer = 'ElasticContainer';
+
+ const selectedConsole = await prompter.pick<'one', string>('Which console do you want to open?', [
+ {
+ name: 'Elastic Container Service (Deployed container status)',
+ value: elasticContainer,
+ },
+ {
+ name: 'CodePipeline (Container build status)',
+ value: codePipeline,
+ },
+ ]);
+
+ if (selectedConsole === elasticContainer) {
+ url = `https://console.aws.amazon.com/ecs/home?region=${Region}#/clusters/${ClusterName}/services/${ServiceName}/details`;
+ } else if (selectedConsole === codePipeline) {
+ url = `https://${Region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${PipelineName}/view`;
+ } else {
+ printer.error('Option not available');
+ return;
+ }
+ }
+
+ open(url, { wait: false });
+ } else {
+ printer.error('There are no REST APIs pushed to the cloud');
+ }
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-rds-walkthrough.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-rds-walkthrough.ts
new file mode 100644
index 0000000000..326e95c370
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-rds-walkthrough.ts
@@ -0,0 +1,282 @@
+import { $TSContext, $TSObject, exitOnNextTick, ResourceCredentialsNotFoundError, ResourceDoesNotExistError, pathManager, JSONUtilities, $TSAny } from 'amplify-cli-core';
+import { printer, prompter } from 'amplify-prompts';
+import chalk from 'chalk';
+import { DataApiParams } from 'graphql-relational-schema-transformer';
+import ora from 'ora';
+import { cfnRootStackFileName } from 'amplify-provider-awscloudformation';
+
+const spinner = ora('');
+const category = 'api';
+const providerName = 'awscloudformation';
+
+export async function serviceWalkthrough(context: $TSContext, datasourceMetadata: $TSObject) {
+ const amplifyMeta = context.amplify.getProjectMeta();
+
+ // Verify that an API exists in the project before proceeding.
+ if (amplifyMeta == null || amplifyMeta[category] == null || Object.keys(amplifyMeta[category]).length === 0) {
+ const errMessage =
+ 'You must create an AppSync API in your project before adding a graphql datasource. Please use "amplify api add" to create the API.';
+ printer.error(errMessage);
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ // Loop through to find the AppSync API Resource Name
+ let appSyncApi: string;
+ const apis = Object.keys(amplifyMeta[category]);
+
+ for (const api of apis) {
+ if (amplifyMeta[category][api].service === 'AppSync') {
+ appSyncApi = api;
+ break;
+ }
+ }
+
+ // If an AppSync API does not exist, inform the user to create the AppSync API
+ if (!appSyncApi) {
+ const errMessage =
+ 'You must create an AppSync API in your project before adding a graphql datasource. Please use "amplify api add" to create the API.';
+ printer.error(errMessage);
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ const { inputs, availableRegions } = datasourceMetadata;
+
+ // FIXME: We should NOT be treating CloudFormation templates as inputs to prompts! This a temporary exception while we move team-provider-info to a service.
+ const cfnJson: $TSAny = JSONUtilities.readJson(`${pathManager.getCurrentCloudRootStackDirPath(pathManager.findProjectRoot())}/${cfnRootStackFileName}`);
+ const cfnJsonParameters = cfnJson?.Resources[`api${appSyncApi}`]?.Properties?.Parameters || {};
+ let selectedRegion = cfnJsonParameters?.rdsRegion;
+ // Region Question
+ if (!selectedRegion) {
+ selectedRegion = await promptWalkthroughQuestion(inputs, 0, availableRegions);
+ }
+
+ const AWS = await getAwsClient(context, 'list');
+
+ // Prepare the SDK with the region
+ AWS.config.update({
+ region: selectedRegion,
+ });
+
+ // RDS Cluster Question
+ let selectedClusterArn = cfnJsonParameters?.rdsClusterIdentifier
+ let clusterResourceId = getRdsClusterResourceIdFromArn(selectedClusterArn, AWS);
+ if (!selectedClusterArn || !clusterResourceId) {
+ ({ selectedClusterArn, clusterResourceId } = await selectCluster(context, inputs, AWS));
+ }
+
+ // Secret Store Question
+ let selectedSecretArn = cfnJsonParameters?.rdsSecretStoreArn;
+ if (!selectedSecretArn) {
+ selectedSecretArn = await getSecretStoreArn(context, inputs, clusterResourceId, AWS);
+ }
+
+ // Database Name Question
+ let selectedDatabase = cfnJsonParameters?.rdsDatabaseName;
+ if (!selectedDatabase) {
+ selectedDatabase = await selectDatabase(context, inputs, selectedClusterArn, selectedSecretArn, AWS);
+ }
+
+ return {
+ region: selectedRegion,
+ dbClusterArn: selectedClusterArn,
+ secretStoreArn: selectedSecretArn,
+ databaseName: selectedDatabase,
+ resourceName: appSyncApi,
+ };
+}
+
+async function getRdsClusterResourceIdFromArn(arn: string|undefined, AWS) {
+ // If the arn was not already existing in cloudformation template, return undefined to prompt for input.
+ if (!arn) {
+ return;
+ }
+
+ const RDS = new AWS.RDS();
+ const describeDBClustersResult = await RDS.describeDBClusters().promise();
+ const rawClusters = describeDBClustersResult.DBClusters;
+ const identifiedCluster = rawClusters.find(cluster => cluster.DBClusterArn === arn);
+ return identifiedCluster.DBClusterIdentifier;
+}
+
+/**
+ *
+ * @param {*} inputs
+ */
+async function selectCluster(context: $TSContext, inputs, AWS) {
+ const RDS = new AWS.RDS();
+
+ const describeDBClustersResult = await RDS.describeDBClusters().promise();
+ const rawClusters = describeDBClustersResult.DBClusters;
+
+ const clusters = new Map();
+ const serverlessClusters = rawClusters.filter(cluster => cluster.EngineMode === 'serverless');
+
+ if (serverlessClusters.length === 0) {
+ const errMessage = 'No properly configured Aurora Serverless clusters found.';
+
+ printer.error(errMessage);
+
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+
+ exitOnNextTick(0);
+ }
+
+ for (const cluster of serverlessClusters) {
+ clusters.set(cluster.DBClusterIdentifier, cluster);
+ }
+
+ if (clusters.size > 1) {
+ const clusterIdentifier = await promptWalkthroughQuestion(inputs, 1, Array.from(clusters.keys()));
+ const selectedCluster = clusters.get(clusterIdentifier);
+
+ return {
+ selectedClusterArn: selectedCluster.DBClusterArn,
+ clusterResourceId: selectedCluster.DbClusterResourceId,
+ };
+ }
+
+ // Pick first and only value
+ const firstCluster = Array.from(clusters.values())[0];
+
+ printer.info(`${chalk.green('✔')} Only one Cluster was found: '${firstCluster.DBClusterIdentifier}' was automatically selected.`);
+
+ return {
+ selectedClusterArn: firstCluster.DBClusterArn,
+ clusterResourceId: firstCluster.DbClusterResourceId,
+ };
+}
+
+/**
+ *
+ * @param {*} inputs
+ * @param {*} clusterResourceId
+ */
+async function getSecretStoreArn(context: $TSContext, inputs, clusterResourceId, AWS) {
+ const SecretsManager = new AWS.SecretsManager();
+ const NextToken = 'NextToken';
+ let rawSecrets = [];
+ const params = {
+ MaxResults: 20,
+ };
+
+ const listSecretsResult = await SecretsManager.listSecrets(params).promise();
+
+ rawSecrets = listSecretsResult.SecretList;
+ let token = listSecretsResult.NextToken;
+ while (token) {
+ params[NextToken] = token;
+ const tempSecretsResult = await SecretsManager.listSecrets(params).promise();
+ rawSecrets = [...rawSecrets, ...tempSecretsResult.SecretList];
+ token = tempSecretsResult.NextToken;
+ }
+
+ const secrets = new Map();
+ const secretsForCluster = rawSecrets.filter(secret => secret.Name.startsWith(`rds-db-credentials/${clusterResourceId}`));
+
+ if (secretsForCluster.length === 0) {
+ const errMessage = 'No RDS access credentials found in the AWS Secrect Manager.';
+
+ printer.error(errMessage);
+
+ await context.usageData.emitError(new ResourceCredentialsNotFoundError(errMessage));
+
+ exitOnNextTick(0);
+ }
+
+ for (const secret of secretsForCluster) {
+ secrets.set(secret.Name, secret.ARN);
+ }
+
+ let selectedSecretArn;
+
+ if (secrets.size > 1) {
+ // Kick off questions flow
+ const selectedSecretName = await promptWalkthroughQuestion(inputs, 2, Array.from(secrets.keys()));
+ selectedSecretArn = secrets.get(selectedSecretName);
+ } else {
+ // Pick first and only value
+ selectedSecretArn = Array.from(secrets.values())[0];
+
+ printer.info(`${chalk.green('✔')} Only one Secret was found for the cluster: '${selectedSecretArn}' was automatically selected.`);
+ }
+
+ return selectedSecretArn;
+}
+
+/**
+ *
+ * @param {*} inputs
+ * @param {*} clusterArn
+ * @param {*} secretArn
+ */
+async function selectDatabase(context: $TSContext, inputs, clusterArn, secretArn, AWS) {
+ // Database Name Question
+ const DataApi = new AWS.RDSDataService();
+ const params = new DataApiParams();
+ const databaseList = [];
+ params.secretArn = secretArn;
+ params.resourceArn = clusterArn;
+ params.sql = 'SHOW databases';
+
+ spinner.start('Fetching Aurora Serverless cluster...');
+
+ try {
+ const dataApiResult = await DataApi.executeStatement(params).promise();
+ const excludedDatabases = ['information_schema', 'performance_schema', 'mysql', 'sys'];
+
+ databaseList.push(...dataApiResult.records.map(record => record[0].stringValue).filter(name => !excludedDatabases.includes(name)));
+
+ spinner.succeed('Fetched Aurora Serverless cluster.');
+ } catch (err) {
+ spinner.fail(err.message);
+
+ if (err.code === 'BadRequestException' && /Access denied for user/.test(err.message)) {
+ const msg =
+ `Ensure that '${secretArn}' contains your database credentials. ` +
+ 'Please note that Aurora Serverless does not support IAM database authentication.';
+ printer.error(msg);
+ }
+ }
+
+ if (databaseList.length === 0) {
+ const errMessage = 'No database found in the selected cluster.';
+
+ printer.error(errMessage);
+
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+
+ exitOnNextTick(0);
+ }
+
+ if (databaseList.length > 1) {
+ return await promptWalkthroughQuestion(inputs, 3, databaseList);
+ }
+
+ printer.info(`${chalk.green('✔')} Only one Database was found: '${databaseList[0]}' was automatically selected.`);
+
+ return databaseList[0];
+}
+
+/**
+ *
+ * @param {*} inputs
+ * @param {*} questionNumber
+ * @param {*} choicesList
+ */
+async function promptWalkthroughQuestion(inputs, questionNumber, choicesList) {
+ const question = {
+ type: inputs[questionNumber].type,
+ name: inputs[questionNumber].key,
+ message: inputs[questionNumber].question,
+ choices: choicesList,
+ };
+ return await prompter.pick(question.message, choicesList)
+}
+
+async function getAwsClient(context: $TSContext, action: string) {
+ const providerPlugins = context.amplify.getProviderPlugins(context);
+ const provider = require(providerPlugins[providerName]);
+ return await provider.getConfiguredAWSClient(context, 'aurora-serverless', action);
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts
new file mode 100644
index 0000000000..64df03c011
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/appSync-walkthrough.ts
@@ -0,0 +1,1172 @@
+import { Duration, Expiration } from '@aws-cdk/core';
+import {
+ $TSContext,
+ $TSObject,
+ exitOnNextTick,
+ FeatureFlags,
+ open,
+ pathManager,
+ ResourceAlreadyExistsError,
+ ResourceDoesNotExistError,
+ stateManager,
+ UnknownResourceTypeError,
+ getGraphQLTransformerAuthDocLink,
+ ApiCategoryFacade,
+} from 'amplify-cli-core';
+import { UpdateApiRequest } from 'amplify-headless-interface';
+import { printer, prompter } from 'amplify-prompts';
+import chalk from 'chalk';
+import * as fs from 'fs-extra';
+import { collectDirectivesByTypeNames, readProjectConfiguration } from 'graphql-transformer-core';
+import inquirer, { CheckboxQuestion, ListChoiceOptions, ListQuestion } from 'inquirer';
+import _ from 'lodash';
+import * as path from 'path';
+import { v4 as uuid } from 'uuid';
+import { category } from '../../../category-constants';
+import { rootAssetDir } from '../aws-constants';
+import { getAllDefaults } from '../default-values/appSync-defaults';
+import { dataStoreLearnMore } from '../sync-conflict-handler-assets/syncAssets';
+import { authConfigHasApiKey, checkIfAuthExists, getAppSyncAuthConfig } from '../utils/amplify-meta-utils';
+import { authConfigToAppSyncAuthType } from '../utils/auth-config-to-app-sync-auth-type-bi-di-mapper';
+import { checkAppsyncApiResourceMigration } from '../utils/check-appsync-api-migration';
+import { defineGlobalSandboxMode } from '../utils/global-sandbox-mode';
+import { resolverConfigToConflictResolution } from '../utils/resolver-config-to-conflict-resolution-bi-di-mapper';
+
+const serviceName = 'AppSync';
+const elasticContainerServiceName = 'ElasticContainer';
+const providerName = 'awscloudformation';
+const graphqlSchemaDir = path.join(rootAssetDir, 'graphql-schemas');
+
+// keep in sync with ServiceName in amplify-category-function, but probably it will not change
+const FunctionServiceNameLambdaFunction = 'Lambda';
+
+const authProviderChoices = [
+ {
+ name: 'API key',
+ value: 'API_KEY',
+ },
+ {
+ name: 'Amazon Cognito User Pool',
+ value: 'AMAZON_COGNITO_USER_POOLS',
+ },
+ {
+ name: 'IAM',
+ value: 'AWS_IAM',
+ },
+ {
+ name: 'OpenID Connect',
+ value: 'OPENID_CONNECT',
+ },
+];
+
+const conflictResolutionHanlderChoices = [
+ {
+ name: 'Auto Merge',
+ value: 'AUTOMERGE',
+ },
+ {
+ name: 'Optimistic Concurrency',
+ value: 'OPTIMISTIC_CONCURRENCY',
+ },
+ {
+ name: 'Custom Lambda',
+ value: 'LAMBDA',
+ },
+ {
+ name: 'Learn More',
+ value: 'Learn More',
+ },
+];
+
+const blankSchemaFile = 'blank-schema.graphql';
+const schemaTemplatesV1 = [
+ {
+ name: 'Single object with fields (e.g., “Todo” with ID, name, description)',
+ value: 'single-object-schema.graphql',
+ },
+ {
+ name: 'One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)',
+ value: 'many-relationship-schema.graphql',
+ },
+ {
+ name: 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)',
+ value: 'single-object-auth-schema.graphql',
+ },
+ {
+ name: 'Blank Schema',
+ value: blankSchemaFile,
+ },
+];
+
+const schemaTemplatesV2 = [
+ {
+ name: 'Single object with fields (e.g., “Todo” with ID, name, description)',
+ value: 'single-object-schema-v2.graphql',
+ },
+ {
+ name: 'One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)',
+ value: 'many-relationship-schema-v2.graphql',
+ },
+ {
+ name: 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)',
+ value: 'single-object-auth-schema-v2.graphql',
+ },
+ {
+ name: 'Blank Schema',
+ value: blankSchemaFile,
+ },
+];
+
+export const openConsole = async (context: $TSContext) => {
+ const amplifyMeta = stateManager.getMeta();
+ const categoryAmplifyMeta = amplifyMeta[category];
+ const { Region } = amplifyMeta.providers[providerName];
+
+ const graphQLApis = Object.keys(categoryAmplifyMeta).filter(resourceName => {
+ const resource = categoryAmplifyMeta[resourceName];
+
+ return (
+ resource.output &&
+ (resource.service === serviceName || (resource.service === elasticContainerServiceName && resource.apiType === 'GRAPHQL'))
+ );
+ });
+
+ if (graphQLApis) {
+ let url;
+ let selectedApi = graphQLApis[0];
+
+ if (graphQLApis.length > 1) {
+ ({ selectedApi } = await inquirer.prompt({
+ type: 'list',
+ name: 'selectedApi',
+ choices: graphQLApis,
+ message: 'Please select the API',
+ }));
+ }
+
+ const selectedResource = categoryAmplifyMeta[selectedApi];
+
+ if (selectedResource.service === serviceName) {
+ const {
+ output: { GraphQLAPIIdOutput },
+ } = selectedResource;
+ const appId = amplifyMeta.providers[providerName].AmplifyAppId;
+ if (!appId) {
+ throw new Error('Missing AmplifyAppId in amplify-meta.json');
+ }
+
+ url = `https://console.aws.amazon.com/appsync/home?region=${Region}#/${GraphQLAPIIdOutput}/v1/queries`;
+
+ const providerPlugin = await import(context.amplify.getProviderPlugins(context)[providerName]);
+ const { isAdminApp, region } = await providerPlugin.isAmplifyAdminApp(appId);
+ if (isAdminApp) {
+ if (region !== Region) {
+ printer.warn(`Region mismatch: Amplify service returned '${region}', but found '${Region}' in amplify-meta.json.`);
+ }
+ const { envName } = context.amplify.getEnvInfo();
+ const baseUrl: string = providerPlugin.adminBackendMap[region].amplifyAdminUrl;
+ url = `${baseUrl}/admin/${appId}/${envName}/datastore`;
+ }
+ } else {
+ // Elastic Container API
+ const {
+ output: { PipelineName, ServiceName, ClusterName },
+ } = selectedResource;
+ const codePipeline = 'CodePipeline';
+ const elasticContainer = 'ElasticContainer';
+
+ const { selectedConsole } = await inquirer.prompt({
+ name: 'selectedConsole',
+ message: 'Which console you want to open',
+ type: 'list',
+ choices: [
+ {
+ name: 'Elastic Container Service (Deployed container status)',
+ value: elasticContainer,
+ },
+ {
+ name: 'CodePipeline (Container build status)',
+ value: codePipeline,
+ },
+ ],
+ });
+
+ if (selectedConsole === elasticContainer) {
+ url = `https://console.aws.amazon.com/ecs/home?region=${Region}#/clusters/${ClusterName}/services/${ServiceName}/details`;
+ } else if (selectedConsole === codePipeline) {
+ url = `https://${Region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${PipelineName}/view`;
+ } else {
+ printer.error('Option not available');
+ return;
+ }
+ }
+
+ open(url, { wait: false });
+ } else {
+ printer.error('AppSync API is not pushed in the cloud.');
+ }
+};
+
+const serviceApiInputWalkthrough = async (context: $TSContext, serviceMetadata) => {
+ let continuePrompt = false;
+ let authConfig;
+ let defaultAuthType;
+ let resolverConfig;
+ const { amplify } = context;
+ const { inputs } = serviceMetadata;
+ const allDefaultValues = getAllDefaults(amplify.getProjectDetails());
+
+ let resourceAnswers = {};
+ resourceAnswers[inputs[1].key] = allDefaultValues[inputs[1].key];
+ resourceAnswers[inputs[0].key] = resourceAnswers[inputs[1].key];
+
+ //
+ // Default authConfig - API Key (expires in 7 days)
+ //
+ authConfig = {
+ defaultAuthentication: {
+ apiKeyConfig: {
+ apiKeyExpirationDays: 7,
+ },
+ authenticationType: 'API_KEY',
+ },
+ additionalAuthenticationProviders: [],
+ };
+
+ //
+ // Repeat prompt until user selects Continue
+ //
+ while (!continuePrompt) {
+ const getAuthModeChoice = async () => {
+ if (authConfig.defaultAuthentication.authenticationType === 'API_KEY') {
+ return `${
+ authProviderChoices.find(choice => choice.value === authConfig.defaultAuthentication.authenticationType).name
+ } (default, expiration time: ${authConfig.defaultAuthentication.apiKeyConfig.apiKeyExpirationDays} days from now)`;
+ }
+ return `${authProviderChoices.find(choice => choice.value === authConfig.defaultAuthentication.authenticationType).name} (default)`;
+ };
+
+ const getAdditionalAuthModeChoices = async () => {
+ let additionalAuthModesText = '';
+ authConfig.additionalAuthenticationProviders.map(async authMode => {
+ additionalAuthModesText += `, ${authProviderChoices.find(choice => choice.value === authMode.authenticationType).name}`;
+ });
+ return additionalAuthModesText;
+ };
+
+ const basicInfoQuestionChoices = [];
+
+ basicInfoQuestionChoices.push({
+ name: chalk`{bold Name:} ${resourceAnswers[inputs[1].key]}`,
+ value: 'API_NAME',
+ });
+
+ basicInfoQuestionChoices.push({
+ name: chalk`{bold Authorization modes:} ${await getAuthModeChoice()}${await getAdditionalAuthModeChoices()}`,
+ value: 'API_AUTH_MODE',
+ });
+
+ basicInfoQuestionChoices.push({
+ name: chalk`{bold Conflict detection (required for DataStore):} ${resolverConfig?.project ? 'Enabled' : 'Disabled'}`,
+ value: 'CONFLICT_DETECTION',
+ });
+
+ if (resolverConfig?.project) {
+ basicInfoQuestionChoices.push({
+ name: chalk`{bold Conflict resolution strategy:} ${
+ conflictResolutionHanlderChoices.find(x => x.value === resolverConfig.project.ConflictHandler).name
+ }`,
+ value: 'CONFLICT_STRATEGY',
+ });
+ }
+
+ basicInfoQuestionChoices.push({
+ name: 'Continue',
+ value: 'CONTINUE',
+ });
+
+ const basicInfoQuestion = {
+ type: 'list',
+ name: 'basicApiSettings',
+ message: 'Here is the GraphQL API that we will create. Select a setting to edit or continue',
+ default: 'CONTINUE',
+ choices: basicInfoQuestionChoices,
+ };
+
+ let { basicApiSettings } = await inquirer.prompt([basicInfoQuestion]);
+
+ switch (basicApiSettings) {
+ case 'API_NAME':
+ const resourceQuestions = [
+ {
+ type: inputs[1].type,
+ name: inputs[1].key,
+ message: inputs[1].question,
+ validate: amplify.inputValidation(inputs[1]),
+ default: () => {
+ const defaultValue = allDefaultValues[inputs[1].key];
+ return defaultValue;
+ },
+ },
+ ];
+ // API name question
+ resourceAnswers = await inquirer.prompt(resourceQuestions);
+ resourceAnswers[inputs[0].key] = resourceAnswers[inputs[1].key];
+ allDefaultValues[inputs[1].key] = resourceAnswers[inputs[1].key];
+ break;
+ case 'API_AUTH_MODE':
+ // Ask additonal questions
+ ({ authConfig, defaultAuthType } = await askDefaultAuthQuestion(context));
+ ({ authConfig } = await askAdditionalQuestions(context, authConfig, defaultAuthType));
+ break;
+ case 'CONFLICT_DETECTION':
+ resolverConfig = await askResolverConflictQuestion(context, resolverConfig);
+ break;
+ case 'CONFLICT_STRATEGY':
+ resolverConfig = await askResolverConflictHandlerQuestion(context);
+ break;
+ case 'CONTINUE':
+ continuePrompt = true;
+ break;
+ }
+ }
+
+ return {
+ answers: resourceAnswers,
+ output: {
+ authConfig,
+ },
+ resolverConfig,
+ };
+};
+
+const updateApiInputWalkthrough = async (context: $TSContext, project: $TSObject, resolverConfig, modelTypes) => {
+ let authConfig;
+ let defaultAuthType;
+ const updateChoices = [
+ {
+ name: 'Authorization modes',
+ value: 'AUTH_MODE',
+ },
+ ];
+ // check if DataStore is enabled for the entire API
+ if (project.config && !_.isEmpty(project.config.ResolverConfig)) {
+ updateChoices.push({
+ name: 'Conflict resolution strategy',
+ value: 'CONFLICT_STRATEGY',
+ });
+ updateChoices.push({
+ name: 'Disable conflict detection',
+ value: 'DISABLE_CONFLICT',
+ });
+ } else {
+ updateChoices.push({
+ name: 'Enable conflict detection (required for DataStore)',
+ value: 'ENABLE_CONFLICT',
+ });
+ }
+
+ const updateOptionQuestion = {
+ type: 'list',
+ name: 'updateOption',
+ message: 'Select a setting to edit',
+ choices: updateChoices,
+ };
+
+ const { updateOption } = await inquirer.prompt([updateOptionQuestion]);
+
+ if (updateOption === 'ENABLE_CONFLICT') {
+ resolverConfig = await askResolverConflictHandlerQuestion(context, modelTypes);
+ } else if (updateOption === 'DISABLE_CONFLICT') {
+ resolverConfig = {};
+ } else if (updateOption === 'AUTH_MODE') {
+ ({ authConfig, defaultAuthType } = await askDefaultAuthQuestion(context));
+ authConfig = await askAdditionalAuthQuestions(context, authConfig, defaultAuthType);
+ } else if (updateOption === 'CONFLICT_STRATEGY') {
+ resolverConfig = await askResolverConflictHandlerQuestion(context, modelTypes);
+ }
+
+ return {
+ authConfig,
+ resolverConfig,
+ };
+};
+
+export const serviceWalkthrough = async (context: $TSContext, serviceMetadata: $TSObject) => {
+ const resourceName = resourceAlreadyExists();
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ await addLambdaAuthorizerChoice(context);
+
+ if (resourceName) {
+ const errMessage =
+ 'You already have an AppSync API in your project. Use the "amplify update api" command to update your existing AppSync API.';
+ printer.warn(errMessage);
+ await context.usageData.emitError(new ResourceAlreadyExistsError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ const { amplify } = context;
+ const { inputs } = serviceMetadata;
+
+ const basicInfoAnswers = await serviceApiInputWalkthrough(context, serviceMetadata);
+ let schemaContent = '';
+ let askToEdit = true;
+
+ // Schema template selection
+ const schemaTemplateOptions = transformerVersion === 2 ? schemaTemplatesV2 : schemaTemplatesV1;
+ const templateSelectionQuestion = {
+ type: inputs[4].type,
+ name: inputs[4].key,
+ message: inputs[4].question,
+ choices: schemaTemplateOptions.filter(templateSchemaFilter(basicInfoAnswers.output.authConfig)),
+ validate: amplify.inputValidation(inputs[4]),
+ };
+
+ const { templateSelection } = await inquirer.prompt(templateSelectionQuestion);
+ const schemaFilePath = path.join(graphqlSchemaDir, templateSelection);
+ schemaContent += transformerVersion === 2 ? defineGlobalSandboxMode(getGraphQLTransformerAuthDocLink(transformerVersion)) : '';
+ schemaContent += fs.readFileSync(schemaFilePath, 'utf8');
+
+ return {
+ ...basicInfoAnswers,
+ noCfnFile: true,
+ schemaContent,
+ askToEdit,
+ };
+};
+
+export const updateWalkthrough = async (context: $TSContext): Promise => {
+ const { allResources } = await context.amplify.getResourceStatus();
+ let resourceDir;
+ let resourceName;
+ let resource;
+ let authConfig;
+ const resources = allResources.filter(resource => resource.service === 'AppSync');
+ await addLambdaAuthorizerChoice(context);
+
+ // There can only be one appsync resource
+ if (resources.length > 0) {
+ resource = resources[0];
+ if (resource.providerPlugin !== providerName) {
+ // TODO: Move message string to seperate file
+ throw new Error(
+ `The selected resource is not managed using AWS Cloudformation. Please use the AWS AppSync Console to make updates to your API - ${resource.resourceName}`,
+ );
+ }
+ ({ resourceName } = resource);
+ resourceDir = pathManager.getResourceDirectoryPath(undefined, category, resourceName);
+ } else {
+ const errMessage = 'No AppSync resource to update. Use the "amplify add api" command to update your existing AppSync API.';
+ printer.error(errMessage);
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+ exitOnNextTick(0);
+ }
+
+ // migrate API project
+ await checkAppsyncApiResourceMigration(context, resourceName, true);
+
+ // Get models
+ const project = await readProjectConfiguration(resourceDir);
+ let resolverConfig = project.config.ResolverConfig;
+
+ await displayApiInformation(context, resource, project);
+
+ // Check for common errors
+ const directiveMap = collectDirectivesByTypeNames(project.schema);
+ let modelTypes = [];
+
+ if (directiveMap.types) {
+ Object.keys(directiveMap.types).forEach(type => {
+ if (directiveMap.types[type].includes('model')) {
+ modelTypes.push(type);
+ }
+ });
+ }
+
+ ({ authConfig, resolverConfig } = await updateApiInputWalkthrough(context, project, resolverConfig, modelTypes));
+
+ return {
+ version: 1,
+ serviceModification: {
+ serviceName: 'AppSync',
+ defaultAuthType: authConfigToAppSyncAuthType(authConfig ? authConfig.defaultAuthentication : undefined),
+ additionalAuthTypes:
+ authConfig && authConfig.additionalAuthenticationProviders
+ ? authConfig.additionalAuthenticationProviders.map(authConfigToAppSyncAuthType)
+ : undefined,
+ conflictResolution: resolverConfigToConflictResolution(resolverConfig),
+ },
+ };
+};
+
+async function displayApiInformation(context: $TSContext, resource: $TSObject, project: $TSObject) {
+ let authModes: string[] = [];
+ authModes.push(
+ `- Default: ${await displayAuthMode(context, resource, resource.output.authConfig.defaultAuthentication.authenticationType)}`,
+ );
+ await resource.output.authConfig.additionalAuthenticationProviders?.map(async authMode => {
+ authModes.push(`- ${await displayAuthMode(context, resource, authMode.authenticationType)}`);
+ });
+
+ printer.info('');
+
+ printer.info('General information');
+ printer.info('- Name: '.concat(resource.resourceName));
+ if (resource?.output?.GraphQLAPIEndpointOutput) {
+ printer.info(`- API endpoint: ${resource?.output?.GraphQLAPIEndpointOutput}`);
+ }
+ printer.info('');
+
+ printer.info('Authorization modes');
+ authModes.forEach(authMode => printer.info(authMode));
+ printer.info('');
+
+ printer.info('Conflict detection (required for DataStore)');
+ if (project.config && !_.isEmpty(project.config.ResolverConfig)) {
+ printer.info(
+ `- Conflict resolution strategy: ${
+ conflictResolutionHanlderChoices.find(choice => choice.value === project.config.ResolverConfig.project.ConflictHandler).name
+ }`,
+ );
+ } else {
+ printer.info('- Disabled');
+ }
+
+ printer.info('');
+}
+
+async function displayAuthMode(context: $TSContext, resource: $TSObject, authMode: string) {
+ if (authMode === 'API_KEY' && resource.output.GraphQLAPIKeyOutput) {
+ let { apiKeys } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'getGraphQLApiKeys', {
+ apiId: resource.output.GraphQLAPIIdOutput,
+ });
+ let apiKeyExpires = apiKeys.find(key => key.id == resource.output.GraphQLAPIKeyOutput)?.expires;
+ if (!apiKeyExpires) {
+ return authProviderChoices.find(choice => choice.value === authMode).name;
+ }
+ let apiKeyExpiresDate = new Date(apiKeyExpires * 1000);
+ return `${authProviderChoices.find(choice => choice.value === authMode).name} expiring ${apiKeyExpiresDate}: ${
+ resource.output.GraphQLAPIKeyOutput
+ }`;
+ }
+ return authProviderChoices.find(choice => choice.value === authMode).name;
+}
+
+async function askAdditionalQuestions(context: $TSContext, authConfig, defaultAuthType, modelTypes?) {
+ authConfig = await askAdditionalAuthQuestions(context, authConfig, defaultAuthType);
+ return { authConfig };
+}
+
+async function askResolverConflictQuestion(context: $TSContext, resolverConfig, modelTypes?) {
+ let resolverConfigResponse: $TSObject = {};
+
+ if (await context.prompt.confirm('Enable conflict detection?', !resolverConfig?.project)) {
+ resolverConfigResponse = await askResolverConflictHandlerQuestion(context, modelTypes);
+ }
+
+ return resolverConfigResponse;
+}
+
+async function askResolverConflictHandlerQuestion(context: $TSContext, modelTypes?) {
+ let resolverConfig: $TSObject = {};
+ const askConflictResolutionStrategy = async msg => {
+ let conflictResolutionStrategy;
+
+ do {
+ const conflictResolutionQuestion: ListQuestion = {
+ type: 'list',
+ name: 'conflictResolutionStrategy',
+ message: msg,
+ default: 'AUTOMERGE',
+ choices: conflictResolutionHanlderChoices,
+ };
+ if (conflictResolutionStrategy === 'Learn More') {
+ conflictResolutionQuestion.prefix = dataStoreLearnMore;
+ }
+ ({ conflictResolutionStrategy } = await inquirer.prompt([conflictResolutionQuestion]));
+ } while (conflictResolutionStrategy === 'Learn More');
+
+ let syncConfig: $TSObject = {
+ ConflictHandler: conflictResolutionStrategy,
+ ConflictDetection: 'VERSION',
+ };
+
+ if (conflictResolutionStrategy === 'LAMBDA') {
+ const { newFunction, lambdaFunctionName } = await askSyncFunctionQuestion();
+ syncConfig.LambdaConflictHandler = {
+ name: lambdaFunctionName,
+ new: newFunction,
+ };
+ }
+
+ return syncConfig;
+ };
+
+ resolverConfig.project = await askConflictResolutionStrategy('Select the default resolution strategy');
+
+ // Ask for per-model resolver override setting
+
+ if (modelTypes && modelTypes.length > 0) {
+ if (await context.prompt.confirm('Do you want to override default per model settings?', false)) {
+ const modelTypeQuestion = {
+ type: 'checkbox',
+ name: 'selectedModelTypes',
+ message: 'Select the models from below:',
+ choices: modelTypes,
+ };
+
+ const { selectedModelTypes } = await inquirer.prompt([modelTypeQuestion]);
+
+ if (selectedModelTypes.length > 0) {
+ resolverConfig.models = {};
+ for (const modelType of selectedModelTypes) {
+ resolverConfig.models[modelType] = await askConflictResolutionStrategy(`Select the resolution strategy for ${modelType} model`);
+ }
+ }
+ }
+ }
+
+ return resolverConfig;
+}
+
+async function askSyncFunctionQuestion() {
+ const syncLambdaQuestion = {
+ type: 'list',
+ name: 'syncLambdaAnswer',
+ message: 'Select from the options below',
+ choices: [
+ {
+ name: 'Create a new Lambda Function',
+ value: 'NEW',
+ },
+ {
+ name: 'Existing Lambda Function',
+ value: 'EXISTING',
+ },
+ ],
+ };
+
+ const { syncLambdaAnswer } = await inquirer.prompt([syncLambdaQuestion]);
+
+ let lambdaFunctionName;
+ const newFunction = syncLambdaAnswer === 'NEW';
+
+ if (!newFunction) {
+ const syncLambdaNameQuestion = {
+ type: 'input',
+ name: 'lambdaFunctionName',
+ message: 'Enter lambda function name',
+ validate: val => !!val,
+ };
+ ({ lambdaFunctionName } = await inquirer.prompt([syncLambdaNameQuestion]));
+ }
+
+ return { newFunction, lambdaFunctionName };
+}
+
+async function addLambdaAuthorizerChoice(context: $TSContext) {
+ const transformerVersion = await ApiCategoryFacade.getTransformerVersion(context);
+ if (transformerVersion === 2 && !authProviderChoices.some(choice => choice.value == 'AWS_LAMBDA')) {
+ authProviderChoices.push({
+ name: 'Lambda',
+ value: 'AWS_LAMBDA',
+ });
+ }
+}
+
+async function askDefaultAuthQuestion(context: $TSContext) {
+ await addLambdaAuthorizerChoice(context);
+ const currentAuthConfig = getAppSyncAuthConfig(stateManager.getMeta());
+ const currentDefaultAuth =
+ currentAuthConfig && currentAuthConfig.defaultAuthentication ? currentAuthConfig.defaultAuthentication.authenticationType : undefined;
+
+ const defaultAuthTypeQuestion = {
+ type: 'list',
+ name: 'defaultAuthType',
+ message: 'Choose the default authorization type for the API',
+ choices: authProviderChoices,
+ default: currentDefaultAuth,
+ };
+
+ const { defaultAuthType } = await inquirer.prompt([defaultAuthTypeQuestion]);
+
+ // Get default auth configured
+ const defaultAuth = await askAuthQuestions(defaultAuthType, context, false, currentAuthConfig?.defaultAuthentication);
+
+ return {
+ authConfig: {
+ defaultAuthentication: defaultAuth,
+ },
+ defaultAuthType,
+ };
+}
+
+export async function askAdditionalAuthQuestions(context: $TSContext, authConfig: $TSObject, defaultAuthType) {
+ const currentAuthConfig = getAppSyncAuthConfig(stateManager.getMeta());
+ authConfig.additionalAuthenticationProviders = [];
+ if (await context.prompt.confirm('Configure additional auth types?')) {
+ // Get additional auth configured
+ const remainingAuthProviderChoices = authProviderChoices.filter(p => p.value !== defaultAuthType);
+ const currentAdditionalAuth = (
+ (currentAuthConfig && currentAuthConfig.additionalAuthenticationProviders
+ ? currentAuthConfig.additionalAuthenticationProviders
+ : []) as any[]
+ ).map(authProvider => authProvider.authenticationType);
+
+ const additionalProvidersQuestion: CheckboxQuestion = {
+ type: 'checkbox',
+ name: 'authType',
+ message: 'Choose the additional authorization types you want to configure for the API',
+ choices: remainingAuthProviderChoices,
+ default: currentAdditionalAuth,
+ };
+
+ const additionalProvidersAnswer = await inquirer.prompt([additionalProvidersQuestion]);
+
+ for (const authProvider of additionalProvidersAnswer.authType) {
+ const config = await askAuthQuestions(
+ authProvider,
+ context,
+ true,
+ currentAuthConfig?.additionalAuthenticationProviders?.find(authSetting => authSetting.authenticationType == authProvider),
+ );
+
+ authConfig.additionalAuthenticationProviders.push(config);
+ }
+ } else {
+ authConfig.additionalAuthenticationProviders = (currentAuthConfig?.additionalAuthenticationProviders || []).filter(
+ p => p.authenticationType !== defaultAuthType,
+ );
+ }
+ return authConfig;
+}
+
+export async function askAuthQuestions(authType: string, context: $TSContext, printLeadText = false, authSettings) {
+ if (authType === 'AMAZON_COGNITO_USER_POOLS') {
+ if (printLeadText) {
+ printer.info('Cognito UserPool configuration');
+ }
+
+ const userPoolConfig = await askUserPoolQuestions(context);
+
+ return userPoolConfig;
+ }
+
+ if (authType === 'API_KEY') {
+ if (printLeadText) {
+ printer.info('API key configuration');
+ }
+
+ const apiKeyConfig = await askApiKeyQuestions(authSettings);
+
+ return apiKeyConfig;
+ }
+
+ if (authType === 'AWS_IAM') {
+ return {
+ authenticationType: 'AWS_IAM',
+ };
+ }
+
+ if (authType === 'OPENID_CONNECT') {
+ if (printLeadText) {
+ printer.info('OpenID Connect configuration');
+ }
+
+ const openIDConnectConfig = await askOpenIDConnectQuestions(authSettings);
+
+ return openIDConnectConfig;
+ }
+
+ if (authType === 'AWS_LAMBDA') {
+ if (printLeadText) {
+ context.print.info('Lambda Authorizer configuration');
+ }
+
+ const lambdaConfig = await askLambdaQuestion(context);
+
+ return lambdaConfig;
+ }
+
+ const errMessage = `Unknown authType: ${authType}`;
+ printer.error(errMessage);
+ await context.usageData.emitError(new UnknownResourceTypeError(errMessage));
+ exitOnNextTick(1);
+}
+
+async function askUserPoolQuestions(context: $TSContext) {
+ let authResourceName = checkIfAuthExists();
+ if (!authResourceName) {
+ authResourceName = await context.amplify.invokePluginMethod(context, 'auth', undefined, 'add', [context, true]);
+ } else {
+ printer.info('Use a Cognito user pool configured as a part of this project.');
+ }
+
+ // Added resources are prefixed with auth
+ authResourceName = `auth${authResourceName}`;
+
+ return {
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
+ userPoolConfig: {
+ userPoolId: authResourceName,
+ },
+ };
+}
+
+export async function askApiKeyQuestions(authSettings: $TSObject = undefined) {
+ let defaultValues = {
+ apiKeyExpirationDays: 7,
+ description: '',
+ };
+ Object.assign(defaultValues, authSettings?.apiKeyConfig);
+
+ const apiKeyQuestions = [
+ {
+ type: 'input',
+ name: 'description',
+ message: 'Enter a description for the API key:',
+ default: defaultValues.description,
+ },
+ {
+ type: 'input',
+ name: 'apiKeyExpirationDays',
+ message: 'After how many days from now the API key should expire (1-365):',
+ default: defaultValues.apiKeyExpirationDays,
+ validate: validateDays,
+ // adding filter to ensure parsing input as int -> https://github.com/SBoudrias/Inquirer.js/issues/866
+ filter: value => {
+ const val = parseInt(value, 10);
+ if (isNaN(val) || val <= 0 || val > 365) {
+ return value;
+ }
+ return val;
+ },
+ },
+ ];
+
+ const apiKeyConfig: $TSObject = {};
+ for (const apiKeyQuestion of apiKeyQuestions) {
+ apiKeyConfig[apiKeyQuestion.name] = await prompter.input(apiKeyQuestion.message, { initial: apiKeyQuestion.default as string })
+ }
+ const apiKeyExpirationDaysNum = Number(apiKeyConfig.apiKeyExpirationDays);
+ apiKeyConfig.apiKeyExpirationDate = Expiration.after(Duration.days(apiKeyExpirationDaysNum)).date;
+ apiKeyConfig.apiKeyExpirationDays = apiKeyExpirationDaysNum;
+
+ return {
+ authenticationType: 'API_KEY',
+ apiKeyConfig,
+ };
+}
+
+async function askOpenIDConnectQuestions(authSettings: $TSObject) {
+ let defaultValues = {
+ authTTL: undefined,
+ clientId: undefined,
+ iatTTL: undefined,
+ issuerUrl: undefined,
+ name: undefined,
+ };
+ Object.assign(defaultValues, authSettings?.openIDConnectConfig);
+
+ const openIDConnectQuestions = [
+ {
+ type: 'input',
+ name: 'name',
+ message: 'Enter a name for the OpenID Connect provider:',
+ default: defaultValues.name,
+ },
+ {
+ type: 'input',
+ name: 'issuerUrl',
+ message: 'Enter the OpenID Connect provider domain (Issuer URL):',
+ validate: validateIssuerUrl,
+ default: defaultValues.issuerUrl,
+ },
+ {
+ type: 'input',
+ name: 'clientId',
+ message: 'Enter the Client Id from your OpenID Client Connect application (optional):',
+ default: defaultValues.clientId,
+ },
+ {
+ type: 'input',
+ name: 'iatTTL',
+ message: 'Enter the number of milliseconds a token is valid after being issued to a user:',
+ validate: validateTTL,
+ default: defaultValues.iatTTL,
+ },
+ {
+ type: 'input',
+ name: 'authTTL',
+ message: 'Enter the number of milliseconds a token is valid after being authenticated:',
+ validate: validateTTL,
+ default: defaultValues.authTTL,
+ },
+ ];
+
+ const openIDConnectConfig = await inquirer.prompt(openIDConnectQuestions);
+
+ return {
+ authenticationType: 'OPENID_CONNECT',
+ openIDConnectConfig,
+ };
+}
+
+async function validateDays(input: string) {
+ const isValid = /^\d{0,3}$/.test(input);
+ const days = isValid ? parseInt(input, 10) : 0;
+ if (!isValid || days < 1 || days > 365) {
+ return 'Number of days must be between 1 and 365.';
+ }
+
+ return true;
+}
+
+function validateIssuerUrl(input: string) {
+ const isValid =
+ /^(((?!http:\/\/(?!localhost))([a-zA-Z0-9.]{1,}):\/\/([a-zA-Z0-9-._~:?#@!$&'()*+,;=/]{1,})\/)|(?!http)(?!https)([a-zA-Z0-9.]{1,}):\/\/)$/.test(
+ input,
+ );
+
+ if (!isValid) {
+ return 'The value must be a valid URI with a trailing forward slash. HTTPS must be used instead of HTTP unless you are using localhost.';
+ }
+
+ return true;
+}
+
+function validateTTL(input: string) {
+ const isValid = /^\d+$/.test(input);
+
+ if (!isValid) {
+ return 'The value must be a number.';
+ }
+
+ return true;
+}
+
+function resourceAlreadyExists() {
+ const meta = stateManager.getMeta();
+ let resourceName;
+
+ if (meta[category]) {
+ const categoryResources = meta[category];
+ for (const resource of Object.keys(categoryResources)) {
+ if (categoryResources[resource].service === serviceName) {
+ resourceName = resource;
+ break;
+ }
+ }
+ }
+
+ return resourceName;
+}
+
+export const migrate = async (context: $TSContext) => {
+ await context.amplify.executeProviderUtils(context, 'awscloudformation', 'compileSchema', {
+ forceCompile: true,
+ migrate: true,
+ });
+};
+
+export const getIAMPolicies = (resourceName: string, operations: string[]) => {
+ let policy: $TSObject = {};
+ const resources = [];
+ const actions = [];
+ if (!FeatureFlags.getBoolean('appSync.generateGraphQLPermissions')) {
+ operations.forEach(crudOption => {
+ switch (crudOption) {
+ case 'create':
+ actions.push('appsync:Create*', 'appsync:StartSchemaCreation', 'appsync:GraphQL');
+ resources.push(buildPolicyResource(resourceName, '/*'));
+ break;
+ case 'update':
+ actions.push('appsync:Update*');
+ break;
+ case 'read':
+ actions.push('appsync:Get*', 'appsync:List*');
+ break;
+ case 'delete':
+ actions.push('appsync:Delete*');
+ break;
+ default:
+ printer.info(`${crudOption} not supported`);
+ }
+ });
+ resources.push(buildPolicyResource(resourceName, null));
+ } else {
+ actions.push('appsync:GraphQL');
+ operations.forEach(operation => resources.push(buildPolicyResource(resourceName, `/types/${operation}/*`)));
+ }
+
+ policy = {
+ Effect: 'Allow',
+ Action: actions,
+ Resource: resources,
+ };
+
+ const attributes = ['GraphQLAPIIdOutput', 'GraphQLAPIEndpointOutput'];
+ if (authConfigHasApiKey(getAppSyncAuthConfig(stateManager.getMeta()))) {
+ attributes.push('GraphQLAPIKeyOutput');
+ }
+
+ return { policy, attributes };
+};
+
+const buildPolicyResource = (resourceName: string, path: string | null) => {
+ return {
+ 'Fn::Join': [
+ '',
+ [
+ 'arn:aws:appsync:',
+ { Ref: 'AWS::Region' },
+ ':',
+ { Ref: 'AWS::AccountId' },
+ ':apis/',
+ {
+ Ref: `${category}${resourceName}GraphQLAPIIdOutput`,
+ },
+ ...(path ? [path] : []),
+ ],
+ ],
+ };
+};
+
+const templateSchemaFilter = authConfig => {
+ const authIncludesCognito = getAuthTypes(authConfig).includes('AMAZON_COGNITO_USER_POOLS');
+ return (templateOption: ListChoiceOptions): boolean =>
+ authIncludesCognito ||
+ templateOption.name !== 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)';
+};
+
+const getAuthTypes = authConfig => {
+ const additionalAuthTypes = (authConfig.additionalAuthenticationProviders || [])
+ .map(provider => provider.authenticationType)
+ .filter(t => !!t);
+
+ const uniqueAuthTypes = new Set([...additionalAuthTypes, authConfig.defaultAuthentication.authenticationType]);
+
+ return [...uniqueAuthTypes.keys()];
+};
+
+async function askLambdaQuestion(context) {
+ const existingFunctions = functionsExist(context);
+ const choices = [
+ {
+ name: 'Create a new Lambda function',
+ value: 'newFunction',
+ },
+ ];
+ if (existingFunctions) {
+ choices.push({
+ name: 'Use a Lambda function already added in the current Amplify project',
+ value: 'projectFunction',
+ });
+ }
+
+ let defaultFunctionType = 'newFunction';
+ const lambdaAnswer = await inquirer.prompt({
+ name: 'functionType',
+ type: 'list',
+ message: 'Choose a Lambda authorization function',
+ choices,
+ default: defaultFunctionType,
+ });
+
+ const { lambdaFunction } = await askLambdaSource(context, lambdaAnswer.functionType);
+ const { ttlSeconds } = await inquirer.prompt({
+ type: 'input',
+ name: 'ttlSeconds',
+ message: 'How long should the authorization response be cached in seconds?',
+ validate: validateTTL,
+ default: '300',
+ });
+
+ const lambdaAuthorizerConfig = {
+ lambdaFunction,
+ ttlSeconds,
+ };
+
+ return {
+ authenticationType: 'AWS_LAMBDA',
+ lambdaAuthorizerConfig,
+ };
+}
+
+function functionsExist(context: $TSContext): boolean {
+ const functionResources = context.amplify.getProjectDetails().amplifyMeta.function;
+ if (!functionResources) {
+ return false;
+ }
+
+ const lambdaFunctions = [];
+ Object.keys(functionResources).forEach(resourceName => {
+ if (functionResources[resourceName].service === FunctionServiceNameLambdaFunction) {
+ lambdaFunctions.push(resourceName);
+ }
+ });
+
+ return lambdaFunctions.length !== 0;
+}
+
+async function askLambdaSource(context: $TSContext, functionType: string) {
+ switch (functionType) {
+ case 'projectFunction':
+ return await askLambdaFromProject(context);
+ case 'newFunction':
+ return await newLambdaFunction(context);
+ default:
+ throw new Error(`Type ${functionType} not supported`);
+ }
+}
+
+async function newLambdaFunction(context: $TSContext) {
+ const resourceName = await createLambdaAuthorizerFunction(context);
+ return { lambdaFunction: resourceName };
+}
+
+async function askLambdaFromProject(context: $TSContext) {
+ const functionResources = context.amplify.getProjectDetails().amplifyMeta.function;
+ const lambdaFunctions = [];
+ Object.keys(functionResources).forEach(resourceName => {
+ if (functionResources[resourceName].service === FunctionServiceNameLambdaFunction) {
+ lambdaFunctions.push(resourceName);
+ }
+ });
+
+ const answer = await inquirer.prompt({
+ name: 'lambdaFunction',
+ type: 'list',
+ message: 'Choose one of the Lambda functions',
+ choices: lambdaFunctions,
+ default: lambdaFunctions[0],
+ });
+
+ await context.amplify.invokePluginMethod(context, 'function', undefined, 'addAppSyncInvokeMethodPermission', [answer.lambdaFunction]);
+
+ return { lambdaFunction: answer.lambdaFunction };
+}
+
+async function createLambdaAuthorizerFunction(context: $TSContext) {
+ const [shortId] = uuid().split('-');
+ const functionName = `graphQlLambdaAuthorizer${shortId}`;
+ const resourceName = await context.amplify.invokePluginMethod(context, 'function', undefined, 'add', [
+ context,
+ 'awscloudformation',
+ FunctionServiceNameLambdaFunction,
+ {
+ functionName,
+ defaultRuntime: 'nodejs',
+ providerContext: {
+ provider: 'awscloudformation',
+ },
+ template: 'lambda-auth',
+ skipAdvancedSection: true,
+ skipNextSteps: true,
+ },
+ ]);
+
+ context.print.success(`Successfully added ${resourceName} function locally`);
+ await context.amplify.invokePluginMethod(context, 'function', undefined, 'addAppSyncInvokeMethodPermission', [resourceName]);
+ return resourceName;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/containers-walkthrough.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/containers-walkthrough.ts
new file mode 100644
index 0000000000..5ba0df021b
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/service-walkthroughs/containers-walkthrough.ts
@@ -0,0 +1,358 @@
+import { $TSAny, $TSContext, $TSObject, exitOnNextTick, ResourceDoesNotExistError } from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import inquirer from 'inquirer';
+import { category } from '../../../category-constants';
+import { DEPLOYMENT_MECHANISM } from '../base-api-stack';
+import { GitHubSourceActionInfo } from '../pipeline-with-awaiter';
+import { getAllDefaults } from '../default-values/containers-defaults';
+
+const serviceName = 'ElasticContainer';
+
+/**
+ * Designed to be backwards compatible with the old way of representing dependencies as
+ * {
+ * category: string
+ * resourceName: string
+ * attributes: string[]
+ * }
+ * and auto-generating environment variable names based on this info
+ * When attributeEnvMap is specified, it can specify a custom environment variable name for a dependency attribute
+ * If no mapping is found for an attribute in the map, then it falls back to the autogenerated value
+ */
+export interface ResourceDependency {
+ category: string; // resource category of the dependency
+ resourceName: string; // name of the dependency
+ attributes: string[]; // attributes that this function depends on (must be outputs of the dependencies CFN template)
+ attributeEnvMap?: { [name: string]: string }; // optional attributes to environment variable names map that will be exposed to the function
+}
+
+export enum API_TYPE {
+ GRAPHQL = 'GRAPHQL',
+ REST = 'REST',
+}
+
+export type ServiceConfiguration = {
+ resourceName: string;
+ imageSource: { type: IMAGE_SOURCE_TYPE; template?: string };
+ gitHubPath: string;
+ authName: string;
+ gitHubToken: string;
+ deploymentMechanism: DEPLOYMENT_MECHANISM;
+ restrictAccess: boolean;
+ dependsOn?: ResourceDependency[]; // resources this function depends on
+ categoryPolicies?: any[]; // IAM policies that should be applied to this lambda
+ mutableParametersState?: any; // Contains the object that is written to function-parameters.json. Kindof a hold-over from older code
+ environmentMap?: Record; // Existing function environment variable map. Should refactor to use dependsOn directly,
+ gitHubInfo?: GitHubSourceActionInfo;
+};
+
+export async function serviceWalkthrough(context: $TSContext, apiType: API_TYPE): Promise> {
+ const allDefaultValues = getAllDefaults();
+
+ const resourceName = await askResourceName(context, allDefaultValues);
+
+ const containerInfo = await askContainerSource(context, resourceName, apiType);
+
+ return { resourceName, ...containerInfo };
+}
+
+async function askResourceName(context: $TSContext, allDefaultValues: $TSObject) {
+ const { amplify } = context;
+
+ const { resourceName } = await inquirer.prompt([
+ {
+ name: 'resourceName',
+ type: 'input',
+ message: 'Provide a friendly name for your resource to be used as a label for this category in the project:',
+ default: allDefaultValues.resourceName,
+ validate: amplify.inputValidation({
+ validation: {
+ operator: 'regex',
+ value: '^(?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*$',
+ onErrorMsg: 'Resource name should be alphanumeric with no uppercase letters',
+ },
+ required: true,
+ }),
+ },
+ ]);
+
+ return resourceName;
+}
+
+async function askContainerSource(context: $TSContext, resourceName: string, apiType: API_TYPE): Promise> {
+ return newContainer(context, resourceName, apiType);
+}
+
+export enum IMAGE_SOURCE_TYPE {
+ TEMPLATE = 'TEMPLATE',
+ CUSTOM = 'CUSTOM',
+}
+
+async function newContainer(context: $TSContext, resourceName: string, apiType: API_TYPE): Promise> {
+ let imageSource: { type: IMAGE_SOURCE_TYPE; template?: string };
+ let choices = [];
+
+ if (apiType === API_TYPE.GRAPHQL) {
+ choices.push({
+ name: 'ExpressJS - GraphQL template',
+ value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'graphql-express' },
+ });
+ }
+
+ if (apiType === API_TYPE.REST) {
+ choices.push({
+ name: 'ExpressJS - REST template',
+ value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'dockerfile-rest-express' },
+ });
+
+ choices.push({
+ name: 'Docker Compose - ExpressJS + Flask template',
+ value: { type: IMAGE_SOURCE_TYPE.TEMPLATE, template: 'dockercompose-rest-express' },
+ });
+ }
+
+ choices = choices.concat([
+ {
+ name: 'Custom (bring your own Dockerfile or docker-compose.yml)',
+ value: { type: IMAGE_SOURCE_TYPE.CUSTOM },
+ },
+ {
+ name: 'Learn More',
+ value: undefined,
+ },
+ ]);
+
+ do {
+ ({ imageSource } = await inquirer.prompt([
+ {
+ name: 'imageSource',
+ type: 'list',
+ message: 'What image would you like to use',
+ choices,
+ default: 'express_hello_world',
+ },
+ ]));
+ } while (imageSource === undefined);
+
+ let deploymentMechanismQuestion;
+
+ const deploymentMechanismChoices = [
+ {
+ name: 'On every "amplify push" (Fully managed container source)',
+ value: DEPLOYMENT_MECHANISM.FULLY_MANAGED,
+ },
+ ];
+
+ if (imageSource.type === IMAGE_SOURCE_TYPE.CUSTOM) {
+ deploymentMechanismChoices.push({
+ name: 'On every Github commit (Independently managed container source)',
+ value: DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED,
+ });
+ }
+
+ deploymentMechanismChoices.push({
+ name: 'Advanced: Self-managed (Learn more: docs.amplify.aws/cli/usage/containers)',
+ value: DEPLOYMENT_MECHANISM.SELF_MANAGED,
+ });
+
+ do {
+ deploymentMechanismQuestion = await inquirer.prompt([
+ {
+ name: 'deploymentMechanism',
+ type: 'list',
+ message: 'When do you want to build & deploy the Fargate task',
+ choices: deploymentMechanismChoices,
+ },
+ ]);
+ } while (deploymentMechanismQuestion.deploymentMechanism === 'Learn More');
+
+ let gitHubPath: string;
+ let gitHubToken: string;
+
+ if (deploymentMechanismQuestion.deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
+ printer.info('We need a Github Personal Access Token to automatically build & deploy your Fargate task on every Github commit.');
+ printer.info(
+ 'Learn more about Github Personal Access Token here: https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token',
+ );
+
+ const gitHubQuestions = await inquirer.prompt([
+ {
+ name: 'github_access_token',
+ type: 'password',
+ message: 'GitHub Personal Access Token:',
+ },
+ {
+ name: 'github_path',
+ type: 'input',
+ message: 'Path to your repo:',
+ },
+ ]);
+
+ gitHubPath = gitHubQuestions.github_path;
+ gitHubToken = gitHubQuestions.github_access_token;
+ }
+
+ const meta = context.amplify.getProjectDetails().amplifyMeta;
+ const hasAccessableResources = ['storage', 'function'].some(categoryName => {
+ return Object.keys(meta[categoryName] ?? {}).length > 0;
+ });
+ let rolePermissions: any = {};
+ if (
+ hasAccessableResources &&
+ (await context.amplify.confirmPrompt('Do you want to access other resources in this project from your api?'))
+ ) {
+ rolePermissions = await context.amplify.invokePluginMethod(context, 'function', undefined, 'askExecRolePermissionsQuestions', [
+ context,
+ resourceName,
+ undefined,
+ undefined,
+ category,
+ serviceName,
+ ]);
+ }
+
+ const { categoryPolicies, environmentMap, dependsOn, mutableParametersState } = rolePermissions;
+
+ const restrictApiQuestion = await inquirer.prompt({
+ name: 'rescrict_access',
+ type: 'confirm',
+ message: 'Do you want to restrict API access',
+ default: true,
+ });
+
+ return {
+ imageSource,
+ gitHubPath,
+ gitHubToken,
+ deploymentMechanism: deploymentMechanismQuestion.deploymentMechanism,
+ restrictAccess: restrictApiQuestion.rescrict_access,
+ categoryPolicies,
+ environmentMap,
+ dependsOn,
+ mutableParametersState,
+ };
+}
+
+export async function updateWalkthrough(context: $TSContext, apiType: API_TYPE) {
+ const { allResources } = await context.amplify.getResourceStatus();
+
+ const resources = allResources
+ .filter(
+ resource =>
+ resource.category === category && resource.service === serviceName && !!resource.providerPlugin && resource.apiType === apiType,
+ )
+ .map(resource => resource.resourceName);
+
+ // There can only be one appsync resource
+ if (resources.length === 0) {
+ const errMessage = `No ${apiType} API resource to update. Use "amplify add api" command to create a new ${apiType} API`;
+ printer.error(errMessage);
+ await context.usageData.emitError(new ResourceDoesNotExistError(errMessage));
+ exitOnNextTick(0);
+ return;
+ }
+
+ const question = [
+ {
+ name: 'resourceName',
+ message: 'Please select the API you would want to update',
+ type: 'list',
+ choices: resources,
+ },
+ ];
+
+ const { resourceName } = await inquirer.prompt(question);
+
+ const resourceSettings = allResources.find(
+ resource =>
+ resource.resourceName === resourceName &&
+ resource.category === category &&
+ resource.service === serviceName &&
+ !!resource.providerPlugin,
+ );
+
+ let { gitHubInfo: { path = undefined } = {} } = resourceSettings;
+ let gitHubToken;
+
+ if (resourceSettings.deploymentMechanism === DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED) {
+ if (await confirm('Would you like to change your GitHub access token')) {
+ const gitHubQuestion = await inquirer.prompt({
+ name: 'gitHubAccessToken',
+ type: 'password',
+ message: 'GitHub Personal Access Token:',
+ });
+ gitHubToken = gitHubQuestion.gitHubAccessToken;
+ }
+
+ if (await confirm('Would you like to change your GitHub Path to your repo')) {
+ const gitHubQuestion = await inquirer.prompt({
+ name: 'gitHubPath',
+ type: 'input',
+ message: 'Path to your repo:',
+ default: path,
+ });
+ path = gitHubQuestion.gitHubPath;
+ }
+ }
+
+ const { environmentMap = {}, mutableParametersState = {} } = resourceSettings;
+
+ const meta = context.amplify.getProjectDetails().amplifyMeta;
+ const hasAccessableResources = ['storage', 'function'].some(categoryName => {
+ return Object.keys(meta[categoryName] ?? {}).length > 0;
+ });
+ let rolePermissions: $TSAny = {};
+ if (
+ hasAccessableResources &&
+ (await context.amplify.confirmPrompt('Do you want to access other resources in this project from your api?'))
+ ) {
+ rolePermissions = await context.amplify.invokePluginMethod(context, 'function', undefined, 'askExecRolePermissionsQuestions', [
+ context,
+ resourceName,
+ mutableParametersState.permissions,
+ environmentMap,
+ category,
+ serviceName,
+ ]);
+ }
+
+ const {
+ categoryPolicies = [],
+ environmentMap: newEnvironmentMap,
+ dependsOn: newDependsOn = [],
+ mutableParametersState: newMutableParametersState,
+ } = rolePermissions;
+
+ const { restrict_access: restrictAccess } = await inquirer.prompt({
+ name: 'restrict_access',
+ type: 'confirm',
+ message: 'Do you want to restrict API access',
+ default: resourceSettings.restrictAccess,
+ });
+
+ return {
+ ...resourceSettings,
+ restrictAccess,
+ environmentMap: newEnvironmentMap,
+ mutableParametersState: newMutableParametersState,
+ dependsOn: newDependsOn,
+ categoryPolicies,
+ gitHubPath: path,
+ gitHubToken,
+ };
+}
+
+async function confirm(question: string) {
+ const { confirm } = await inquirer.prompt({
+ type: 'confirm',
+ default: false,
+ message: question,
+ name: 'confirm',
+ });
+
+ return confirm;
+}
+
+export async function getPermissionPolicies(context, service, resourceName, crudOptions) {
+ throw new Error('IAM access not available for this resource');
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/sync-conflict-handler-assets/syncAssets.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/sync-conflict-handler-assets/syncAssets.ts
new file mode 100644
index 0000000000..bbde2f3300
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/sync-conflict-handler-assets/syncAssets.ts
@@ -0,0 +1,16 @@
+import chalk from 'chalk';
+
+const learnMore = `When more than one system is updating an item in your GraphQL backend at a single time, Amplify DataStore can use different strategies with AWS AppSync to resolve these conflicts based on your use case.
+This can be on the entire API (recommended) or for advanced use cases you can change these for each one of your GraphQL types.
+
+Automerge is the default mechanism where GraphQL type information can be used to merge objects using the scalar type context as long as two fields in a type are not in conflict.
+When this happens the data is merged and AppSync will update the object version so that all clients are updated.
+This also functions on lists of scalars where the updates are concatenated.
+
+Optimistic Concurrency Control accepts the latest committed write to the database.
+Other writers are rejected and must handle merges through other means, such as a client-side callback.
+
+Finally you can also also configure a Lambda Function to resolve conflicts depending on your custom business need, such as letting specific users in a system have priority on making updates to data.
+`;
+
+export const dataStoreLearnMore = chalk.green(learnMore);
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/amplify-meta-utils.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/amplify-meta-utils.ts
new file mode 100644
index 0000000000..e3385279de
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/amplify-meta-utils.ts
@@ -0,0 +1,58 @@
+import { $TSAny, $TSMeta, $TSObject, AmplifyCategories, AmplifySupportedService, stateManager } from 'amplify-cli-core';
+import _ from 'lodash';
+
+export const authConfigHasApiKey = (authConfig?: $TSAny) => {
+ if (!authConfig) {
+ return false;
+ }
+ return (
+ Array.of(authConfig.defaultAuthentication)
+ .concat(authConfig.additionalAuthenticationProviders)
+ .filter(auth => !!auth) // filter out undefined elements which can happen if there are no addtl auth providers
+ .map(auth => auth.authenticationType)
+ .findIndex(authType => authType === 'API_KEY') > -1
+ );
+};
+
+export const checkIfAuthExists = () => {
+ const amplifyMeta = stateManager.getMeta();
+ let authResourceName;
+ const authServiceName = AmplifySupportedService.COGNITO;
+ const authCategoryName = AmplifyCategories.AUTH;
+
+ if (amplifyMeta[authCategoryName] && Object.keys(amplifyMeta[authCategoryName]).length > 0) {
+ const categoryResources = amplifyMeta[authCategoryName];
+ Object.keys(categoryResources).forEach(resource => {
+ if (categoryResources[resource].service === authServiceName) {
+ authResourceName = resource;
+ }
+ });
+ }
+
+ return authResourceName;
+};
+
+// some utility functions to extract the AppSync API name and config from amplify-meta
+
+export const getAppSyncAuthConfig = (projectMeta: $TSMeta) => {
+ const entry = getAppSyncAmplifyMetaEntry(projectMeta);
+ if (entry) {
+ const value = entry[1] as $TSAny;
+ return value && value.output ? value.output.authConfig : {};
+ }
+};
+
+export const getAppSyncResourceName = (projectMeta: $TSMeta): string | undefined => {
+ const entry = getAppSyncAmplifyMetaEntry(projectMeta);
+ if (entry) {
+ return entry[0];
+ }
+};
+
+// project meta is the contents of amplify-meta.json
+// typically retreived using context.amplify.getProjectMeta()
+const getAppSyncAmplifyMetaEntry = (projectMeta: $TSMeta) => {
+ return Object.entries(projectMeta[AmplifyCategories.API] || {}).find(
+ ([, value]) => (value as $TSObject).service === AmplifySupportedService.APPSYNC,
+ );
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.ts
new file mode 100644
index 0000000000..948b72a495
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/auth-config-to-app-sync-auth-type-bi-di-mapper.ts
@@ -0,0 +1,94 @@
+import {
+ AppSyncAuthType,
+ AppSyncAPIKeyAuthType,
+ AppSyncCognitoUserPoolsAuthType,
+ AppSyncOpenIDConnectAuthType,
+ AppSyncLambdaAuthType,
+} from 'amplify-headless-interface';
+import _ from 'lodash';
+
+/**
+ * Converts the authConfig object that is returned by the AppSync walkthrough into the AppSyncAuthType defined by the AddApiRequest
+ *
+ * Used when transforming the walkthrough result into an AddApiRequest
+ */
+export const authConfigToAppSyncAuthType = (authConfig: any = {}): AppSyncAuthType => {
+ return _.get(authConfigToAppSyncAuthTypeMap, authConfig.authenticationType, () => undefined)(authConfig);
+};
+
+/**
+ * Converts an AppSyncAuthType object into the authConfig object that gets written to amplify-meta
+ *
+ * This conversion is necessary to ensure we are storing the authConfig in the same way as older projects
+ * @param authType
+ */
+export const appSyncAuthTypeToAuthConfig = (authType?: AppSyncAuthType) => {
+ if (!authType) return undefined;
+ return _.get(appSyncAuthTypeToAuthConfigMap, authType.mode, () => undefined)(authType);
+};
+
+const authConfigToAppSyncAuthTypeMap: Record AppSyncAuthType> = {
+ API_KEY: authConfig => ({
+ mode: 'API_KEY',
+ expirationTime: authConfig.apiKeyConfig.apiKeyExpirationDays,
+ apiKeyExpirationDate: authConfig.apiKeyConfig?.apiKeyExpirationDate,
+ keyDescription: authConfig.apiKeyConfig.description,
+ }),
+ AWS_IAM: () => ({
+ mode: 'AWS_IAM',
+ }),
+ AMAZON_COGNITO_USER_POOLS: authConfig => ({
+ mode: 'AMAZON_COGNITO_USER_POOLS',
+ cognitoUserPoolId: authConfig.userPoolConfig.userPoolId,
+ }),
+ OPENID_CONNECT: authConfig => ({
+ mode: 'OPENID_CONNECT',
+ openIDProviderName: authConfig.openIDConnectConfig.name,
+ openIDIssuerURL: authConfig.openIDConnectConfig.issuerUrl,
+ openIDClientID: authConfig.openIDConnectConfig.clientId,
+ openIDAuthTTL: authConfig.openIDConnectConfig.authTTL,
+ openIDIatTTL: authConfig.openIDConnectConfig.iatTTL,
+ }),
+ AWS_LAMBDA: authConfig => ({
+ mode: 'AWS_LAMBDA',
+ lambdaFunction: authConfig.lambdaAuthorizerConfig.lambdaFunction,
+ ttlSeconds: authConfig.lambdaAuthorizerConfig.ttlSeconds,
+ }),
+};
+
+const appSyncAuthTypeToAuthConfigMap: Record any> = {
+ API_KEY: (authType: AppSyncAPIKeyAuthType) => ({
+ authenticationType: 'API_KEY',
+ apiKeyConfig: {
+ apiKeyExpirationDays: authType.expirationTime,
+ apiKeyExpirationDate: authType?.apiKeyExpirationDate,
+ description: authType.keyDescription,
+ },
+ }),
+ AWS_IAM: () => ({
+ authenticationType: 'AWS_IAM',
+ }),
+ AMAZON_COGNITO_USER_POOLS: (authType: AppSyncCognitoUserPoolsAuthType) => ({
+ authenticationType: 'AMAZON_COGNITO_USER_POOLS',
+ userPoolConfig: {
+ userPoolId: authType.cognitoUserPoolId,
+ },
+ }),
+ OPENID_CONNECT: (authType: AppSyncOpenIDConnectAuthType) => ({
+ authenticationType: 'OPENID_CONNECT',
+ openIDConnectConfig: {
+ name: authType.openIDProviderName,
+ issuerUrl: authType.openIDIssuerURL,
+ clientId: authType.openIDClientID,
+ authTTL: authType.openIDAuthTTL,
+ iatTTL: authType.openIDIatTTL,
+ },
+ }),
+ AWS_LAMBDA: (authType: AppSyncLambdaAuthType) => ({
+ authenticationType: 'AWS_LAMBDA',
+ lambdaAuthorizerConfig: {
+ lambdaFunction: authType.lambdaFunction,
+ ttlSeconds: authType.ttlSeconds,
+ },
+ }),
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/check-appsync-api-migration.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/check-appsync-api-migration.ts
new file mode 100644
index 0000000000..6f49e0cccb
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/check-appsync-api-migration.ts
@@ -0,0 +1,19 @@
+import { $TSContext, AmplifyCategories, getMigrateResourceMessageForOverride } from 'amplify-cli-core';
+import { printer, prompter } from 'amplify-prompts';
+import { AppsyncApiInputState } from '../api-input-manager/appsync-api-input-state';
+import { migrateResourceToSupportOverride } from './migrate-api-override-resource';
+
+export const checkAppsyncApiResourceMigration = async (context: $TSContext, apiName: string, isUpdate: boolean): Promise => {
+ const cliState = new AppsyncApiInputState(context, apiName);
+ if (!cliState.cliInputFileExists()) {
+ printer.debug('cli-inputs.json doesnt exist');
+ const headlessMigrate = context.input.options?.yes || context.input.options?.forcePush || context.input.options?.headless;
+ if (headlessMigrate || (await prompter.yesOrNo(getMigrateResourceMessageForOverride(AmplifyCategories.API, apiName, isUpdate), true))) {
+ // generate cli-inputs for migration from parameters.json
+ await migrateResourceToSupportOverride(apiName);
+ return true;
+ }
+ return false;
+ }
+ return true;
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/containers-artifacts.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/containers-artifacts.ts
new file mode 100644
index 0000000000..e411cfcc33
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/containers-artifacts.ts
@@ -0,0 +1,364 @@
+import { Octokit } from '@octokit/rest';
+import * as fs from 'fs-extra';
+import inquirer from 'inquirer';
+import * as path from 'path';
+import { v4 as uuid } from 'uuid';
+import { provider as cloudformationProviderName } from '../../../provider-utils/awscloudformation/aws-constants';
+import { getContainers } from '../../../provider-utils/awscloudformation/docker-compose';
+import Container from '../docker-compose/ecs-objects/container';
+import { EcsStack } from '../ecs-apigw-stack';
+import { API_TYPE, ResourceDependency } from '../../../provider-utils/awscloudformation/service-walkthroughs/containers-walkthrough';
+import { getGitHubOwnerRepoFromPath } from '../../../provider-utils/awscloudformation/utils/github';
+import { $TSAny, $TSContext, JSONUtilities, pathManager, readCFNTemplate } from 'amplify-cli-core';
+import { DEPLOYMENT_MECHANISM } from '../base-api-stack';
+import { setExistingSecretArns } from './containers/set-existing-secret-arns';
+import { category } from '../../../category-constants';
+import * as cdk from '@aws-cdk/core';
+
+export const cfnFileName = (resourceName: string) => `${resourceName}-cloudformation-template.json`;
+
+export type ApiResource = {
+ category: string;
+ resourceName: string;
+ gitHubInfo?: {
+ path: string;
+ tokenSecretArn: string;
+ };
+ deploymentMechanism: DEPLOYMENT_MECHANISM;
+ authName: string;
+ restrictAccess: boolean;
+ dependsOn: ResourceDependency[];
+ environmentMap: Record;
+ categoryPolicies: $TSAny[];
+ mutableParametersState: $TSAny;
+ output?: Record;
+ apiType?: API_TYPE;
+ exposedContainer?: { name: string; port: number };
+};
+
+type ExposedContainer = {
+ name: string;
+ port: number;
+};
+
+type ContainerArtifactsMetadata = {
+ exposedContainer: ExposedContainer;
+ pipelineInfo: { consoleUrl: string };
+};
+
+export async function generateContainersArtifacts(
+ context: $TSContext,
+ resource: ApiResource,
+ askForExposedContainer: boolean = false,
+): Promise {
+ const {
+ providers: { [cloudformationProviderName]: provider },
+ } = context.amplify.getProjectMeta();
+
+ const { StackName: envName } = provider;
+
+ const {
+ category: categoryName,
+ resourceName,
+ gitHubInfo,
+ deploymentMechanism,
+ categoryPolicies = [],
+ dependsOn,
+ environmentMap,
+ restrictAccess,
+ apiType,
+ } = resource;
+
+ const backendDir = context.amplify.pathManager.getBackendDirPath();
+ const resourceDir = path.normalize(path.join(backendDir, categoryName, resourceName));
+ const srcPath = path.join(resourceDir, 'src');
+
+ const { containersPorts, containers, isInitialDeploy, desiredCount, exposedContainer, secretsArns } = await processDockerConfig(
+ context,
+ resource,
+ srcPath,
+ askForExposedContainer,
+ );
+ const repositories = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'describeEcrRepositories');
+
+ const existingEcrRepositories: Set = new Set(
+ repositories
+ .map(({ repositoryName }) => repositoryName)
+ .filter(repositoryName => repositoryName.startsWith(`${envName}-${categoryName}-${resourceName}-`)),
+ );
+
+ const stack = new EcsStack(undefined, 'ContainersStack', {
+ categoryName,
+ apiName: resourceName,
+ taskPorts: containersPorts,
+ dependsOn,
+ policies: categoryPolicies,
+ taskEnvironmentVariables: environmentMap,
+ gitHubSourceActionInfo: gitHubInfo,
+ deploymentMechanism,
+ containers,
+ isInitialDeploy,
+ desiredCount,
+ restrictAccess,
+ currentStackName: envName,
+ apiType,
+ exposedContainer,
+ secretsArns,
+ existingEcrRepositories,
+ });
+
+ const cfn = stack.toCloudFormation();
+
+ JSONUtilities.writeJson(path.normalize(path.join(resourceDir, cfnFileName(resourceName))), cfn);
+
+ return {
+ exposedContainer,
+ pipelineInfo: { consoleUrl: stack.getPipelineConsoleUrl(provider.Region) },
+ };
+}
+
+export async function processDockerConfig(
+ context: $TSContext,
+ resource: ApiResource,
+ srcPath: string,
+ askForExposedContainer: boolean = false,
+) {
+ const {
+ providers: { [cloudformationProviderName]: provider },
+ } = context.amplify.getProjectMeta();
+
+ const { StackName: envName } = provider;
+
+ const { resourceName, gitHubInfo, deploymentMechanism, output, exposedContainer: exposedContainerFromMeta } = resource;
+
+ const dockerComposeFileNameYaml = 'docker-compose.yaml';
+ const dockerComposeFileNameYml = 'docker-compose.yml';
+ const dockerfileFileName = 'Dockerfile';
+
+ const containerDefinitionFileNames = [dockerComposeFileNameYaml, dockerComposeFileNameYml, dockerfileFileName];
+
+ const containerDefinitionFiles: Record = {};
+
+ for await (const fileName of containerDefinitionFileNames) {
+ switch (deploymentMechanism) {
+ case DEPLOYMENT_MECHANISM.FULLY_MANAGED:
+ case DEPLOYMENT_MECHANISM.SELF_MANAGED:
+ const filePath = path.normalize(path.join(srcPath, fileName));
+
+ if (fs.existsSync(filePath)) {
+ containerDefinitionFiles[fileName] = fs.readFileSync(filePath).toString();
+ }
+ break;
+ case DEPLOYMENT_MECHANISM.INDENPENDENTLY_MANAGED:
+ const { path: repoUri, tokenSecretArn } = gitHubInfo;
+
+ const { SecretString: gitHubToken } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'retrieveSecret', {
+ secretArn: tokenSecretArn,
+ });
+
+ const octokit = new Octokit({ auth: gitHubToken });
+
+ const { owner, repo, branch, path: pathInRepo } = getGitHubOwnerRepoFromPath(repoUri);
+
+ try {
+ const {
+ data: { content, encoding },
+ } = (await octokit.repos.getContent({
+ owner,
+ repo,
+ ...(branch ? { ref: branch } : undefined), // only include branch if not undefined
+ path: path.join(pathInRepo, fileName),
+ })) as { data: { content?: string; encoding?: string } };
+
+ containerDefinitionFiles[fileName] = Buffer.from(content, encoding).toString('utf8');
+ } catch (error) {
+ const { status } = error;
+
+ // It is ok if the file doesn't exist, we skip it
+ if (status !== 404) {
+ throw error;
+ }
+ }
+ break;
+ default: {
+ const exhaustiveCheck: never = deploymentMechanism;
+ throw new Error(`Unhandled type [${exhaustiveCheck}]`);
+ }
+ }
+ }
+
+ if (Object.keys(containerDefinitionFiles).length === 0) {
+ throw new Error('No definition available (docker-compose.yaml / docker-compose.yml / Dockerfile)');
+ }
+
+ if (containerDefinitionFiles[dockerComposeFileNameYaml] && containerDefinitionFiles[dockerComposeFileNameYml]) {
+ throw new Error('There should be only one docker-compose.yaml / docker-compose.yml)');
+ }
+
+ const composeContents = containerDefinitionFiles[dockerComposeFileNameYaml] || containerDefinitionFiles[dockerComposeFileNameYml];
+ const { [dockerfileFileName]: dockerfileContents } = containerDefinitionFiles;
+
+ const { buildspec, containers, service, secrets } = getContainers(composeContents, dockerfileContents);
+
+ const containersPorts = containers.reduce(
+ (acc, container) => acc.concat(container.portMappings.map(({ containerPort }) => containerPort)),
+ [],
+ );
+
+ const newContainersName = Array.from(new Set(containers.map(({ name }) => name)));
+
+ let isInitialDeploy = Object.keys(output ?? {}).length === 0;
+ const currentContainersSet = new Set(output?.ContainerNames?.split(','));
+ // Service require all containers to exists
+ isInitialDeploy = isInitialDeploy || newContainersName.some(newContainer => !currentContainersSet.has(newContainer));
+
+ let exposedContainer: { name: string; port: number };
+
+ const containersExposed = containers.filter(container => container.portMappings.length > 0);
+
+ if (containersPorts.length === 0) {
+ throw new Error('Service requires at least one exposed port');
+ } else if (containersPorts.length > 1) {
+ exposedContainer = await checkContainerExposed(containersExposed, exposedContainerFromMeta, askForExposedContainer);
+ } else {
+ exposedContainer = {
+ name: containersExposed[0].name,
+ port: containersExposed[0].portMappings[0].containerPort,
+ };
+ }
+
+ fs.ensureDirSync(srcPath);
+ fs.writeFileSync(path.join(srcPath, 'buildspec.yml'), buildspec);
+
+ const secretsArns: Map = new Map();
+
+ if ((await shouldUpdateSecrets(context, secrets)) || isInitialDeploy) {
+ // Normalizes paths
+ // Validate secrets file paths, existence and prefixes
+ const errors = Object.entries(secrets).reduce((acc, [secretName, secretFilePath]) => {
+ const baseDir = path.isAbsolute(secretFilePath) ? '' : srcPath;
+ const normalizedFilePath = path.normalize(path.join(baseDir, secretFilePath));
+
+ secrets[secretName] = normalizedFilePath;
+
+ let canRead = true;
+
+ try {
+ const fd = fs.openSync(normalizedFilePath, 'r');
+
+ fs.closeSync(fd);
+ } catch (err) {
+ canRead = false;
+ }
+
+ if (!canRead) {
+ acc.push(`Secret file "${secretFilePath}" can't be read.`);
+ return acc;
+ }
+
+ const basename = path.basename(normalizedFilePath);
+ const hasCorrectPrefix = basename.startsWith('.secret-');
+
+ if (!hasCorrectPrefix) {
+ acc.push(`Secret file "${secretFilePath}" doesn't start with the ".secret-" prefix.`);
+ return acc;
+ }
+
+ const isInsideSrc = normalizedFilePath.startsWith(path.join(srcPath, path.sep));
+ if (isInsideSrc) {
+ acc.push(`Secret file "${secretFilePath}" should not be inside the "src" folder. The "src" folder will be uploaded to S3.`);
+ return acc;
+ }
+
+ return acc;
+ }, []);
+
+ if (errors.length > 0) {
+ throw new Error(['Error(s) in secret file(s):'].concat(errors).join('\n'));
+ }
+
+ for await (const entries of Object.entries(secrets)) {
+ const [secretName, secretFilePath] = entries;
+
+ const contents = fs.readFileSync(secretFilePath).toString();
+
+ const ssmSecretName = `${envName}-${resourceName}-${secretName}`;
+
+ const { ARN: secretArn } = await context.amplify.executeProviderUtils(context, 'awscloudformation', 'upsertSecretValue', {
+ secret: contents,
+ description: `Secret for ${resourceName}`,
+ name: ssmSecretName,
+ version: uuid(),
+ });
+
+ const [prefix,] = secretArn.toString().split(ssmSecretName);
+ const secretArnRef = cdk.Fn.join('', [
+ prefix,
+ cdk.Fn.ref('rootStackName'),
+ '-',
+ resourceName,
+ '-',
+ secretName,
+ ]);
+
+ secretsArns.set(secretName, secretArnRef);
+ }
+ } else {
+ const { cfnTemplate } = readCFNTemplate(path.join(pathManager.getBackendDirPath(), category, resourceName, cfnFileName(resourceName)));
+ setExistingSecretArns(secretsArns, cfnTemplate);
+ }
+
+ const desiredCount = service?.replicas ?? 1; // TODO: 1 should be from meta (HA setting)
+
+ return {
+ containersPorts,
+ containers,
+ isInitialDeploy,
+ desiredCount,
+ exposedContainer,
+ secretsArns,
+ };
+}
+
+async function shouldUpdateSecrets(context: $TSContext, secrets: Record): Promise {
+ const hasSecrets = Object.keys(secrets).length > 0;
+
+ if (!hasSecrets || context.exeInfo.inputParams.yes) {
+ return false;
+ }
+
+ const { update_secrets } = await inquirer.prompt({
+ name: 'update_secrets',
+ type: 'confirm',
+ message: 'Secret configuration detected. Do you wish to store new values in the cloud?',
+ default: false,
+ });
+
+ return update_secrets;
+}
+
+async function checkContainerExposed(
+ containersExposed: Container[],
+ exposedContainerFromMeta: { name: string; port: number } = { name: '', port: 0 },
+ askForExposedContainer: boolean = false,
+): Promise<{ name: string; port: number }> {
+ const containerExposed = containersExposed.find(container => container.name === exposedContainerFromMeta.name);
+
+ if (!askForExposedContainer && containerExposed?.portMappings.find(port => port.containerPort === exposedContainerFromMeta.port)) {
+ return { ...exposedContainerFromMeta };
+ } else {
+ const choices: { name: string; value: Container }[] = containersExposed.map(container => ({ name: container.name, value: container }));
+
+ const { containerToExpose } = await inquirer.prompt({
+ message: 'Select which container is the entrypoint',
+ name: 'containerToExpose',
+ type: 'list',
+ choices,
+ });
+
+ return {
+ name: containerToExpose.name,
+ port: containerToExpose.portMappings[0].containerPort,
+ };
+ }
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/containers/set-existing-secret-arns.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/containers/set-existing-secret-arns.ts
new file mode 100644
index 0000000000..cc15a1d1fc
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/containers/set-existing-secret-arns.ts
@@ -0,0 +1,55 @@
+import { $TSAny } from 'amplify-cli-core';
+import _ from 'lodash';
+import * as cdk from '@aws-cdk/core';
+
+/**
+ * Check if the template contains existing secret configuration and if so, add it to the secretsMap
+ * The secrets configuration is stored in the template in the following format
+ * {
+ * "Resources": {
+ "TaskDefinition": {
+ "Type": "AWS::ECS::TaskDefinition",
+ "Properties": {
+ "ContainerDefinitions": [
+ {
+ "Secrets": [
+ {
+ "Name": "SECRETNAME",
+ "ValueFrom": ""
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ */
+export const setExistingSecretArns = (secretsMap: Map, cfnObj: $TSAny) => {
+ if (_.isEmpty(cfnObj)) {
+ return;
+ }
+ const taskDef = Object.values(cfnObj?.Resources) // get all the resources
+ .find((value: $TSAny) => value?.Type === 'AWS::ECS::TaskDefinition') as $TSAny; // find the task definition
+ const containerDefs = taskDef?.Properties?.ContainerDefinitions as $TSAny[]; // pull out just the container definitions
+ if (!Array.isArray(containerDefs)) {
+ return;
+ }
+ containerDefs
+ .map(def => def?.Secrets) // get the secrets array
+ .filter(secrets => !_.isEmpty(secrets)) // filter out defs that don't contain secrets
+ .flat(1) // merge nested secrets array into one array
+ .filter(secretDef => !!secretDef?.Name) // make sure the name is defined
+ .filter(secretDef => !!secretDef.ValueFrom) // make sure the arn is defined
+ .forEach(secretDef => {
+ if (typeof secretDef.ValueFrom === 'object' && secretDef.ValueFrom['Fn::Join']) {
+ const [delimiter, values] = secretDef.ValueFrom['Fn::Join'];
+ secretsMap.set(
+ secretDef.Name,
+ cdk.Fn.join(delimiter, values.map(val => val.Ref ? cdk.Fn.ref(val.Ref) : val)),
+ );
+ }
+ else {
+ secretsMap.set(secretDef.Name, secretDef.ValueFrom);
+ }
+ }); // add it to the secretsMap map
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/dynamic-imports.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/dynamic-imports.ts
new file mode 100644
index 0000000000..2c7adced53
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/dynamic-imports.ts
@@ -0,0 +1,10 @@
+import * as path from 'path';
+
+export const serviceMetadataFor = async (service: string) =>
+ (await import(path.join('..', '..', 'supported-services'))).supportedServices[service];
+
+export const datasourceMetadataFor = async (datasource: string) =>
+ (await import(path.join('..', '..', 'supported-datasources'))).supportedDatasources[datasource];
+
+export const getServiceWalkthrough = async (walkthroughFilename: string) =>
+ (await import(path.join('..', 'service-walkthroughs', walkthroughFilename))).serviceWalkthrough;
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/edit-schema-flow.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/edit-schema-flow.ts
new file mode 100644
index 0000000000..3d339aecd0
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/edit-schema-flow.ts
@@ -0,0 +1,14 @@
+import { $TSContext, pathManager } from 'amplify-cli-core';
+import { prompter } from 'amplify-prompts';
+import * as path from 'path';
+import { category } from '../../../category-constants';
+import { gqlSchemaFilename } from '../aws-constants';
+
+export const editSchemaFlow = async (context: $TSContext, apiName: string) => {
+ if (!(await prompter.yesOrNo('Do you want to edit the schema now?', true))) {
+ return;
+ }
+
+ const schemaPath = path.join(pathManager.getResourceDirectoryPath(undefined, category, apiName), gqlSchemaFilename);
+ await context.amplify.openEditor(context, schemaPath, false);
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/get-appsync-auth-config.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/get-appsync-auth-config.ts
new file mode 100644
index 0000000000..93e289854f
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/get-appsync-auth-config.ts
@@ -0,0 +1,19 @@
+import { $TSContext } from 'amplify-cli-core';
+import { AppsyncApiInputState } from '../api-input-manager/appsync-api-input-state';
+import { appSyncAuthTypeToAuthConfig } from './auth-config-to-app-sync-auth-type-bi-di-mapper';
+
+/**
+ *
+ * @param resourceName
+ * @returns authConfig
+ */
+export const getAuthConfig = async (context: $TSContext, resourceName: string) => {
+ const cliState = new AppsyncApiInputState(context, resourceName);
+ if (cliState.cliInputFileExists()) {
+ const appsyncInputs = cliState.getCLIInputPayload().serviceConfiguration;
+ return {
+ defaultAuthentication: appSyncAuthTypeToAuthConfig(appsyncInputs.defaultAuthType),
+ additionalAuthenticationProviders: (appsyncInputs.additionalAuthTypes || []).map(appSyncAuthTypeToAuthConfig),
+ };
+ }
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/get-appsync-resolver-config.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/get-appsync-resolver-config.ts
new file mode 100644
index 0000000000..a8b442dcce
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/get-appsync-resolver-config.ts
@@ -0,0 +1,16 @@
+import { $TSContext } from 'amplify-cli-core';
+import { AppsyncApiInputState } from '../api-input-manager/appsync-api-input-state';
+import { conflictResolutionToResolverConfig } from './resolver-config-to-conflict-resolution-bi-di-mapper';
+
+/**
+ *
+ * @param resourceName
+ * @returns resolverConfig
+ */
+export const getResolverConfig = async (context: $TSContext, resourceName: string) => {
+ const cliState = new AppsyncApiInputState(context, resourceName);
+ if (cliState.cliInputFileExists()) {
+ const appsyncInputs = cliState.getCLIInputPayload().serviceConfiguration;
+ return conflictResolutionToResolverConfig(appsyncInputs.conflictResolution);
+ }
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/getAppSyncApiName.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/getAppSyncApiName.ts
new file mode 100644
index 0000000000..2e28ce65d4
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/getAppSyncApiName.ts
@@ -0,0 +1,15 @@
+import { $TSContext, AmplifySupportedService } from 'amplify-cli-core';
+
+export const getAppSyncApiResourceName = async (context: $TSContext): Promise => {
+ const { allResources } = await context.amplify.getResourceStatus();
+ const apiResource = allResources.filter((resource: { service: string }) => resource.service === AmplifySupportedService.APPSYNC);
+ let apiResourceName;
+
+ if (apiResource.length > 0) {
+ const resource = apiResource[0];
+ apiResourceName = resource.resourceName;
+ } else {
+ throw new Error(`${AmplifySupportedService.APPSYNC} API does not exist. To add an api, use "amplify update api".`);
+ }
+ return apiResourceName;
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/github.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/github.ts
new file mode 100644
index 0000000000..6d73ee22a3
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/github.ts
@@ -0,0 +1,14 @@
+export function getGitHubOwnerRepoFromPath(path: string) {
+ if (!path.startsWith('https://github.com/')) {
+ throw Error(`Invalid Repo Path ${path}`);
+ }
+
+ const [, , , owner, repo, , branch, ...pathParts] = path.split('/');
+
+ return {
+ owner,
+ repo,
+ branch,
+ path: pathParts.join('/'),
+ };
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/global-sandbox-mode.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/global-sandbox-mode.ts
new file mode 100644
index 0000000000..b9cd1c9884
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/global-sandbox-mode.ts
@@ -0,0 +1,6 @@
+export function defineGlobalSandboxMode(link: string): string {
+ return `# This "input" configures a global authorization rule to enable public access to
+# all models in this schema. Learn more about authorization rules here: ${link}
+input AMPLIFY { globalAuthRule: AuthRule = { allow: public } } # FOR TESTING ONLY!\n
+`;
+}
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/migrate-api-override-resource.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/migrate-api-override-resource.ts
new file mode 100644
index 0000000000..d1f2c8331a
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/migrate-api-override-resource.ts
@@ -0,0 +1,120 @@
+import {
+ $TSAny,
+ AmplifyCategories,
+ JSONUtilities,
+ NotInitializedError,
+ pathManager,
+ ResourceDoesNotExistError,
+ stateManager,
+} from 'amplify-cli-core';
+import { printer } from 'amplify-prompts';
+import * as fs from 'fs-extra';
+import { ResolverConfig } from 'graphql-transformer-core';
+import _ from 'lodash';
+import * as path from 'path';
+import { v4 as uuid } from 'uuid';
+import { gqlSchemaFilename } from '../aws-constants';
+import { AppSyncCLIInputs } from '../service-walkthrough-types/appsync-user-input-types';
+import { authConfigToAppSyncAuthType } from './auth-config-to-app-sync-auth-type-bi-di-mapper';
+import { resolverConfigToConflictResolution } from './resolver-config-to-conflict-resolution-bi-di-mapper';
+
+type ApiMetaData = {
+ resourceName: string;
+ authConfig: $TSAny;
+ resolverConfig: ResolverConfig;
+};
+export const migrateResourceToSupportOverride = async (resourceName: string) => {
+ printer.debug('Starting Migration Process');
+ /**
+ * backup resource folder
+ * get parameters.json
+ * generate and save cliInputs
+ * return cliInputs
+ * */
+ const projectPath = pathManager.findProjectRoot();
+ if (!projectPath) {
+ // New project, hence not able to find the amplify dir
+ throw new NotInitializedError();
+ }
+ const apiresourceDirPath = pathManager.getResourceDirectoryPath(undefined, AmplifyCategories.API, resourceName);
+ const backupApiResourceFolder = backup(apiresourceDirPath, projectPath, resourceName);
+
+ try {
+ let resolverConfig = {};
+ const transformConfig =
+ JSONUtilities.readJson(path.join(apiresourceDirPath, 'transform.conf.json'), { throwIfNotExist: false }) ?? {};
+ if (!_.isEmpty(transformConfig) && !_.isEmpty(transformConfig!.ResolverConfig)) {
+ resolverConfig = transformConfig!.ResolverConfig;
+ }
+ const authConfig = stateManager.getMeta()[AmplifyCategories.API][resourceName].output.authConfig;
+ if (_.isEmpty(authConfig)) {
+ throw new ResourceDoesNotExistError(
+ `auth configuration not present for ${resourceName}. Try amplify pull to sync your folder structure`,
+ );
+ }
+ const parameters: ApiMetaData = {
+ authConfig,
+ resolverConfig,
+ resourceName,
+ };
+ // convert parameters.json to cli-inputs.json
+ const cliInputs = generateCliInputs(parameters, apiresourceDirPath);
+ const cliInputsPath = path.join(apiresourceDirPath, 'cli-inputs.json');
+ JSONUtilities.writeJson(cliInputsPath, cliInputs);
+ printer.debug('Migration is Successful');
+ } catch (e) {
+ printer.error('There was an error migrating your project.');
+ rollback(apiresourceDirPath, backupApiResourceFolder);
+ printer.debug('migration operations are rolled back.');
+ throw e;
+ } finally {
+ cleanUp(backupApiResourceFolder);
+ }
+};
+
+function backup(authresourcePath: string, projectPath: string, resourceName: string) {
+ if (fs.existsSync(authresourcePath)) {
+ const backupauthResourceDirName = `${resourceName}-BACKUP-${uuid().split('-')[0]}`;
+ const backupauthResourceDirPath = path.join(projectPath, backupauthResourceDirName);
+
+ if (fs.existsSync(backupauthResourceDirPath)) {
+ const error = new Error(`Backup folder at ${backupauthResourceDirPath} already exists, remove the folder and retry the operation.`);
+
+ error.name = 'BackupFolderAlreadyExist';
+ error.stack = undefined;
+
+ throw error;
+ }
+
+ fs.copySync(authresourcePath, backupauthResourceDirPath);
+ return backupauthResourceDirPath;
+ }
+}
+
+function rollback(authresourcePath: string, backupauthResourceDirPath: string) {
+ if (fs.existsSync(authresourcePath) && fs.existsSync(backupauthResourceDirPath)) {
+ fs.removeSync(authresourcePath);
+ fs.moveSync(backupauthResourceDirPath, authresourcePath);
+ }
+}
+
+function cleanUp(authresourcePath: string | undefined) {
+ if (!!authresourcePath && fs.existsSync(authresourcePath)) fs.removeSync(authresourcePath);
+}
+
+const generateCliInputs = (parameters: ApiMetaData, apiResourceDir: string): AppSyncCLIInputs => {
+ return {
+ version: 1,
+ serviceConfiguration: {
+ serviceName: 'AppSync',
+ defaultAuthType: authConfigToAppSyncAuthType(parameters.authConfig ? parameters.authConfig.defaultAuthentication : undefined),
+ additionalAuthTypes:
+ !_.isEmpty(parameters.authConfig) && !_.isEmpty(parameters.authConfig.additionalAuthenticationProviders)
+ ? parameters.authConfig.additionalAuthenticationProviders.map(authConfigToAppSyncAuthType)
+ : undefined,
+ conflictResolution: resolverConfigToConflictResolution(parameters.resolverConfig),
+ apiName: parameters.resourceName,
+ gqlSchemaPath: path.join(apiResourceDir, gqlSchemaFilename),
+ },
+ };
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/print-api-key-warnings.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/print-api-key-warnings.ts
new file mode 100644
index 0000000000..eb8a6d423e
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/print-api-key-warnings.ts
@@ -0,0 +1,19 @@
+import { printer } from 'amplify-prompts';
+
+// If adding or removing the API_KEY auth type, print a warning that resources that depend on the API must re-add the API as a dependency to have the API key parameter added / removed.
+export const printApiKeyWarnings = (oldConfigHadApiKey: boolean, newConfigHasApiKey: boolean) => {
+ if (oldConfigHadApiKey && !newConfigHasApiKey) {
+ printer.warn('The API_KEY auth type has been removed from the API.');
+ printer.warn(
+ 'If other resources depend on this API, run "amplify update " and reselect this API to remove the dependency on the API key.',
+ );
+ printer.warn('This must be done before running "amplify push" to prevent a push failure');
+ }
+
+ if (!oldConfigHadApiKey && newConfigHasApiKey) {
+ printer.warn('The API_KEY auth type has been added to the API.');
+ printer.warn(
+ 'If other resources depend on this API and need access to the API key, run "amplify update " and reselect this API as a dependency to add the API key dependency.',
+ );
+ }
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper.ts
new file mode 100644
index 0000000000..c3e9365edf
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/resolver-config-to-conflict-resolution-bi-di-mapper.ts
@@ -0,0 +1,106 @@
+import { ConflictResolution, PerModelResolutionstrategy, ResolutionStrategy, LambdaResolutionStrategy } from 'amplify-headless-interface';
+import { ResolverConfig, SyncConfig, ConflictHandlerType, SyncConfigLAMBDA } from 'graphql-transformer-core';
+import _ from 'lodash';
+
+export const conflictResolutionToResolverConfig = (conflictResolution: ConflictResolution = {}): ResolverConfig => {
+ const result: ResolverConfig = {};
+ if (_.isEmpty(conflictResolution)) return undefined;
+ if (conflictResolution.defaultResolutionStrategy) {
+ result.project = resolutionStrategyToSyncConfig(conflictResolution.defaultResolutionStrategy);
+ }
+ if (conflictResolution.perModelResolutionStrategy) {
+ result.models = modelSyncConfigTransformer(conflictResolution.perModelResolutionStrategy);
+ }
+ return result;
+};
+
+export const resolverConfigToConflictResolution = (resolverConfig: ResolverConfig = {}): ConflictResolution => {
+ const result: ConflictResolution = {};
+ if (resolverConfig.project) {
+ result.defaultResolutionStrategy = syncConfigToResolutionStrategy(resolverConfig.project);
+ }
+ if (resolverConfig.models) {
+ result.perModelResolutionStrategy = modelResolutionStrategyTransformer(resolverConfig.models);
+ }
+ return result;
+};
+
+const modelSyncConfigTransformer = (perModelResolutionStrategy: PerModelResolutionstrategy[]): { [key: string]: SyncConfig } => {
+ const result: { [key: string]: SyncConfig } = {};
+ perModelResolutionStrategy.forEach(
+ strategy => (result[strategy.entityName] = resolutionStrategyToSyncConfig(strategy.resolutionStrategy)),
+ );
+ return result;
+};
+
+const modelResolutionStrategyTransformer = (modelSyncConfig: { [key: string]: SyncConfig }): PerModelResolutionstrategy[] => {
+ const result: PerModelResolutionstrategy[] = [];
+ Object.entries(modelSyncConfig)
+ .map(
+ ([key, value]): PerModelResolutionstrategy => ({
+ resolutionStrategy: syncConfigToResolutionStrategy(value),
+ entityName: key,
+ }),
+ )
+ .forEach(modelStrategy => result.push(modelStrategy));
+ return result;
+};
+
+const resolutionStrategyToSyncConfig = (resolutionStrategy: ResolutionStrategy, newFunctionMap?: Record): SyncConfig => {
+ const defaultMapper = () => undefined;
+ return _.get(resolutionStrategyToSyncConfigMap, resolutionStrategy.type, defaultMapper)(resolutionStrategy);
+};
+
+const resolutionStrategyToSyncConfigMap: Record SyncConfig> = {
+ AUTOMERGE: () => ({
+ ConflictHandler: ConflictHandlerType.AUTOMERGE,
+ ConflictDetection: 'VERSION',
+ }),
+ OPTIMISTIC_CONCURRENCY: () => ({
+ ConflictHandler: ConflictHandlerType.OPTIMISTIC,
+ ConflictDetection: 'VERSION',
+ }),
+ LAMBDA: (resolutionStrategy: LambdaResolutionStrategy) => {
+ switch (resolutionStrategy.resolver.type) {
+ case 'EXISTING':
+ const { name, region, arn } = resolutionStrategy.resolver;
+ return {
+ ConflictHandler: ConflictHandlerType.LAMBDA,
+ ConflictDetection: 'VERSION',
+ LambdaConflictHandler: { name, region, lambdaArn: arn },
+ };
+ case 'NEW':
+ throw new Error(
+ 'Tried to convert LambdaResolutionStrategy "NEW" to SyncConfig. New resources must be generated prior to this conversion and then replaced with a LambdaResolutionStrategy of type "EXISTING"',
+ );
+ }
+ },
+};
+
+const syncConfigToResolutionStrategy = (syncConfig: SyncConfig): ResolutionStrategy => {
+ const defaultMapper = (): ResolutionStrategy => ({ type: 'NONE' });
+ return _.get(syncConfigToResolutionStrategyMap, syncConfig.ConflictHandler, defaultMapper)(syncConfig);
+};
+
+const syncConfigToResolutionStrategyMap: Record ResolutionStrategy> = {
+ AUTOMERGE: () => ({
+ type: 'AUTOMERGE',
+ }),
+ OPTIMISTIC_CONCURRENCY: () => ({
+ type: 'OPTIMISTIC_CONCURRENCY',
+ }),
+ LAMBDA: (syncConfig: SyncConfigLAMBDA) => ({
+ type: 'LAMBDA',
+ resolver: (syncConfig.LambdaConflictHandler as any).new
+ ? {
+ // this is a hack to pass the "new" flag into the ResolutionStrategy
+ type: 'NEW',
+ }
+ : {
+ type: 'EXISTING',
+ name: syncConfig.LambdaConflictHandler.name,
+ region: syncConfig.LambdaConflictHandler.region,
+ arn: syncConfig.LambdaConflictHandler.lambdaArn,
+ },
+ }),
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rest-api-path-utils.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rest-api-path-utils.ts
new file mode 100644
index 0000000000..2893bfe9a9
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/rest-api-path-utils.ts
@@ -0,0 +1,89 @@
+// validatePathName checks that the provided path name is of a valid path structure.
+// Examples of valid path structures: /book, /book/{isbn}, /book/{isbn}/page/{pageNum}
+export const validatePathName = (name: string) => {
+ // Allow the path /
+ if (name === '/') {
+ return true;
+ }
+
+ if (name.length === 0) {
+ return 'The path must not be empty';
+ }
+
+ if (name.charAt(name.length - 1) === '/') {
+ return 'The path must not end with /';
+ }
+
+ if (name.charAt(0) !== '/') {
+ return 'The path must begin with / e.g. /items';
+ }
+
+ // Matches parameterized paths such as /book/{isbn}/page/{pageNum}
+ // This regex also catches the above conditions, but those are left in to provide clearer error messages.
+ if (!/^(?:\/(?:[a-zA-Z0-9\-]+|{[a-zA-Z0-9\-]+}))+$/.test(name)) {
+ return 'Each path part must use characters a-z A-Z 0-9 - and must not be empty.\nOptionally, a path part can be surrounded by { } to denote a path parameter.';
+ }
+
+ return true;
+};
+
+// checkForPathOverlap checks to see if the provided path name eclipses or overlaps any other paths in the provided list of paths.
+//
+// checkForPathOverlap returns false if the provided path name does not overlap with any of the other provided paths.
+// checkForPathOverlap returns an object with the following structure if the provided path name does overlap with any of the provided paths:
+// {
+// higherOrderPath: string,
+// lowerOrderPath: string,
+// }
+//
+// checkForPathOverlap assumes that all provided paths have previously been run through validatePathName().
+export const checkForPathOverlap = (name: string, paths: string[]) => {
+ // Split name into an array of its components.
+ const split = name.split('/').filter(sub => sub !== ''); // Because name starts with a /, this filters out the first empty element
+
+ // Sort paths so that the prefix paths of name are checked with shorter paths first.
+ paths.sort();
+
+ // Check if any prefix of this path matches an existing path.
+ //
+ // Convert parameters to: '{}'. When evaluating whether paths overlap, we're only concerned about the placement of parameters in those
+ // paths --- not what the parameters are named.
+ //
+ // Ex: paths /book/{isbn} and /book/{publication-year} overlap. We aren't concerned with the fact that the parameters in those two routes
+ // are named "isbn" and "publication-year"; we're concerned about the fact that the subpaths after /book in both paths are parameters.
+ let subpath = '';
+ let overlappingPath = '';
+ const subMatch = split.some(sub => {
+ // If the current subpath is a parameter, convert it to: '{}'.
+ sub = sub.replace(/{[a-zA-Z0-9\-]+}/g, '{}');
+ subpath = `${subpath}/${sub}`;
+ // Explicitly check for the path / since it overlaps with any other valid path.
+ // If the path isn't /, replace all of its parameters with '{}' when checking for overlap in find().
+ overlappingPath = paths.find(name => name === '/' || name.replace(/{[a-zA-Z0-9\-]+}/g, '{}') === subpath);
+ return overlappingPath !== undefined;
+ });
+ if (subMatch) {
+ // To determine which of the overlapping paths is the higher order path, count the number of occurrences of '/' in both paths.
+ const nameSlashCount = name.split('/').length - 1;
+ const overlappingPathSlashCount = overlappingPath.split('/').length - 1;
+ if (nameSlashCount < overlappingPathSlashCount) {
+ return {
+ higherOrderPath: name,
+ lowerOrderPath: overlappingPath,
+ };
+ }
+ return {
+ higherOrderPath: overlappingPath,
+ lowerOrderPath: name,
+ };
+ }
+
+ // This path doesn't overlap with any of the other provided paths.
+ return false;
+};
+
+// Convert a CloudFormation parameterized path to an ExpressJS parameterized path
+// /library/{libraryId}/book/{isbn} => /library/:libraryId/book/:isbn
+export const formatCFNPathParamsForExpressJs = (path: string) => {
+ return path.replace(/{([a-zA-Z0-9\-]+)}/g, ':$1');
+};
diff --git a/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/service-walkthrough-result-to-add-api-request.ts b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/service-walkthrough-result-to-add-api-request.ts
new file mode 100644
index 0000000000..5a8fb460a3
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/awscloudformation/utils/service-walkthrough-result-to-add-api-request.ts
@@ -0,0 +1,18 @@
+import { AddApiRequest } from 'amplify-headless-interface';
+import _ from 'lodash';
+import { resolverConfigToConflictResolution } from './resolver-config-to-conflict-resolution-bi-di-mapper';
+import { authConfigToAppSyncAuthType } from './auth-config-to-app-sync-auth-type-bi-di-mapper';
+
+// Temporary conversion function between the existing output of the appSync service walkthrough and the new AddApiRequest interface
+// Long-term, the service walkthrough should be refactored to directly return an object conforming to the interface
+export const serviceWalkthroughResultToAddApiRequest = (result): AddApiRequest => ({
+ version: 1,
+ serviceConfiguration: {
+ serviceName: 'AppSync',
+ apiName: result.answers.apiName,
+ transformSchema: result.schemaContent,
+ defaultAuthType: authConfigToAppSyncAuthType(result.output.authConfig.defaultAuthentication),
+ additionalAuthTypes: (result.output.authConfig.additionalAuthenticationProviders || []).map(authConfigToAppSyncAuthType),
+ conflictResolution: resolverConfigToConflictResolution(result.resolverConfig),
+ },
+});
diff --git a/packages/amplify-category-api/src/provider-utils/supported-datasources.ts b/packages/amplify-category-api/src/provider-utils/supported-datasources.ts
new file mode 100644
index 0000000000..08fb905ac6
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/supported-datasources.ts
@@ -0,0 +1,53 @@
+export const supportedDataSources = {
+ 'Aurora Serverless': {
+ inputs: [
+ {
+ key: 'region',
+ type: 'list',
+ question: 'Provide the region in which your cluster is located:',
+ required: true,
+ },
+ {
+ key: 'rdsClusterIdentifier',
+ type: 'list',
+ question: 'Select the Aurora Serverless cluster that will be used as the data source for your API:',
+ required: true,
+ },
+ {
+ key: 'rdsSecretStoreArn',
+ type: 'list',
+ question: 'Select the secret used to access your Aurora Serverless cluster:',
+ required: true,
+ },
+ {
+ key: 'databaseName',
+ type: 'list',
+ question: 'Select the database to use as the datasource:',
+ required: true,
+ },
+ ],
+ alias: 'Aurora Serverless',
+ serviceWalkthroughFilename: 'appSync-rds-walkthrough.js',
+ cfnFilename: 'appSync-rds-cloudformation-template-default.yml.ejs',
+ provider: 'awscloudformation',
+ availableRegions: [
+ 'us-east-1',
+ 'us-east-2',
+ 'us-west-1',
+ 'us-west-2',
+ 'ap-south-1',
+ 'ap-southeast-1',
+ 'ap-southeast-2',
+ 'ap-northeast-1',
+ 'ap-northeast-2',
+ 'ca-central-1',
+ 'eu-central-1',
+ 'eu-north-1',
+ 'eu-west-1',
+ 'eu-west-2',
+ 'eu-west-3',
+ 'me-south-1',
+ 'sa-east-1',
+ ],
+ },
+};
diff --git a/packages/amplify-category-api/src/provider-utils/supported-services.ts b/packages/amplify-category-api/src/provider-utils/supported-services.ts
new file mode 100644
index 0000000000..21de2e9c38
--- /dev/null
+++ b/packages/amplify-category-api/src/provider-utils/supported-services.ts
@@ -0,0 +1,153 @@
+export const supportedServices = {
+ AppSync: {
+ inputs: [
+ {
+ key: 'resourceName',
+ type: 'input',
+ question: 'Provide a friendly name for your resource to be used as label for this category in the project:',
+ validation: {
+ operator: 'regex',
+ value: '^[a-zA-Z0-9]+$',
+ onErrorMsg: 'Resource name should be alphanumeric',
+ },
+ required: true,
+ },
+ {
+ key: 'apiName',
+ type: 'input',
+ question: 'Provide API name:',
+ validation: {
+ operator: 'regex',
+ value: '^[a-zA-Z0-9]{1,80}$',
+ onErrorMsg: 'You can use the following characters: a-z A-Z 0-9 with maximum length of 80',
+ },
+ required: true,
+ },
+ {
+ key: 'apiCreationChoice',
+ type: 'confirm',
+ question: 'Do you have an annotated GraphQL schema?',
+ required: true,
+ },
+ {
+ key: 'schemaFilePath',
+ type: 'input',
+ question: 'Provide your schema file path:',
+ required: true,
+ },
+ {
+ key: 'templateSelection',
+ type: 'list',
+ question: 'Choose a schema template:',
+ options: [
+ {
+ name: 'Single object with fields (e.g., “Todo” with ID, name, description)',
+ value: 'single-object-schema.graphql',
+ },
+ {
+ name: 'One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)',
+ value: 'many-relationship-schema.graphql',
+ },
+ {
+ name: 'Objects with fine-grained access control (e.g., a project management app with owner-based authorization)',
+ value: 'single-object-auth-schema.graphql',
+ },
+ {
+ name: 'Blank Schema',
+ value: 'blank-schema.graphql',
+ },
+ ],
+ required: true,
+ },
+ {
+ key: 'editSchemaChoice',
+ type: 'confirm',
+ question: 'Do you want to edit the schema now?',
+ required: true,
+ },
+ {
+ key: 'editorSelection',
+ type: 'list',
+ question: 'Choose the editor you want to open the schema in:',
+ options: [
+ {
+ name: 'Sublime Text',
+ value: 'sublime',
+ },
+ {
+ name: 'Atom Editor',
+ value: 'atom',
+ },
+ {
+ name: 'Visual Studio Code',
+ value: 'code',
+ },
+ {
+ name: 'IDEA 14 CE',
+ value: 'idea14ce',
+ },
+ {
+ name: 'Vim (via Terminal, macOS only)',
+ value: 'vim',
+ },
+ {
+ name: 'Emacs (via Terminal, macOS only)',
+ value: 'emacs',
+ },
+ {
+ name: 'None - Use my env variables to open my default editor',
+ value: 'none',
+ },
+ ],
+ required: true,
+ },
+ {
+ key: 'dynamoDbType',
+ type: 'list',
+ question: 'Choose a DynamoDB data source option',
+ options: [
+ {
+ name: 'Use the DynamoDB table configured in the current Amplify project',
+ value: 'currentProject',
+ },
+ {
+ name: 'Create a new DynamoDB table',
+ value: 'newResource',
+ },
+ {
+ name: 'Use a DynamoDB table already deployed on AWS',
+ value: 'cloudResource',
+ },
+ ],
+ },
+ ],
+ alias: 'GraphQL',
+ serviceWalkthroughFilename: 'appSync-walkthrough.js',
+ cfnFilename: 'appSync-cloudformation-template-default.yml.ejs',
+ provider: 'awscloudformation',
+ },
+ 'API Gateway': {
+ inputs: [
+ {
+ key: 'apiName',
+ question: 'Provide a friendly name for your API:',
+ required: true,
+ },
+ {
+ key: 'pathName',
+ question: 'HTTP path name?',
+ type: 'input',
+ required: 'true',
+ },
+ {
+ key: 'lambdaFunction',
+ question: 'Select the Lambda function',
+ required: true,
+ type: 'input',
+ },
+ ],
+ alias: 'REST',
+ serviceWalkthroughFilename: 'apigw-walkthrough.js',
+ provider: 'awscloudformation',
+ },
+};
diff --git a/packages/amplify-category-api/tsconfig.json b/packages/amplify-category-api/tsconfig.json
new file mode 100644
index 0000000000..4689ad577b
--- /dev/null
+++ b/packages/amplify-category-api/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "lib",
+ "rootDir": "src",
+ "strict": false,
+ "allowJs": false
+ },
+ "exclude": [
+ "coverage",
+ "lib",
+ "scripts",
+ "resources/awscloudformation/lambdas",
+ "resources/awscloudformation/container-templates",
+ "resources/awscloudformation/graphql-lambda-authorizer",
+ "resources/awscloudformation/overrides-resource",
+ "scripts",
+ "src/__tests__"
+ ]
+}
diff --git a/packages/amplify-e2e-core/src/init/initProjectHelper.ts b/packages/amplify-e2e-core/src/init/initProjectHelper.ts
index 16052f9252..c60a5b77ac 100644
--- a/packages/amplify-e2e-core/src/init/initProjectHelper.ts
+++ b/packages/amplify-e2e-core/src/init/initProjectHelper.ts
@@ -80,13 +80,17 @@ export function initJSProjectWithProfile(cwd: string, settings?: Partial {
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
+ chain
+ .wait('Help improve Amplify CLI by sharing non sensitive configurations on failures')
+ .sendYes()
+ .wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/)
+ .run((err: Error) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
});
}
@@ -120,6 +124,8 @@ export function initAndroidProjectWithProfile(cwd: string, settings: Object): Pr
.sendCarriageReturn()
.wait('Please choose the profile you want to use')
.sendLine(s.profileName)
+ .wait('Help improve Amplify CLI by sharing non sensitive configurations on failures')
+ .sendYes()
.wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/)
.run((err: Error) => {
if (!err) {
@@ -167,6 +173,8 @@ export function initIosProjectWithProfile(cwd: string, settings: Object): Promis
.sendCarriageReturn()
.wait('Please choose the profile you want to use')
.sendLine(s.profileName)
+ .wait('Help improve Amplify CLI by sharing non sensitive configurations on failures')
+ .sendYes()
.wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/)
.run((err: Error) => {
if (!err) {
@@ -186,7 +194,7 @@ export function initFlutterProjectWithProfile(cwd: string, settings: Object): Pr
addCircleCITags(cwd);
return new Promise((resolve, reject) => {
- let chain = spawn(getCLIPath(), ['init'], { cwd, stripColors: true })
+ const chain = spawn(getCLIPath(), ['init'], { cwd, stripColors: true })
.wait('Enter a name for the project')
.sendLine(s.name)
.wait('Initialize the project with the above configuration?')
@@ -207,14 +215,16 @@ export function initFlutterProjectWithProfile(cwd: string, settings: Object): Pr
.sendLine(s.profileName);
singleSelect(chain, s.region, amplifyRegions);
-
- chain.wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
- if (!err) {
- resolve();
- } else {
- reject(err);
- }
- });
+ chain
+ .wait('Help improve Amplify CLI by sharing non sensitive configurations on failures')
+ .sendYes()
+ .wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
+ if (!err) {
+ resolve();
+ } else {
+ reject(err);
+ }
+ });
});
}
@@ -227,7 +237,7 @@ export function initProjectWithAccessKey(
addCircleCITags(cwd);
return new Promise((resolve, reject) => {
- let chain = spawn(getCLIPath(), ['init'], {
+ const chain = spawn(getCLIPath(), ['init'], {
cwd,
stripColors: true,
env: {
@@ -268,13 +278,16 @@ export function initProjectWithAccessKey(
singleSelect(chain, s.region, amplifyRegions);
- chain.wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
- if (!err) {
- resolve();
- } else {
- reject(err);
- }
- });
+ chain
+ .wait('Help improve Amplify CLI by sharing non sensitive configurations on failures')
+ .sendYes()
+ .wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
+ if (!err) {
+ resolve();
+ } else {
+ reject(err);
+ }
+ });
});
}
@@ -282,7 +295,7 @@ export function initNewEnvWithAccessKey(cwd: string, s: { envName: string; acces
addCircleCITags(cwd);
return new Promise((resolve, reject) => {
- let chain = spawn(getCLIPath(), ['init'], {
+ const chain = spawn(getCLIPath(), ['init'], {
cwd,
stripColors: true,
env: {
diff --git a/packages/amplify-e2e-core/src/utils/pinpoint.ts b/packages/amplify-e2e-core/src/utils/pinpoint.ts
index b8aed98395..206d570a15 100644
--- a/packages/amplify-e2e-core/src/utils/pinpoint.ts
+++ b/packages/amplify-e2e-core/src/utils/pinpoint.ts
@@ -119,13 +119,16 @@ export function initProjectForPinpoint(cwd: string): Promise {
singleSelect(chain, settings.region, amplifyRegions);
- chain.wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
- if (!err) {
- resolve();
- } else {
- reject(err);
- }
- });
+ chain
+ .wait('Help improve Amplify CLI by sharing non sensitive configurations on failures')
+ .sendYes()
+ .wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
+ if (!err) {
+ resolve();
+ } else {
+ reject(err);
+ }
+ });
});
}
diff --git a/packages/amplify-e2e-tests/src/init-special-cases/index.ts b/packages/amplify-e2e-tests/src/init-special-cases/index.ts
index 2ff8f5889f..0c794e11d4 100644
--- a/packages/amplify-e2e-tests/src/init-special-cases/index.ts
+++ b/packages/amplify-e2e-tests/src/init-special-cases/index.ts
@@ -87,12 +87,15 @@ async function initWorkflow(cwd: string, settings: { accessKeyId: string; secret
singleSelect(chain, settings.region, amplifyRegions);
- chain.wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
- if (!err) {
- resolve();
- } else {
- reject(err);
- }
- });
+ chain
+ .wait('Help improve Amplify CLI by sharing non sensitive configurations on failures')
+ .sendYes()
+ .wait(/Try "amplify add api" to create a backend API and then "amplify (push|publish)" to deploy everything/).run((err: Error) => {
+ if (!err) {
+ resolve();
+ } else {
+ reject(err);
+ }
+ });
});
}
diff --git a/yarn.lock b/yarn.lock
index 52ab3b306e..98e4c57db5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -28,13 +28,13 @@
iterall "^1.1.3"
uuid "^3.1.0"
-"@aws-amplify/amplify-category-custom@2.3.31":
- version "2.3.31"
- resolved "https://registry.npmjs.org/@aws-amplify/amplify-category-custom/-/amplify-category-custom-2.3.31.tgz#fa12965c22c02e330dd40517359db29c12fb8b14"
- integrity sha512-jf+qa0+QzD/6/5B/hO12R/DZsxdxX0V3bry8ejXhpDvgZTJ/9y4GnIcaXYlEAfpuK27SlKWBWwhBmYzuSVQtyQ==
+"@aws-amplify/amplify-category-custom@2.3.33":
+ version "2.3.33"
+ resolved "https://registry.npmjs.org/@aws-amplify/amplify-category-custom/-/amplify-category-custom-2.3.33.tgz#ab86f9016d5fed1dc5e83232c95b8007ad7048c4"
+ integrity sha512-NjkKnrbulIcV72Bg0VpRshKoU9i0x96+LLNtipj+62IyjkqfVLNMb0VLxo8nDM/27INc84RsxhP2ALpc8SAqEw==
dependencies:
- amplify-cli-core "2.8.0"
- amplify-prompts "2.1.0"
+ amplify-cli-core "2.9.0"
+ amplify-prompts "2.2.0"
execa "^5.1.1"
fs-extra "^8.1.0"
glob "^7.2.0"
@@ -122,12 +122,12 @@
dependencies:
"@aws-amplify/core" "4.5.6"
-"@aws-amplify/cli-extensibility-helper@2.3.27":
- version "2.3.27"
- resolved "https://registry.npmjs.org/@aws-amplify/cli-extensibility-helper/-/cli-extensibility-helper-2.3.27.tgz#6a66d5658826bdc666ccd37a89e9384e3cdb65b8"
- integrity sha512-Gp+Nt+J1GN8mVNWdV7hT5IX0er2ZCFplmi4FqrWs2JdUxby8k4SC91E+oLB/uGvQd+VIpYEOqz8qJgxfkTfjbw==
+"@aws-amplify/cli-extensibility-helper@2.3.29":
+ version "2.3.29"
+ resolved "https://registry.npmjs.org/@aws-amplify/cli-extensibility-helper/-/cli-extensibility-helper-2.3.29.tgz#c057c1147a0871efe2cc3295529f2297e6e76754"
+ integrity sha512-DTFTA1WkfToE6bA0ZZjfX3zBIwK0Z9guR1m+wutUiDHZcd6JBZUOPmEdLPzcJ8ruhyJbRYla6GEbstJO9XSTEQ==
dependencies:
- "@aws-amplify/amplify-category-custom" "2.3.31"
+ "@aws-amplify/amplify-category-custom" "2.3.33"
"@aws-cdk/aws-apigateway" "~1.124.0"
"@aws-cdk/aws-appsync" "~1.124.0"
"@aws-cdk/aws-cognito" "~1.124.0"
@@ -137,8 +137,8 @@
"@aws-cdk/aws-lambda" "~1.124.0"
"@aws-cdk/aws-s3" "~1.124.0"
"@aws-cdk/core" "~1.124.0"
- amplify-cli-core "2.8.0"
- amplify-prompts "2.1.0"
+ amplify-cli-core "2.9.0"
+ amplify-prompts "2.2.0"
"@aws-amplify/core@4.5.6":
version "4.5.6"
@@ -298,6 +298,21 @@
"@aws-cdk/cx-api" "1.124.0"
constructs "^3.3.69"
+"@aws-cdk/assertions@~1.124.0":
+ version "1.124.0"
+ resolved "https://registry.npmjs.org/@aws-cdk/assertions/-/assertions-1.124.0.tgz#4d61226f341cf4f8ba66238cf46fa9150cc87afb"
+ integrity sha512-jvJf701HizOu3gZHFeC0YQHED2P9aRzKuBk76kA07pAVxxLN7AWTbPrMBtC0ToCVnhazZ1sftxjnLOkxLyyloA==
+ dependencies:
+ "@aws-cdk/cloud-assembly-schema" "1.124.0"
+ "@aws-cdk/core" "1.124.0"
+ "@aws-cdk/cx-api" "1.124.0"
+ colors "^1.4.0"
+ constructs "^3.3.69"
+ diff "^5.0.0"
+ fast-deep-equal "^3.1.3"
+ string-width "^4.2.2"
+ table "^6.7.1"
+
"@aws-cdk/assets@1.124.0", "@aws-cdk/assets@~1.124.0":
version "1.124.0"
resolved "https://registry.npmjs.org/@aws-cdk/assets/-/assets-1.124.0.tgz#eaeacef8f2c03b93e3b742b6fe767b69e3cad4b3"
@@ -366,7 +381,7 @@
"@aws-cdk/core" "1.124.0"
constructs "^3.3.69"
-"@aws-cdk/aws-autoscaling-common@1.124.0":
+"@aws-cdk/aws-autoscaling-common@1.124.0", "@aws-cdk/aws-autoscaling-common@~1.124.0":
version "1.124.0"
resolved "https://registry.npmjs.org/@aws-cdk/aws-autoscaling-common/-/aws-autoscaling-common-1.124.0.tgz#c8e6e1b3d87336fb63b585a7f5d1f84e563ac285"
integrity sha512-Ef+YT0GSXtkc0jTwXmNqjbUtjHXgIg5FSt4lkfWRe2DzG/AeCEZDY0s+yUJMn6tXk1ubmqgIZO/i0VhTq8nW/g==
@@ -375,7 +390,7 @@
"@aws-cdk/core" "1.124.0"
constructs "^3.3.69"
-"@aws-cdk/aws-autoscaling-hooktargets@1.124.0":
+"@aws-cdk/aws-autoscaling-hooktargets@1.124.0", "@aws-cdk/aws-autoscaling-hooktargets@~1.124.0":
version "1.124.0"
resolved "https://registry.npmjs.org/@aws-cdk/aws-autoscaling-hooktargets/-/aws-autoscaling-hooktargets-1.124.0.tgz#b3441a97898a8846055929074943bdac599f4305"
integrity sha512-g0q+Jv124/krjMqT8U5gAKGHJwZkg5/yiswfWIy4aqEOieSwOwii4YuFUUIyghe779ua1yXFyEWnSfYAymXWgA==
@@ -444,7 +459,7 @@
"@aws-cdk/cx-api" "1.124.0"
constructs "^3.3.69"
-"@aws-cdk/aws-cloudfront@1.124.0":
+"@aws-cdk/aws-cloudfront@1.124.0", "@aws-cdk/aws-cloudfront@~1.124.0":
version "1.124.0"
resolved "https://registry.npmjs.org/@aws-cdk/aws-cloudfront/-/aws-cloudfront-1.124.0.tgz#81dd569112799ca5720ecb5efe502bddf06cd503"
integrity sha512-vd0vSEfKVWRtUMOWi5micPuC3qhLC7z7Dre/+X7N/YNqA+U+NX/g5k7jd00UdRnBiSxE34WRezHKsADUw8Tz6w==
@@ -882,7 +897,7 @@
"@aws-cdk/cx-api" "1.124.0"
constructs "^3.3.69"
-"@aws-cdk/aws-route53-targets@1.124.0":
+"@aws-cdk/aws-route53-targets@1.124.0", "@aws-cdk/aws-route53-targets@~1.124.0":
version "1.124.0"
resolved "https://registry.npmjs.org/@aws-cdk/aws-route53-targets/-/aws-route53-targets-1.124.0.tgz#fa1ddcce2af9c552574ab96d1af946ca717b9021"
integrity sha512-6+hN0zYlDmdXUZHmn5zVFD/sfH788212nLfRUo3V4Lg/XsV4vS3OV8b8wsctZCehuF1lagSIIF7rQJlGoYSBnA==
@@ -939,7 +954,7 @@
"@aws-cdk/cx-api" "1.124.0"
constructs "^3.3.69"
-"@aws-cdk/aws-sam@1.124.0":
+"@aws-cdk/aws-sam@1.124.0", "@aws-cdk/aws-sam@~1.124.0":
version "1.124.0"
resolved "https://registry.npmjs.org/@aws-cdk/aws-sam/-/aws-sam-1.124.0.tgz#cc20765fb556f1691ca64963a146c1fb106da66b"
integrity sha512-5yQsH3FNp+Mc4FdNwtrG1uRGqzXFSZEcds0FeVs338wDYLPDMnBe4kZzmOO4hY7d7siKP3E5tn+v8cSGvTUiSA==
@@ -1029,7 +1044,7 @@
"@aws-cdk/core" "1.124.0"
constructs "^3.3.69"
-"@aws-cdk/aws-ssm@1.124.0":
+"@aws-cdk/aws-ssm@1.124.0", "@aws-cdk/aws-ssm@~1.124.0":
version "1.124.0"
resolved "https://registry.npmjs.org/@aws-cdk/aws-ssm/-/aws-ssm-1.124.0.tgz#54c6f6959ea0327b38a865bec76445aa23bbda51"
integrity sha512-41mopAh9ZTg3SL04P0WOO1d3sCDYJL3A/fPWCxDuzizSojdI2EWqvKf/5Lu1NY1jYZG+2ls7xqgG4b8x9BDILw==
@@ -1802,16 +1817,16 @@
tslib "^2.0.0"
"@aws-sdk/client-s3@^3.25.0":
- version "3.95.0"
- resolved "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.95.0.tgz#8f3883a31b5d91e80a99064313c33f1da2abdc53"
- integrity sha512-IyPmgh5h8X5810buIcW0EqYZdm+MUN8FFugGQnpPFofhV77q9ISVS0XdsieZjBpZrFNg6qoSyvytwkGGGZP1IQ==
+ version "3.100.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.100.0.tgz#6cae596da29889848b009e34a4126842038906b0"
+ integrity sha512-UmgFdJabWtiUS4dWsC3kZ+ZMvH5QUUbDnLS8pT12duwLGt+xlWPn3PUkV8kL6qjG6XePLUgCqFTLjDD4tsTZNg==
dependencies:
"@aws-crypto/sha1-browser" "2.0.0"
"@aws-crypto/sha256-browser" "2.0.0"
"@aws-crypto/sha256-js" "2.0.0"
- "@aws-sdk/client-sts" "3.95.0"
+ "@aws-sdk/client-sts" "3.100.0"
"@aws-sdk/config-resolver" "3.80.0"
- "@aws-sdk/credential-provider-node" "3.95.0"
+ "@aws-sdk/credential-provider-node" "3.100.0"
"@aws-sdk/eventstream-serde-browser" "3.78.0"
"@aws-sdk/eventstream-serde-config-resolver" "3.78.0"
"@aws-sdk/eventstream-serde-node" "3.78.0"
@@ -1839,15 +1854,15 @@
"@aws-sdk/node-http-handler" "3.94.0"
"@aws-sdk/protocol-http" "3.78.0"
"@aws-sdk/signature-v4-multi-region" "3.88.0"
- "@aws-sdk/smithy-client" "3.85.0"
+ "@aws-sdk/smithy-client" "3.99.0"
"@aws-sdk/types" "3.78.0"
"@aws-sdk/url-parser" "3.78.0"
"@aws-sdk/util-base64-browser" "3.58.0"
"@aws-sdk/util-base64-node" "3.55.0"
"@aws-sdk/util-body-length-browser" "3.55.0"
"@aws-sdk/util-body-length-node" "3.55.0"
- "@aws-sdk/util-defaults-mode-browser" "3.85.0"
- "@aws-sdk/util-defaults-mode-node" "3.85.0"
+ "@aws-sdk/util-defaults-mode-browser" "3.99.0"
+ "@aws-sdk/util-defaults-mode-node" "3.99.0"
"@aws-sdk/util-stream-browser" "3.78.0"
"@aws-sdk/util-stream-node" "3.78.0"
"@aws-sdk/util-user-agent-browser" "3.78.0"
@@ -1860,6 +1875,42 @@
fast-xml-parser "3.19.0"
tslib "^2.3.1"
+"@aws-sdk/client-sso@3.100.0":
+ version "3.100.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.100.0.tgz#50fd953ff77f5f9cc1b67f2b6144016ef7e74112"
+ integrity sha512-nmBRUO5QfQ2IO8fHb37p8HT3n1ZooPb3sfTQejrpFH9Eq82VEOatIGt6yH3yTQ8+mhbabEhV5aY2wt/0D7wGVA==
+ dependencies:
+ "@aws-crypto/sha256-browser" "2.0.0"
+ "@aws-crypto/sha256-js" "2.0.0"
+ "@aws-sdk/config-resolver" "3.80.0"
+ "@aws-sdk/fetch-http-handler" "3.78.0"
+ "@aws-sdk/hash-node" "3.78.0"
+ "@aws-sdk/invalid-dependency" "3.78.0"
+ "@aws-sdk/middleware-content-length" "3.78.0"
+ "@aws-sdk/middleware-host-header" "3.78.0"
+ "@aws-sdk/middleware-logger" "3.78.0"
+ "@aws-sdk/middleware-retry" "3.80.0"
+ "@aws-sdk/middleware-serde" "3.78.0"
+ "@aws-sdk/middleware-stack" "3.78.0"
+ "@aws-sdk/middleware-user-agent" "3.78.0"
+ "@aws-sdk/node-config-provider" "3.80.0"
+ "@aws-sdk/node-http-handler" "3.94.0"
+ "@aws-sdk/protocol-http" "3.78.0"
+ "@aws-sdk/smithy-client" "3.99.0"
+ "@aws-sdk/types" "3.78.0"
+ "@aws-sdk/url-parser" "3.78.0"
+ "@aws-sdk/util-base64-browser" "3.58.0"
+ "@aws-sdk/util-base64-node" "3.55.0"
+ "@aws-sdk/util-body-length-browser" "3.55.0"
+ "@aws-sdk/util-body-length-node" "3.55.0"
+ "@aws-sdk/util-defaults-mode-browser" "3.99.0"
+ "@aws-sdk/util-defaults-mode-node" "3.99.0"
+ "@aws-sdk/util-user-agent-browser" "3.78.0"
+ "@aws-sdk/util-user-agent-node" "3.80.0"
+ "@aws-sdk/util-utf8-browser" "3.55.0"
+ "@aws-sdk/util-utf8-node" "3.55.0"
+ tslib "^2.3.1"
+
"@aws-sdk/client-sso@3.48.0":
version "3.48.0"
resolved "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.48.0.tgz#63935b337b5d58d46d38d3251c113201da0ed9b0"
@@ -1896,14 +1947,15 @@
"@aws-sdk/util-utf8-node" "3.47.2"
tslib "^2.3.0"
-"@aws-sdk/client-sso@3.95.0":
- version "3.95.0"
- resolved "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.95.0.tgz#0226f9feb8896a90d4ee1627a5ddbd7a3adab163"
- integrity sha512-yxiVyRG5ULTVzOTmlrsy1krjpBQo20+ZfQ9p++A7cA8dIsytzjlPLvtY+Pzz0pTa3h2B6tlVBmumgEQzh5Y8Ug==
+"@aws-sdk/client-sts@3.100.0":
+ version "3.100.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.100.0.tgz#147b65a91ebfa564ac58aef51e14b0ece8adb26c"
+ integrity sha512-WHy0e6COf6/LfMsYqG9H4SGaQRDjuckMtwOLtu6cYr4cro3bOU5pNuyEjAdUHCpuhZgiE6gkZozhTlxMJqIuRQ==
dependencies:
"@aws-crypto/sha256-browser" "2.0.0"
"@aws-crypto/sha256-js" "2.0.0"
"@aws-sdk/config-resolver" "3.80.0"
+ "@aws-sdk/credential-provider-node" "3.100.0"
"@aws-sdk/fetch-http-handler" "3.78.0"
"@aws-sdk/hash-node" "3.78.0"
"@aws-sdk/invalid-dependency" "3.78.0"
@@ -1911,25 +1963,29 @@
"@aws-sdk/middleware-host-header" "3.78.0"
"@aws-sdk/middleware-logger" "3.78.0"
"@aws-sdk/middleware-retry" "3.80.0"
+ "@aws-sdk/middleware-sdk-sts" "3.78.0"
"@aws-sdk/middleware-serde" "3.78.0"
+ "@aws-sdk/middleware-signing" "3.78.0"
"@aws-sdk/middleware-stack" "3.78.0"
"@aws-sdk/middleware-user-agent" "3.78.0"
"@aws-sdk/node-config-provider" "3.80.0"
"@aws-sdk/node-http-handler" "3.94.0"
"@aws-sdk/protocol-http" "3.78.0"
- "@aws-sdk/smithy-client" "3.85.0"
+ "@aws-sdk/smithy-client" "3.99.0"
"@aws-sdk/types" "3.78.0"
"@aws-sdk/url-parser" "3.78.0"
"@aws-sdk/util-base64-browser" "3.58.0"
"@aws-sdk/util-base64-node" "3.55.0"
"@aws-sdk/util-body-length-browser" "3.55.0"
"@aws-sdk/util-body-length-node" "3.55.0"
- "@aws-sdk/util-defaults-mode-browser" "3.85.0"
- "@aws-sdk/util-defaults-mode-node" "3.85.0"
+ "@aws-sdk/util-defaults-mode-browser" "3.99.0"
+ "@aws-sdk/util-defaults-mode-node" "3.99.0"
"@aws-sdk/util-user-agent-browser" "3.78.0"
"@aws-sdk/util-user-agent-node" "3.80.0"
"@aws-sdk/util-utf8-browser" "3.55.0"
"@aws-sdk/util-utf8-node" "3.55.0"
+ entities "2.2.0"
+ fast-xml-parser "3.19.0"
tslib "^2.3.1"
"@aws-sdk/client-sts@3.48.0":
@@ -1973,47 +2029,6 @@
fast-xml-parser "3.19.0"
tslib "^2.3.0"
-"@aws-sdk/client-sts@3.95.0":
- version "3.95.0"
- resolved "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.95.0.tgz#a5168f72de0d2125a01ca84d067e2a0502977fc2"
- integrity sha512-qeoiEyBB5IQyjgjkCCBiQnISar7OJgjuTf2alM6ehjq8H4/T5VeMCeohEs5FR0/O60sJn40ZpL3YY/OmMh55UA==
- dependencies:
- "@aws-crypto/sha256-browser" "2.0.0"
- "@aws-crypto/sha256-js" "2.0.0"
- "@aws-sdk/config-resolver" "3.80.0"
- "@aws-sdk/credential-provider-node" "3.95.0"
- "@aws-sdk/fetch-http-handler" "3.78.0"
- "@aws-sdk/hash-node" "3.78.0"
- "@aws-sdk/invalid-dependency" "3.78.0"
- "@aws-sdk/middleware-content-length" "3.78.0"
- "@aws-sdk/middleware-host-header" "3.78.0"
- "@aws-sdk/middleware-logger" "3.78.0"
- "@aws-sdk/middleware-retry" "3.80.0"
- "@aws-sdk/middleware-sdk-sts" "3.78.0"
- "@aws-sdk/middleware-serde" "3.78.0"
- "@aws-sdk/middleware-signing" "3.78.0"
- "@aws-sdk/middleware-stack" "3.78.0"
- "@aws-sdk/middleware-user-agent" "3.78.0"
- "@aws-sdk/node-config-provider" "3.80.0"
- "@aws-sdk/node-http-handler" "3.94.0"
- "@aws-sdk/protocol-http" "3.78.0"
- "@aws-sdk/smithy-client" "3.85.0"
- "@aws-sdk/types" "3.78.0"
- "@aws-sdk/url-parser" "3.78.0"
- "@aws-sdk/util-base64-browser" "3.58.0"
- "@aws-sdk/util-base64-node" "3.55.0"
- "@aws-sdk/util-body-length-browser" "3.55.0"
- "@aws-sdk/util-body-length-node" "3.55.0"
- "@aws-sdk/util-defaults-mode-browser" "3.85.0"
- "@aws-sdk/util-defaults-mode-node" "3.85.0"
- "@aws-sdk/util-user-agent-browser" "3.78.0"
- "@aws-sdk/util-user-agent-node" "3.80.0"
- "@aws-sdk/util-utf8-browser" "3.55.0"
- "@aws-sdk/util-utf8-node" "3.55.0"
- entities "2.2.0"
- fast-xml-parser "3.19.0"
- tslib "^2.3.1"
-
"@aws-sdk/client-textract@3.6.1":
version "3.6.1"
resolved "https://registry.npmjs.org/@aws-sdk/client-textract/-/client-textract-3.6.1.tgz#b8972f53f0353222b4c052adc784291e602be6aa"
@@ -2187,6 +2202,20 @@
"@aws-sdk/url-parser" "3.78.0"
tslib "^2.3.1"
+"@aws-sdk/credential-provider-ini@3.100.0":
+ version "3.100.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.100.0.tgz#5da9d935f1632cc0a32a1b5222a692e3dc78c1cb"
+ integrity sha512-2pCtth/Iv4mATRwb2g1nJdd9TolMyhTnhcskopukFvzp13VS5cgtz0hgYmJNnztTF8lpKJhJaidtKS5JJTWnHg==
+ dependencies:
+ "@aws-sdk/credential-provider-env" "3.78.0"
+ "@aws-sdk/credential-provider-imds" "3.81.0"
+ "@aws-sdk/credential-provider-sso" "3.100.0"
+ "@aws-sdk/credential-provider-web-identity" "3.78.0"
+ "@aws-sdk/property-provider" "3.78.0"
+ "@aws-sdk/shared-ini-file-loader" "3.80.0"
+ "@aws-sdk/types" "3.78.0"
+ tslib "^2.3.1"
+
"@aws-sdk/credential-provider-ini@3.48.0":
version "3.48.0"
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.48.0.tgz#57b83cde1931f1a30ab6a7ec272697eec9751702"
@@ -2212,14 +2241,16 @@
"@aws-sdk/types" "3.6.1"
tslib "^1.8.0"
-"@aws-sdk/credential-provider-ini@3.95.0":
- version "3.95.0"
- resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.95.0.tgz#c4d1d64b8ad5e89d450966b254bd796bd320a2d1"
- integrity sha512-Ditfnmo8/F79Zj8HmaRZZTDsthhvKcdgGFus+pF3kxZxyR3YU/k7/eGUISZeCQhA0/9nwxXMFDUmAIsa0AMfyg==
+"@aws-sdk/credential-provider-node@3.100.0":
+ version "3.100.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.100.0.tgz#8a5cab82c2943cf376986865f4d3a734f023eb44"
+ integrity sha512-PMIPnn/dhv9tlWR0qXnANJpTumujWfhKnLAsV3BUqB1K9IzWqz/zXjCT0jcSUTY8X/VkSuehtBdCKvOOM5mMqg==
dependencies:
"@aws-sdk/credential-provider-env" "3.78.0"
"@aws-sdk/credential-provider-imds" "3.81.0"
- "@aws-sdk/credential-provider-sso" "3.95.0"
+ "@aws-sdk/credential-provider-ini" "3.100.0"
+ "@aws-sdk/credential-provider-process" "3.80.0"
+ "@aws-sdk/credential-provider-sso" "3.100.0"
"@aws-sdk/credential-provider-web-identity" "3.78.0"
"@aws-sdk/property-provider" "3.78.0"
"@aws-sdk/shared-ini-file-loader" "3.80.0"
@@ -2257,22 +2288,6 @@
"@aws-sdk/types" "3.6.1"
tslib "^1.8.0"
-"@aws-sdk/credential-provider-node@3.95.0":
- version "3.95.0"
- resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.95.0.tgz#5ec6bf233b22689bf873f7039fae5cee33991e14"
- integrity sha512-NCXejOQg5p+oJPaUPa+jIrU7pStm8zZtOn6lXVnubZrQClv2+ZQrgxVt6otN464SWqMQbmLFYNyYf4bsLJaEhA==
- dependencies:
- "@aws-sdk/credential-provider-env" "3.78.0"
- "@aws-sdk/credential-provider-imds" "3.81.0"
- "@aws-sdk/credential-provider-ini" "3.95.0"
- "@aws-sdk/credential-provider-process" "3.80.0"
- "@aws-sdk/credential-provider-sso" "3.95.0"
- "@aws-sdk/credential-provider-web-identity" "3.78.0"
- "@aws-sdk/property-provider" "3.78.0"
- "@aws-sdk/shared-ini-file-loader" "3.80.0"
- "@aws-sdk/types" "3.78.0"
- tslib "^2.3.1"
-
"@aws-sdk/credential-provider-process@3.47.2":
version "3.47.2"
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.47.2.tgz#876ebcb031b90c484c860b39fdbcc4d635f1dfe3"
@@ -2305,6 +2320,17 @@
"@aws-sdk/types" "3.78.0"
tslib "^2.3.1"
+"@aws-sdk/credential-provider-sso@3.100.0":
+ version "3.100.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.100.0.tgz#c930bb55565a9f0e580ddb0b9721e6fd9d8eafe7"
+ integrity sha512-DwJvrh77vBJ1/fS9z0i2QuIvSk4pATA4DH8AEWoQ8LQX97tp3es7gZV5Wu93wFsEyIYC8penz6pNVq5QajMk2A==
+ dependencies:
+ "@aws-sdk/client-sso" "3.100.0"
+ "@aws-sdk/property-provider" "3.78.0"
+ "@aws-sdk/shared-ini-file-loader" "3.80.0"
+ "@aws-sdk/types" "3.78.0"
+ tslib "^2.3.1"
+
"@aws-sdk/credential-provider-sso@3.48.0":
version "3.48.0"
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.48.0.tgz#425a0a4e43134493cd649304959de1ba38cd6e9b"
@@ -2317,17 +2343,6 @@
"@aws-sdk/util-credentials" "3.47.2"
tslib "^2.3.0"
-"@aws-sdk/credential-provider-sso@3.95.0":
- version "3.95.0"
- resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.95.0.tgz#326f7788fabb6814bd67a3b357eb9492c9e46acf"
- integrity sha512-YIzBBWUKkazvoM8CsCbf4YIs5ENtOr5KXUd6e993c3oRMNsUykOtf/AUBN6G3HItuyxoA8vW/8M6tKX44cRCAg==
- dependencies:
- "@aws-sdk/client-sso" "3.95.0"
- "@aws-sdk/property-provider" "3.78.0"
- "@aws-sdk/shared-ini-file-loader" "3.80.0"
- "@aws-sdk/types" "3.78.0"
- tslib "^2.3.1"
-
"@aws-sdk/credential-provider-web-identity@3.47.2":
version "3.47.2"
resolved "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.47.2.tgz#3c184ea47253d9d3a34f09dc5d9abf9b6b755d4b"
@@ -2582,9 +2597,9 @@
tslib "^1.8.0"
"@aws-sdk/lib-storage@^3.25.0":
- version "3.97.0"
- resolved "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.97.0.tgz#a21eba211f51136048845d767d1ed45e9df6997a"
- integrity sha512-PKLQbzoa7OkVi/EMepllmFmLgII3V4j1WbBPhZDVogx3Isvdza83meGVfmtQteZAp31pq3IN7exY4uezrmOkUw==
+ version "3.100.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.100.0.tgz#88d7976dbee04ed0dcccccb6100b680279f02296"
+ integrity sha512-IP8Y310+24FOI3bZqdx9mTef1fKUa5YFfMa+Zmfj4+cxMB5/5wrAc2MacyQdPshmdOCIfJ8Izikop6SqeEQqcg==
dependencies:
buffer "5.6.0"
events "3.3.0"
@@ -3258,10 +3273,10 @@
"@aws-sdk/types" "3.6.1"
tslib "^1.8.0"
-"@aws-sdk/smithy-client@3.85.0":
- version "3.85.0"
- resolved "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.85.0.tgz#70852daa14fef9af1adfb4411237026cb68943da"
- integrity sha512-Ox/yQEAnANzhpJMyrpuxWtF/i3EviavENczT7fo4uwSyZTz/sfSBQNjs/YAG1UeA6uOI3pBP5EaFERV5hr2fRA==
+"@aws-sdk/smithy-client@3.99.0":
+ version "3.99.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/smithy-client/-/smithy-client-3.99.0.tgz#e9cd92e95983734b88204432a44ee52388af0e1c"
+ integrity sha512-N9xgCcwbOBZ4/WuROzlErExXV6+vFrFkNJzeBT31/avvrHXjxgxwQlMoXoQCfM8PyRuDuVSfZeoh1iIRfoxidA==
dependencies:
"@aws-sdk/middleware-stack" "3.78.0"
"@aws-sdk/types" "3.78.0"
@@ -3491,10 +3506,10 @@
bowser "^2.11.0"
tslib "^2.3.0"
-"@aws-sdk/util-defaults-mode-browser@3.85.0":
- version "3.85.0"
- resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.85.0.tgz#215e99e8815f885ce722668a0e5afbbca69fa964"
- integrity sha512-oqK/e2pHuMWrvTJWtDBzylbj232ezlTay5dCq4RQlyi3LPPVBQ08haYD1Mk2ikQ/qa0XvbSD6YVhjpTlvwRNjw==
+"@aws-sdk/util-defaults-mode-browser@3.99.0":
+ version "3.99.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-browser/-/util-defaults-mode-browser-3.99.0.tgz#4d5b71279e89a25b9dd1d44ab7631fecbe48c447"
+ integrity sha512-qSYjUGuN8n7Q/zAi0tzU4BrU389jQosXtjp7eHpLATl0pKGpaHx6rJNwbiNhvBhBEfmSgqsJ09b4gZUpUezHEw==
dependencies:
"@aws-sdk/property-provider" "3.78.0"
"@aws-sdk/types" "3.78.0"
@@ -3513,10 +3528,10 @@
"@aws-sdk/types" "3.47.1"
tslib "^2.3.0"
-"@aws-sdk/util-defaults-mode-node@3.85.0":
- version "3.85.0"
- resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.85.0.tgz#8cd27ea50ddce298ec586d36eb6379ba14d7bfaf"
- integrity sha512-KDNl4H8jJJLh6y7I3MSwRKe4plKbFKK8MVkS0+Fce/GJh4EnqxF0HzMMaSeNUcPvO2wHRq2a60+XW+0d7eWo1A==
+"@aws-sdk/util-defaults-mode-node@3.99.0":
+ version "3.99.0"
+ resolved "https://registry.npmjs.org/@aws-sdk/util-defaults-mode-node/-/util-defaults-mode-node-3.99.0.tgz#72f75b02b337bc5bd221ab3504caa4709cf88c8f"
+ integrity sha512-8TUO0kEnQcgT1gAW9y9oO6a5gKhfEGEUeKidEgbTczEUrjr3aCXIC+p0DI5FJfImwPrTKXra8A22utDM92phWw==
dependencies:
"@aws-sdk/config-resolver" "3.80.0"
"@aws-sdk/credential-provider-imds" "3.81.0"
@@ -3780,20 +3795,20 @@
integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==
"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.10.5", "@babel/core@^7.12.3", "@babel/core@^7.7.5":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.0.tgz#c58d04d7c6fbfb58ea7681e2b9145cfb62726756"
- integrity sha512-Xyw74OlJwDijToNi0+6BBI5mLLR5+5R3bcSH80LXzjzEGEUlvNzujEE71BaD/ApEZHAvFI/Mlmp4M5lIkdeeWw==
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876"
+ integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.16.7"
- "@babel/generator" "^7.18.0"
- "@babel/helper-compilation-targets" "^7.17.10"
+ "@babel/generator" "^7.18.2"
+ "@babel/helper-compilation-targets" "^7.18.2"
"@babel/helper-module-transforms" "^7.18.0"
- "@babel/helpers" "^7.18.0"
+ "@babel/helpers" "^7.18.2"
"@babel/parser" "^7.18.0"
"@babel/template" "^7.16.7"
- "@babel/traverse" "^7.18.0"
- "@babel/types" "^7.18.0"
+ "@babel/traverse" "^7.18.2"
+ "@babel/types" "^7.18.2"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@@ -3811,12 +3826,12 @@
source-map "^0.5.0"
trim-right "^1.0.1"
-"@babel/generator@^7.18.0", "@babel/generator@^7.5.0":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.0.tgz#46d28e8a18fc737b028efb25ab105d74473af43f"
- integrity sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==
+"@babel/generator@^7.18.2", "@babel/generator@^7.5.0":
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d"
+ integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==
dependencies:
- "@babel/types" "^7.18.0"
+ "@babel/types" "^7.18.2"
"@jridgewell/gen-mapping" "^0.3.0"
jsesc "^2.5.1"
@@ -3827,10 +3842,10 @@
dependencies:
"@babel/types" "^7.16.7"
-"@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.10":
- version "7.17.10"
- resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.10.tgz#09c63106d47af93cf31803db6bc49fef354e2ebe"
- integrity sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==
+"@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.10", "@babel/helper-compilation-targets@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b"
+ integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==
dependencies:
"@babel/compat-data" "^7.17.10"
"@babel/helper-validator-option" "^7.16.7"
@@ -3850,12 +3865,10 @@
"@babel/helper-replace-supers" "^7.16.7"
"@babel/helper-split-export-declaration" "^7.16.7"
-"@babel/helper-environment-visitor@^7.16.7":
- version "7.16.7"
- resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7"
- integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==
- dependencies:
- "@babel/types" "^7.16.7"
+"@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd"
+ integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==
"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.17.9":
version "7.17.9"
@@ -3872,7 +3885,7 @@
dependencies:
"@babel/types" "^7.16.7"
-"@babel/helper-member-expression-to-functions@^7.16.7", "@babel/helper-member-expression-to-functions@^7.17.7":
+"@babel/helper-member-expression-to-functions@^7.17.7":
version "7.17.7"
resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz#a34013b57d8542a8c4ff8ba3f747c02452a4d8c4"
integrity sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==
@@ -3912,23 +3925,23 @@
resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96"
integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==
-"@babel/helper-replace-supers@^7.16.7":
- version "7.16.7"
- resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz#e9f5f5f32ac90429c1a4bdec0f231ef0c2838ab1"
- integrity sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==
+"@babel/helper-replace-supers@^7.16.7", "@babel/helper-replace-supers@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.18.2.tgz#41fdfcc9abaf900e18ba6e5931816d9062a7b2e0"
+ integrity sha512-XzAIyxx+vFnrOxiQrToSUOzUOn0e1J2Li40ntddek1Y69AXUTXoDJ40/D5RdjFu7s7qHiaeoTiempZcbuVXh2Q==
dependencies:
- "@babel/helper-environment-visitor" "^7.16.7"
- "@babel/helper-member-expression-to-functions" "^7.16.7"
+ "@babel/helper-environment-visitor" "^7.18.2"
+ "@babel/helper-member-expression-to-functions" "^7.17.7"
"@babel/helper-optimise-call-expression" "^7.16.7"
- "@babel/traverse" "^7.16.7"
- "@babel/types" "^7.16.7"
+ "@babel/traverse" "^7.18.2"
+ "@babel/types" "^7.18.2"
-"@babel/helper-simple-access@^7.10.4", "@babel/helper-simple-access@^7.17.7":
- version "7.17.7"
- resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz#aaa473de92b7987c6dfa7ce9a7d9674724823367"
- integrity sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==
+"@babel/helper-simple-access@^7.10.4", "@babel/helper-simple-access@^7.17.7", "@babel/helper-simple-access@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9"
+ integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==
dependencies:
- "@babel/types" "^7.17.0"
+ "@babel/types" "^7.18.2"
"@babel/helper-skip-transparent-expression-wrappers@^7.16.0":
version "7.16.0"
@@ -3954,14 +3967,14 @@
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==
-"@babel/helpers@^7.18.0":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.0.tgz#aff37c3590de42102b54842446146d0205946370"
- integrity sha512-AE+HMYhmlMIbho9nbvicHyxFwhrO+xhKB6AhRxzl8w46Yj0VXTZjEsAoBVC7rB2I0jzX+yWyVybnO08qkfx6kg==
+"@babel/helpers@^7.18.2":
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384"
+ integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg==
dependencies:
"@babel/template" "^7.16.7"
- "@babel/traverse" "^7.18.0"
- "@babel/types" "^7.18.0"
+ "@babel/traverse" "^7.18.2"
+ "@babel/types" "^7.18.2"
"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7":
version "7.17.12"
@@ -3978,9 +3991,9 @@
integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.0", "@babel/parser@^7.7.0":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.0.tgz#10a8d4e656bc01128d299a787aa006ce1a91e112"
- integrity sha512-AqDccGC+m5O/iUStSJy3DGRIUFu7WbY/CppZYwrEUB4N0tZlnI8CSTsgL7v5fHVFmUbRv2sd+yy27o8Ydt4MGg==
+ version "7.18.4"
+ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
+ integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
"@babel/plugin-proposal-class-properties@^7.0.0":
version "7.17.12"
@@ -4129,23 +4142,23 @@
"@babel/helper-plugin-utils" "^7.16.7"
"@babel/plugin-transform-block-scoping@^7.0.0":
- version "7.17.12"
- resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.17.12.tgz#68fc3c4b3bb7dfd809d97b7ed19a584052a2725c"
- integrity sha512-jw8XW/B1i7Lqwqj2CbrViPcZijSxfguBWZP2aN59NHgxUyO/OcO1mfdCxH13QhN5LbWhPkX+f+brKGhZTiqtZQ==
+ version "7.18.4"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.4.tgz#7988627b3e9186a13e4d7735dc9c34a056613fb9"
+ integrity sha512-+Hq10ye+jlvLEogSOtq4mKvtk7qwcUQ1f0Mrueai866C82f844Yom2cttfJdMdqRLTxWpsbfbkIkOIfovyUQXw==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
"@babel/plugin-transform-classes@^7.0.0":
- version "7.17.12"
- resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.17.12.tgz#da889e89a4d38375eeb24985218edeab93af4f29"
- integrity sha512-cvO7lc7pZat6BsvH6l/EGaI8zpl8paICaoGk+7x7guvtfak/TbIf66nYmJOH13EuG0H+Xx3M+9LQDtSvZFKXKw==
+ version "7.18.4"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.4.tgz#51310b812a090b846c784e47087fa6457baef814"
+ integrity sha512-e42NSG2mlKWgxKUAD9EJJSkZxR67+wZqzNxLSpc51T8tRU5SLFHsPmgYR5yr7sdgX4u+iHA1C5VafJ6AyImV3A==
dependencies:
"@babel/helper-annotate-as-pure" "^7.16.7"
- "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/helper-environment-visitor" "^7.18.2"
"@babel/helper-function-name" "^7.17.9"
"@babel/helper-optimise-call-expression" "^7.16.7"
"@babel/helper-plugin-utils" "^7.17.12"
- "@babel/helper-replace-supers" "^7.16.7"
+ "@babel/helper-replace-supers" "^7.18.2"
"@babel/helper-split-export-declaration" "^7.16.7"
globals "^11.1.0"
@@ -4212,13 +4225,13 @@
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-modules-commonjs@^7.0.0":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.0.tgz#3be575e19fbd273d42adbc84566b1fad3582b3db"
- integrity sha512-cCeR0VZWtfxWS4YueAK2qtHtBPJRSaJcMlbS8jhSIm/A3E2Kpro4W1Dn4cqJtp59dtWfXjQwK7SPKF8ghs7rlw==
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.2.tgz#1aa8efa2e2a6e818b6a7f2235fceaf09bdb31e9e"
+ integrity sha512-f5A865gFPAJAEE0K7F/+nm5CmAE3y8AWlMBG9unu5j9+tk50UQVK0QS8RNxSp7MJf0wh97uYyLWt3Zvu71zyOQ==
dependencies:
"@babel/helper-module-transforms" "^7.18.0"
"@babel/helper-plugin-utils" "^7.17.12"
- "@babel/helper-simple-access" "^7.17.7"
+ "@babel/helper-simple-access" "^7.18.2"
babel-plugin-dynamic-import-node "^2.3.3"
"@babel/plugin-transform-object-super@^7.0.0":
@@ -4277,9 +4290,9 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.16.0"
"@babel/plugin-transform-template-literals@^7.0.0":
- version "7.17.12"
- resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.17.12.tgz#4aec0a18f39dd86c442e1d077746df003e362c6e"
- integrity sha512-kAKJ7DX1dSRa2s7WN1xUAuaQmkTpN+uig4wCKWivVXIObqGbVTUlSavHyfI2iZvz89GFAMGm9p2DBJ4Y1Tp0hw==
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.2.tgz#31ed6915721864847c48b656281d0098ea1add28"
+ integrity sha512-/cmuBVw9sZBGZVOMkpAEaVLwm4JmK2GZ1dFKOGGpMzEHWFmyZZ59lUU0PdRr8YNYeQdNzTDwuxP2X2gzydTc9g==
dependencies:
"@babel/helper-plugin-utils" "^7.17.12"
@@ -4293,21 +4306,14 @@
"@babel/plugin-syntax-typescript" "^7.10.4"
"@babel/runtime-corejs3@^7.10.2":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.0.tgz#eed03023c5226b1e2b2ba32b8b6af5cb0518a6c7"
- integrity sha512-G5FaGZOWORq9zthDjIrjib5XlcddeqLbIiDO3YQsut6j7aGf76xn0umUC/pA6+nApk3hQJF4JzLzg5PCl6ewJg==
+ version "7.18.3"
+ resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.3.tgz#52f0241a31e0ec61a6187530af6227c2846bd60c"
+ integrity sha512-l4ddFwrc9rnR+EJsHsh+TJ4A35YqQz/UqcjtlX2ov53hlJYG5CxtQmNZxyajwDVmCxwy++rtvGU5HazCK4W41Q==
dependencies:
core-js-pure "^3.20.2"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3", "@babel/runtime@^7.9.6":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.0.tgz#6d77142a19cb6088f0af662af1ada37a604d34ae"
- integrity sha512-YMQvx/6nKEaucl0MY56mwIG483xk8SDNdlUwb2Ts6FUpr7fm85DxEmsY18LXBNhcTz6tO6JwZV8w1W06v8UKeg==
- dependencies:
- regenerator-runtime "^0.13.4"
-
-"@babel/runtime@^7.5.5":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.9.6":
version "7.18.3"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4"
integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==
@@ -4323,19 +4329,19 @@
"@babel/parser" "^7.16.7"
"@babel/types" "^7.16.7"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.18.0", "@babel/traverse@^7.7.0":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.0.tgz#0e5ec6db098660b2372dd63d096bf484e32d27ba"
- integrity sha512-oNOO4vaoIQoGjDQ84LgtF/IAlxlyqL4TUuoQ7xLkQETFaHkY1F7yazhB4Kt3VcZGL0ZF/jhrEpnXqUb0M7V3sw==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.7.0":
+ version "7.18.2"
+ resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz#b77a52604b5cc836a9e1e08dca01cba67a12d2e8"
+ integrity sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==
dependencies:
"@babel/code-frame" "^7.16.7"
- "@babel/generator" "^7.18.0"
- "@babel/helper-environment-visitor" "^7.16.7"
+ "@babel/generator" "^7.18.2"
+ "@babel/helper-environment-visitor" "^7.18.2"
"@babel/helper-function-name" "^7.17.9"
"@babel/helper-hoist-variables" "^7.16.7"
"@babel/helper-split-export-declaration" "^7.16.7"
"@babel/parser" "^7.18.0"
- "@babel/types" "^7.18.0"
+ "@babel/types" "^7.18.2"
debug "^4.1.0"
globals "^11.1.0"
@@ -4356,10 +4362,10 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
-"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.12", "@babel/types@^7.18.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
- version "7.18.0"
- resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.0.tgz#ef523ea349722849cb4bf806e9342ede4d071553"
- integrity sha512-vhAmLPAiC8j9K2GnsnLPCIH5wCrPpYIVBCWRBFDCB7Y/BXLqi/O+1RSTTM2bsmg6U/551+FCf9PNPxjABmxHTw==
+"@babel/types@^7.0.0", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.17.12", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.7.0":
+ version "7.18.4"
+ resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354"
+ integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==
dependencies:
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
@@ -4785,7 +4791,16 @@
"@graphql-tools/utils" "8.6.12"
tslib "~2.4.0"
-"@graphql-tools/schema@8.3.13", "@graphql-tools/schema@^8.3.1":
+"@graphql-tools/merge@^6.0.18":
+ version "6.2.17"
+ resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.17.tgz#4dedf87d8435a5e1091d7cc8d4f371ed1e029f1f"
+ integrity sha512-G5YrOew39fZf16VIrc49q3c8dBqQDD0ax5LYPiNja00xsXDi0T9zsEWVt06ApjtSdSF6HDddlu5S12QjeN8Tow==
+ dependencies:
+ "@graphql-tools/schema" "^8.0.2"
+ "@graphql-tools/utils" "8.0.2"
+ tslib "~2.3.0"
+
+"@graphql-tools/schema@8.3.13", "@graphql-tools/schema@^8.0.2", "@graphql-tools/schema@^8.3.1":
version "8.3.13"
resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.13.tgz#099460459d7821dd8deb34952900fe300085ba0b"
integrity sha512-e+bx1VHj1i5v4HmhCYCar0lqdoLmkRi/CfV07rTqHR6CRDbIb/S/qDCajHLt7FCovQ5ozlI5sRVbBhzfq5H0PQ==
@@ -4816,6 +4831,13 @@
value-or-promise "^1.0.11"
ws "^8.3.0"
+"@graphql-tools/utils@8.0.2":
+ version "8.0.2"
+ resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.0.2.tgz#795a8383cdfdc89855707d62491c576f439f3c51"
+ integrity sha512-gzkavMOgbhnwkHJYg32Adv6f+LxjbQmmbdD5Hty0+CWxvaiuJq+nU6tzb/7VSU4cwhbNLx/lGu2jbCPEW1McZQ==
+ dependencies:
+ tslib "~2.3.0"
+
"@graphql-tools/utils@8.6.12", "@graphql-tools/utils@^8.5.1":
version "8.6.12"
resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.12.tgz#0a550dc0331fd9b097fe7223d65cbbee720556e4"
@@ -6264,9 +6286,9 @@
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
"@types/node@*", "@types/node@>=12":
- version "17.0.35"
- resolved "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz#635b7586086d51fb40de0a2ec9d1014a5283ba4a"
- integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==
+ version "17.0.40"
+ resolved "https://registry.npmjs.org/@types/node/-/node-17.0.40.tgz#76ee88ae03650de8064a6cf75b8d95f9f4a16090"
+ integrity sha512-UXdBxNGqTMtm7hCwh9HtncFVLrXoqA3oJW30j6XWp5BH/wu3mVeaxo7cq5benFdBw34HB3XDT2TRPI7rXZ+mDg==
"@types/node@^10.17.60":
version "10.17.60"
@@ -6274,14 +6296,14 @@
integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
"@types/node@^12.12.6":
- version "12.20.52"
- resolved "https://registry.npmjs.org/@types/node/-/node-12.20.52.tgz#2fd2dc6bfa185601b15457398d4ba1ef27f81251"
- integrity sha512-cfkwWw72849SNYp3Zx0IcIs25vABmFh73xicxhCkTcvtZQeIez15PpwQN8fY3RD7gv1Wrxlc9MEtfMORZDEsGw==
+ version "12.20.54"
+ resolved "https://registry.npmjs.org/@types/node/-/node-12.20.54.tgz#38a3dff8c2a939553f2cdb85dcddc68be46c3c68"
+ integrity sha512-CFMnEPkSXWALI73t1oIWyb8QOmVrp6RruAqIx349sd+1ImaFwzlKcz55mwrx/yLyOyz1gkq/UKuNOigt27PXqg==
"@types/node@^16.9.2":
- version "16.11.36"
- resolved "https://registry.npmjs.org/@types/node/-/node-16.11.36.tgz#9ab9f8276987132ed2b225cace2218ba794fc751"
- integrity sha512-FR5QJe+TaoZ2GsMHkjuwoNabr+UrJNRr2HNOo+r/7vhcuntM6Ee/pRPOnRhhL2XE9OOvX9VLEq+BcXl3VjNoWA==
+ version "16.11.38"
+ resolved "https://registry.npmjs.org/@types/node/-/node-16.11.38.tgz#be0edd097b23eace6c471c525a74b3f98803017f"
+ integrity sha512-hjO/0K140An3GWDw2HJfq7gko3wWeznbjXgg+rzPdVzhe198hp4x2i1dgveAOEiFKd8sOilAxzoSJiVv5P/CUg==
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
@@ -6309,9 +6331,9 @@
integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
"@types/prettier@^2.0.0":
- version "2.6.1"
- resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.1.tgz#76e72d8a775eef7ce649c63c8acae1a0824bbaed"
- integrity sha512-XFjFHmaLVifrAKaZ+EKghFHtHSUonyw8P2Qmy2/+osBnrKbH9UYtlK10zg8/kCt47MFilll/DEDKy3DHfJ0URw==
+ version "2.6.3"
+ resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a"
+ integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg==
"@types/rimraf@^3.0.0":
version "3.0.2"
@@ -6416,23 +6438,23 @@
"@typescript-eslint/types" "4.33.0"
"@typescript-eslint/visitor-keys" "4.33.0"
-"@typescript-eslint/scope-manager@5.26.0":
- version "5.26.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz#44209c7f649d1a120f0717e0e82da856e9871339"
- integrity sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw==
+"@typescript-eslint/scope-manager@5.27.1":
+ version "5.27.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d"
+ integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==
dependencies:
- "@typescript-eslint/types" "5.26.0"
- "@typescript-eslint/visitor-keys" "5.26.0"
+ "@typescript-eslint/types" "5.27.1"
+ "@typescript-eslint/visitor-keys" "5.27.1"
"@typescript-eslint/types@4.33.0":
version "4.33.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72"
integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==
-"@typescript-eslint/types@5.26.0":
- version "5.26.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.26.0.tgz#cb204bb154d3c103d9cc4d225f311b08219469f3"
- integrity sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA==
+"@typescript-eslint/types@5.27.1":
+ version "5.27.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1"
+ integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==
"@typescript-eslint/typescript-estree@4.33.0":
version "4.33.0"
@@ -6447,13 +6469,13 @@
semver "^7.3.5"
tsutils "^3.21.0"
-"@typescript-eslint/typescript-estree@5.26.0":
- version "5.26.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz#16cbceedb0011c2ed4f607255f3ee1e6e43b88c3"
- integrity sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w==
+"@typescript-eslint/typescript-estree@5.27.1":
+ version "5.27.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808"
+ integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==
dependencies:
- "@typescript-eslint/types" "5.26.0"
- "@typescript-eslint/visitor-keys" "5.26.0"
+ "@typescript-eslint/types" "5.27.1"
+ "@typescript-eslint/visitor-keys" "5.27.1"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
@@ -6461,14 +6483,14 @@
tsutils "^3.21.0"
"@typescript-eslint/utils@^5.10.0":
- version "5.26.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.26.0.tgz#896b8480eb124096e99c8b240460bb4298afcfb4"
- integrity sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg==
+ version "5.27.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f"
+ integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==
dependencies:
"@types/json-schema" "^7.0.9"
- "@typescript-eslint/scope-manager" "5.26.0"
- "@typescript-eslint/types" "5.26.0"
- "@typescript-eslint/typescript-estree" "5.26.0"
+ "@typescript-eslint/scope-manager" "5.27.1"
+ "@typescript-eslint/types" "5.27.1"
+ "@typescript-eslint/typescript-estree" "5.27.1"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
@@ -6480,12 +6502,12 @@
"@typescript-eslint/types" "4.33.0"
eslint-visitor-keys "^2.0.0"
-"@typescript-eslint/visitor-keys@5.26.0":
- version "5.26.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz#7195f756e367f789c0e83035297c45b417b57f57"
- integrity sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q==
+"@typescript-eslint/visitor-keys@5.27.1":
+ version "5.27.1"
+ resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af"
+ integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==
dependencies:
- "@typescript-eslint/types" "5.26.0"
+ "@typescript-eslint/types" "5.27.1"
eslint-visitor-keys "^3.3.0"
"@wry/equality@^0.1.2":
@@ -6648,9 +6670,9 @@ amazon-cognito-identity-js@5.2.9:
js-cookie "^2.2.1"
amplify-appsync-simulator@^2.3.12:
- version "2.3.13"
- resolved "https://registry.npmjs.org/amplify-appsync-simulator/-/amplify-appsync-simulator-2.3.13.tgz#852cfb514d73cc6dc55323b21d2c775b08ed5bae"
- integrity sha512-EUAlVqUknnu7f9o8+wiFR98pHI4tN60pKZ9V/g94wF0lfk97TnywtR0aZCKeX7jaMaH8t59QxFCwyQvz6EBIWw==
+ version "2.4.0"
+ resolved "https://registry.npmjs.org/amplify-appsync-simulator/-/amplify-appsync-simulator-2.4.0.tgz#d0c4b68f4d15c50c64c6b0b2854d724aa330bf93"
+ integrity sha512-PeghZLv+7O7DRyow43qqYFPP9UZxMABpl+/4nCwfMPlr/VF0pSO2Q+RVvv9BOOf/GhEyH+J/ZvUD2tIMmAghDA==
dependencies:
"@graphql-tools/schema" "^8.3.1"
"@graphql-tools/utils" "^8.5.1"
@@ -6684,13 +6706,13 @@ amplify-appsync-simulator@^2.3.12:
ws "^7.5.7"
amplify-category-function@^4.0.3:
- version "4.0.5"
- resolved "https://registry.npmjs.org/amplify-category-function/-/amplify-category-function-4.0.5.tgz#e1e69ca79362562fe3ed3ad81b329fe8d0f2569f"
- integrity sha512-LadWPMIfLzLyXP6aux1ZbTKR38l4+WZBvSgPIgS8PJLPOAc2HPhJB62Ii1KRPMJdvkcodk6OpbKxzmKi36KrtQ==
+ version "4.0.7"
+ resolved "https://registry.npmjs.org/amplify-category-function/-/amplify-category-function-4.0.7.tgz#8d28554125e13caeff2930bb8fc674f23567601b"
+ integrity sha512-rXpuqV3t5Ga0rvEuZmQ41D3UHo82xz+KxM+HyPdJUfKWJij02IMZvx2Kq32MQ4l31ayCkBHzxeynHdpXTMLzdA==
dependencies:
- amplify-cli-core "2.8.0"
+ amplify-cli-core "2.9.0"
amplify-function-plugin-interface "1.9.5"
- amplify-prompts "2.1.0"
+ amplify-prompts "2.2.0"
archiver "^5.3.0"
aws-sdk "^2.1113.0"
chalk "^4.1.1"
@@ -6699,7 +6721,7 @@ amplify-category-function@^4.0.3:
folder-hash "^4.0.2"
fs-extra "^8.1.0"
globby "^11.0.3"
- graphql-transformer-core "^7.5.1"
+ graphql-transformer-core "^7.5.2"
inquirer "^7.3.3"
inquirer-datepicker "^2.0.0"
jstreemap "^1.28.2"
@@ -6709,16 +6731,16 @@ amplify-category-function@^4.0.3:
promise-sequential "^1.1.1"
uuid "^8.3.2"
-amplify-cli-core@2.8.0, amplify-cli-core@^2.6.0:
- version "2.8.0"
- resolved "https://registry.npmjs.org/amplify-cli-core/-/amplify-cli-core-2.8.0.tgz#dbdf0c6cd6ec2da26a30f188a8fe1da43229351a"
- integrity sha512-pcU/SFqYb1VaeyB8wfdaeoRv+LInAMpWLR5E204npuESGZQK3rtKqc5WCwtRNR6b3b2Pma9Q/KOZOP5Fg5IXcg==
+amplify-cli-core@2.9.0, amplify-cli-core@^2.6.0, amplify-cli-core@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.npmjs.org/amplify-cli-core/-/amplify-cli-core-2.9.0.tgz#6995bb17e45d0b8f3b7471300e78f950e9494f82"
+ integrity sha512-9ScW1ahYT0hDIRMOu+rZ454ooo11dNFhH5UYQc6qTUbt12l3AsHDBTylwRSBH7hECd1xQiXqsdmMKhIUlZDtvw==
dependencies:
- "@aws-amplify/graphql-transformer-core" "^0.17.1"
- "@aws-amplify/graphql-transformer-interfaces" "^1.14.1"
+ "@aws-amplify/graphql-transformer-core" "^0.17.2"
+ "@aws-amplify/graphql-transformer-interfaces" "^1.14.2"
ajv "^6.12.6"
- amplify-cli-logger "1.1.3"
- amplify-prompts "2.1.0"
+ amplify-cli-logger "1.2.0"
+ amplify-prompts "2.2.0"
chalk "^4.1.1"
ci-info "^2.0.0"
cloudform-types "^4.2.0"
@@ -6726,7 +6748,7 @@ amplify-cli-core@2.8.0, amplify-cli-core@^2.6.0:
execa "^5.1.1"
fs-extra "^8.1.0"
globby "^11.0.3"
- graphql-transformer-core "^7.5.1"
+ graphql-transformer-core "^7.5.2"
hjson "^3.2.1"
js-yaml "^4.0.0"
lodash "^4.17.21"
@@ -6738,10 +6760,10 @@ amplify-cli-core@2.8.0, amplify-cli-core@^2.6.0:
typescript-json-schema "~0.52.0"
which "^2.0.2"
-amplify-cli-logger@1.1.3:
- version "1.1.3"
- resolved "https://registry.npmjs.org/amplify-cli-logger/-/amplify-cli-logger-1.1.3.tgz#f08c9b0f389442b6ace7226e6d15c35727bc1dae"
- integrity sha512-mYCfmtbmqLfC0AtUhTZDKzq7v7v81RIeyeVgP1neDoAd0/bEzyPN1sSOVv3Y+Z4C2rX30JvbjSEtcNg4evWg/A==
+amplify-cli-logger@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/amplify-cli-logger/-/amplify-cli-logger-1.2.0.tgz#635d22732ac4a6e958d33038ca0b331cfc90e95c"
+ integrity sha512-MidSMWUyWwPtqtkLvmlLe46P36AqY4TRFggoXuTk//wGtN1wfGCuwTI47aoyBhFoHYiXHRIyo/AXd/fLX8iJAQ==
dependencies:
winston "^3.3.3"
winston-daily-rotate-file "^4.5.0"
@@ -6777,17 +6799,17 @@ amplify-function-plugin-interface@1.9.5, amplify-function-plugin-interface@^1.9.
resolved "https://registry.npmjs.org/amplify-function-plugin-interface/-/amplify-function-plugin-interface-1.9.5.tgz#caa95891f7976d29a3d43c51f7c6a372178b9a81"
integrity sha512-x49gI8U1863JC7akkUTQ5WrAhOPD8/PRx9LCwRshl1RJNMH4AaF40Gx+ueDVV/SLAsFc2GM0xoigxsvX+Cu4qQ==
-amplify-headless-interface@^1.14.2:
- version "1.14.3"
- resolved "https://registry.npmjs.org/amplify-headless-interface/-/amplify-headless-interface-1.14.3.tgz#0bf0fb115db3f4756eb8c348fc24112ef398d7e7"
- integrity sha512-L4gKs3G2dEeegbUXa1I+HO91HD6ffSNrO9YC0fX1EuHEprIxLnKghVhyww8ANxi5iMzlyOEkwpi7NMWq1JY2wA==
+amplify-headless-interface@1.15.0, amplify-headless-interface@^1.14.2, amplify-headless-interface@^1.15.0:
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/amplify-headless-interface/-/amplify-headless-interface-1.15.0.tgz#a72d001d11d230ab48844b6e57b5f5c9f1cb030e"
+ integrity sha512-6U22nhKLu67i6/zbBXZO+1TYmLXym+/e9fnwqeOhnctMCybU56RdWvj4/MhQIywn4DyOTHvYBkRT5o4f1hFSPg==
amplify-nodejs-function-runtime-provider@^2.2.28:
- version "2.2.30"
- resolved "https://registry.npmjs.org/amplify-nodejs-function-runtime-provider/-/amplify-nodejs-function-runtime-provider-2.2.30.tgz#15607e0a7b8eff6aaa2957bc8ea23de6f56a0d15"
- integrity sha512-oxZM3VW2ZJh9sYjseODeLwaIKUMzvuZPAo/S59b09WxOWb+EIpGHdAKF6l60N7XmYEMXdO6aSueSGDIprsKCUQ==
+ version "2.2.32"
+ resolved "https://registry.npmjs.org/amplify-nodejs-function-runtime-provider/-/amplify-nodejs-function-runtime-provider-2.2.32.tgz#2e51b1f7932ba0fd13163c86eb3ac2bed637e0db"
+ integrity sha512-epXfAoZXEa96wUGYFyVhqoTn3urhmdl5AbVQTRDzLQWIXuZ/oQrUVYd/prr+y3hVK78G3jBEb9ZpYLIOIp00mA==
dependencies:
- amplify-cli-core "2.8.0"
+ amplify-cli-core "2.9.0"
amplify-function-plugin-interface "1.9.5"
archiver "^5.3.0"
execa "^5.1.1"
@@ -6795,24 +6817,24 @@ amplify-nodejs-function-runtime-provider@^2.2.28:
fs-extra "^8.1.0"
glob "^7.2.0"
-amplify-prompts@2.1.0, amplify-prompts@^2.0.0, amplify-prompts@^2.0.1:
- version "2.1.0"
- resolved "https://registry.npmjs.org/amplify-prompts/-/amplify-prompts-2.1.0.tgz#2d54c700493fc8b40a79edd61040dcc73b80326e"
- integrity sha512-52m7sPmQbh3wdNwzYZldK5eRg2432dx8G5Obak2rSeLmThgYagsKhF622mRIgxq5yc87hyEjAzjifMfLwkKzbQ==
+amplify-prompts@2.2.0, amplify-prompts@^2.0.0, amplify-prompts@^2.0.1, amplify-prompts@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/amplify-prompts/-/amplify-prompts-2.2.0.tgz#288a52ee990eb19a51eed28c20bd43703980ec78"
+ integrity sha512-JZv9GTVz3//s8QlhNBZRuYcUCoxdAEJU6q2tivl2hN0XK3pckeofaXqv6Fi8e9c8NfUyA/u+JVRR7vE4QIMtyw==
dependencies:
amplify-cli-shared-interfaces "1.1.0"
chalk "^4.1.1"
enquirer "^2.3.6"
-amplify-provider-awscloudformation@^6.1.3:
- version "6.2.0"
- resolved "https://registry.npmjs.org/amplify-provider-awscloudformation/-/amplify-provider-awscloudformation-6.2.0.tgz#5f327e3ab798aa03c69d69e1bf114a3bf665d9e9"
- integrity sha512-yPAb1A5/TTKGDgKPpIfmijGYvxZ2SYG9hgoOmSHqy43UOsvYteoonQ18XZN23Jb3LRvsIl2rLjwWsbH5Gekj8w==
+amplify-provider-awscloudformation@^6.1.3, amplify-provider-awscloudformation@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.npmjs.org/amplify-provider-awscloudformation/-/amplify-provider-awscloudformation-6.3.0.tgz#409aac4d4fdbc37bf1470bed53c955d4f8046e01"
+ integrity sha512-WsPWS0hJE159Lvm9U7cDgB/aJz+DbzPgoxkdkKh8muadRP48/FnazJrIZj/pRioQPvexS8R+MjJrTty/Rd7PNQ==
dependencies:
- "@aws-amplify/amplify-category-custom" "2.3.31"
- "@aws-amplify/cli-extensibility-helper" "2.3.27"
- "@aws-amplify/graphql-transformer-core" "^0.17.1"
- "@aws-amplify/graphql-transformer-interfaces" "^1.14.1"
+ "@aws-amplify/amplify-category-custom" "2.3.33"
+ "@aws-amplify/cli-extensibility-helper" "2.3.29"
+ "@aws-amplify/graphql-transformer-core" "^0.17.2"
+ "@aws-amplify/graphql-transformer-interfaces" "^1.14.2"
"@aws-cdk/assert" "~1.124.0"
"@aws-cdk/assets" "~1.124.0"
"@aws-cdk/aws-apigatewayv2" "~1.124.0"
@@ -6854,11 +6876,11 @@ amplify-provider-awscloudformation@^6.1.3:
"@aws-cdk/region-info" "~1.124.0"
"@octokit/rest" "^18.0.9"
ajv "^6.12.6"
- amplify-cli-core "2.8.0"
- amplify-cli-logger "1.1.3"
+ amplify-cli-core "2.9.0"
+ amplify-cli-logger "1.2.0"
amplify-codegen "^3.0.0"
- amplify-prompts "2.1.0"
- amplify-util-import "2.2.30"
+ amplify-prompts "2.2.0"
+ amplify-util-import "2.2.32"
archiver "^5.3.0"
aws-sdk "^2.1113.0"
bottleneck "2.19.5"
@@ -6874,7 +6896,7 @@ amplify-provider-awscloudformation@^6.1.3:
fs-extra "^8.1.0"
glob "^7.2.0"
graphql "^14.5.8"
- graphql-transformer-core "^7.5.1"
+ graphql-transformer-core "^7.5.2"
ignore "^5.2.0"
ini "^1.3.5"
inquirer "^7.3.3"
@@ -6910,12 +6932,20 @@ amplify-storage-simulator@^1.6.7:
xml "^1.0.1"
xml-js "^1.6.11"
-amplify-util-import@2.2.30:
- version "2.2.30"
- resolved "https://registry.npmjs.org/amplify-util-import/-/amplify-util-import-2.2.30.tgz#fc20b0dcf1f8d52086c8cf2c3d79d6a7e5e4b76c"
- integrity sha512-/B4jHOlwGhShRf1LTenwRgNTtyRnZzFzgp11d5S2nzu9pDbruqR1vJVHDmeKgGOU2d1dNZi3yCJ9GDClHgM60A==
+amplify-util-headless-input@^1.9.5:
+ version "1.9.5"
+ resolved "https://registry.npmjs.org/amplify-util-headless-input/-/amplify-util-headless-input-1.9.5.tgz#30cec34338140b1938b7bddf2e7d07095941b58b"
+ integrity sha512-dAXewft+FW80TQ4Iwq/f45jDyNn6rFivGOo6cQd7xIQgKmBPwyz24+/5KL363AwX+gkXFFPizacTgMAqigwjcw==
+ dependencies:
+ ajv "^6.12.6"
+ amplify-headless-interface "1.15.0"
+
+amplify-util-import@2.2.32:
+ version "2.2.32"
+ resolved "https://registry.npmjs.org/amplify-util-import/-/amplify-util-import-2.2.32.tgz#ae984043f8fd946fca968ee5372f6c8c8427c71b"
+ integrity sha512-BJ8+Ffc+SBBBBnYcuoJcf6y2UdIzUgKtIA0Rm2oybLeBZ4wQ2kl3vpU/5KDDJDbCv+RTH2it7KTzs8JmCJKAeg==
dependencies:
- amplify-cli-core "2.8.0"
+ amplify-cli-core "2.9.0"
aws-sdk "^2.1113.0"
amplify-velocity-template@1.4.8:
@@ -7564,9 +7594,9 @@ aws-sdk-mock@^5.6.2:
traverse "^0.6.6"
aws-sdk@2.518.0, aws-sdk@^2.1113.0, aws-sdk@^2.1122.0, aws-sdk@^2.1141.0, aws-sdk@^2.518.0, aws-sdk@^2.848.0, aws-sdk@^2.979.0:
- version "2.1145.0"
- resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1145.0.tgz#9da2e0abb5aed130b0fb33d0452cc468b22d2465"
- integrity sha512-bjZJGFxHJadnp2kbg1etKw7ID1QmmKk1ivML0Xtt6S6GnGSfX8zVuLMkJZaxPMjlyZ6xeilGwzk2F9igxBCPCQ==
+ version "2.1148.0"
+ resolved "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1148.0.tgz#028211e724aee5118223eb5fa65495eaae4b5083"
+ integrity sha512-FUYAyveKmS5eqIiGQgrGVsLZwwtI+K6S6Gz8oJf56pgypZCo9dV+cXO4aaS+vN0+LSmGh6dSKc6G8h8FYASIJg==
dependencies:
buffer "4.9.2"
events "1.1.1"
@@ -7575,7 +7605,7 @@ aws-sdk@2.518.0, aws-sdk@^2.1113.0, aws-sdk@^2.1122.0, aws-sdk@^2.1141.0, aws-sd
querystring "0.2.0"
sax "1.2.1"
url "0.10.3"
- uuid "3.3.2"
+ uuid "8.0.0"
xml2js "0.4.19"
aws-sign2@~0.7.0:
@@ -8171,9 +8201,9 @@ camelcase@^6.0.0, camelcase@^6.2.0:
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001332:
- version "1.0.30001342"
- resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001342.tgz#87152b1e3b950d1fbf0093e23f00b6c8e8f1da96"
- integrity sha512-bn6sOCu7L7jcbBbyNhLg0qzXdJ/PMbybZTH/BA6Roet9wxYRm6Tr9D0s0uhLkOZ6MSG+QU6txUgdpr3MXIVqjA==
+ version "1.0.30001346"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001346.tgz#e895551b46b9cc9cc9de852facd42f04839a8fbe"
+ integrity sha512-q6ibZUO2t88QCIPayP/euuDREq+aMAxFE5S70PkrLh0iTDj/zEhgvJRKC2+CvXY6EWc6oQwUR48lL5vCW6jiXQ==
capital-case@^1.0.4:
version "1.0.4"
@@ -8438,7 +8468,7 @@ clone-deep@^4.0.1:
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
- integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
+ integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
cloudform-types@^4.2.0:
version "4.2.0"
@@ -8470,12 +8500,12 @@ cmd-shim@^4.1.0:
co@^4.6.0:
version "4.6.0"
resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
- integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
+ integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
- integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+ integrity sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==
codecov@^3.7.0:
version "3.8.3"
@@ -8496,7 +8526,7 @@ collect-v8-coverage@^1.0.0:
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
- integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=
+ integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==
dependencies:
map-visit "^1.0.0"
object-visit "^1.0.0"
@@ -8518,7 +8548,7 @@ color-convert@^2.0.1:
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
- integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
@@ -8602,7 +8632,7 @@ common-tags@^1.8.0:
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
- integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
+ integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
compare-func@^2.0.0:
version "2.0.0"
@@ -8630,7 +8660,7 @@ compress-commons@^4.1.0:
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
- integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
concat-stream@^2.0.0:
version "2.0.0"
@@ -8658,7 +8688,7 @@ confusing-browser-globals@^1.0.10:
console-control-strings@^1.0.0, console-control-strings@~1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
- integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+ integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
constant-case@3.0.3:
version "3.0.3"
@@ -8672,7 +8702,7 @@ constant-case@3.0.3:
constant-case@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46"
- integrity sha1-QXV2TTidP6nI7NKRhu1gBSQ7akY=
+ integrity sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==
dependencies:
snake-case "^2.1.0"
upper-case "^1.1.1"
@@ -8687,9 +8717,9 @@ constant-case@^3.0.4:
upper-case "^2.0.2"
constructs@^3.3.125, constructs@^3.3.69:
- version "3.4.16"
- resolved "https://registry.npmjs.org/constructs/-/constructs-3.4.16.tgz#68c4cee94d3f3ebec1d8ec14878259ded122ddee"
- integrity sha512-xHUmfoHrCMoMTz6V1uZ4+JllZhHwQM23F3VEZ5V2Z1itJLwc7YzBOgr1JOPB+pjSu31hePiPostREIW8LoSxlw==
+ version "3.4.29"
+ resolved "https://registry.npmjs.org/constructs/-/constructs-3.4.29.tgz#3e3b8cfe50918fa50f5c9632643335e80394ef7d"
+ integrity sha512-UDtkc4luNV5ixUGEfmVAteZjlcg+HIwb9F//dYsuTXb2P5elDVDl16Mh10TY7Vtx4CUHX3IzxQWLWYxT78unLA==
content-disposition@0.5.4, content-disposition@^0.5.2:
version "0.5.4"
@@ -8804,7 +8834,7 @@ convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0,
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
- integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
+ integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
cookie@0.5.0:
version "0.5.0"
@@ -8819,7 +8849,7 @@ cookie@^0.4.0:
copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
- integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
+ integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==
copyfiles@^2.2.0:
version "2.4.1"
@@ -8835,9 +8865,9 @@ copyfiles@^2.2.0:
yargs "^16.1.0"
core-js-pure@^3.20.2:
- version "3.22.7"
- resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.7.tgz#f58489d9b309fa7b26486a0f70d4ec19a418084e"
- integrity sha512-wTriFxiZI+C8msGeh7fJcbC/a0V8fdInN1oS2eK79DMBGs8iIJiXhtFJCiT3rBa8w6zroHWW3p8ArlujZ/Mz+w==
+ version "3.22.8"
+ resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.22.8.tgz#f2157793b58719196ccf9673cc14f3683adc0957"
+ integrity sha512-bOxbZIy9S5n4OVH63XaLVXZ49QKicjowDx/UELyJ68vxfCRpYsbyh/WNZNfEfAk+ekA8vSjt+gCDpvh672bc3w==
core-js@^2.4.0, core-js@^2.4.1:
version "2.6.12"
@@ -8845,14 +8875,14 @@ core-js@^2.4.0, core-js@^2.4.1:
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-js@^3.6.4:
- version "3.22.7"
- resolved "https://registry.npmjs.org/core-js/-/core-js-3.22.7.tgz#8d6c37f630f6139b8732d10f2c114c3f1d00024f"
- integrity sha512-Jt8SReuDKVNZnZEzyEQT5eK6T2RRCXkfTq7Lo09kpm+fHjgGewSbNjV+Wt4yZMhPDdzz2x1ulI5z/w4nxpBseg==
+ version "3.22.8"
+ resolved "https://registry.npmjs.org/core-js/-/core-js-3.22.8.tgz#23f860b1fe60797cc4f704d76c93fea8a2f60631"
+ integrity sha512-UoGQ/cfzGYIuiq6Z7vWL1HfkE9U9IZ4Ub+0XSiJTCzvbZzgPA69oDF2f+lgJ6dFFLEdjW5O6svvoKzXX23xFkA==
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
- integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+ integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==
core-util-is@~1.0.0:
version "1.0.3"
@@ -8868,12 +8898,12 @@ cors@^2.8.5:
vary "^1"
cosmiconfig-typescript-loader@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-2.0.0.tgz#bc4f5bfcaa11a353714ecdef00c4f2226ef191b8"
- integrity sha512-2NlGul/E3vTQEANqPziqkA01vfiuUU8vT0jZAuUIjEW8u3eCcnCQWLggapCjhbF76s7KQF0fM0kXSKmzaDaG1g==
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-2.0.1.tgz#5622bb1eb87d293570bcc3a57f406940e0960113"
+ integrity sha512-B9s6sX/omXq7I6gC6+YgLmrBFMJhPWew7ty/X5Tuwtd2zOSgWaUdXjkuVwbe3qqcdETo60+1nSVMekq//LIXVA==
dependencies:
cosmiconfig "^7"
- ts-node "^10.7.0"
+ ts-node "^10.8.0"
cosmiconfig@^5.2.1:
version "5.2.1"
@@ -8943,9 +8973,9 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
which "^2.0.1"
cross-undici-fetch@^0.4.0:
- version "0.4.3"
- resolved "https://registry.npmjs.org/cross-undici-fetch/-/cross-undici-fetch-0.4.3.tgz#465d13d4d7d7a1dae75f8ad85aa0ecfa2752e99f"
- integrity sha512-mv1jusEQsFnBHEBkpFaYROKAzAWyuW8ZyN48NcyqkjLGRrscMKuFRmUigUrkE/pdprQZjNTQQ/aWJKe6F4tzTA==
+ version "0.4.5"
+ resolved "https://registry.npmjs.org/cross-undici-fetch/-/cross-undici-fetch-0.4.5.tgz#077bbd299301b0ad517aa7451d17894c931406ef"
+ integrity sha512-3u5LFSPiD5frvhBmU2bH7kv7pa8/WSh3gfwyLsx84oP5mSGttd8eNXU7UofketwKCnCb2gjhCGnVpoUCb1RxDQ==
dependencies:
abort-controller "^3.0.0"
busboy "^1.6.0"
@@ -8958,7 +8988,7 @@ cross-undici-fetch@^0.4.0:
crypt@0.0.2:
version "0.0.2"
resolved "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
- integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
+ integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
crypto-js@^4.1.1:
version "4.1.1"
@@ -8985,7 +9015,7 @@ cssstyle@^2.3.0:
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
- integrity sha1-mI3zP+qxke95mmE2nddsF635V+o=
+ integrity sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==
dependencies:
array-find-index "^1.0.1"
@@ -8997,12 +9027,12 @@ damerau-levenshtein@^1.0.7:
dank-each@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/dank-each/-/dank-each-1.0.0.tgz#a861343a4b28d362203d405c77864dc9a6403730"
- integrity sha1-qGE0Okso02IgPUBcd4ZNyaZANzA=
+ integrity sha512-gMDy24y+3LlnAaHq4WFwRKliMZRkGp41Gy9JVsD1BO5tprb/lEh4afJlkankcTqRoppSaHRwgFQX61QjJ5ClfQ==
dank-map@~0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/dank-map/-/dank-map-0.1.0.tgz#e99e77f382c68f2e5ab2b3f3a82b8031957529a8"
- integrity sha1-6Z5384LGjy5asrPzqCuAMZV1Kag=
+ integrity sha512-mQoLySkWc5bQM8XKXz0rIuISX/+12rSSfPojYlTVT6KPj3LsvfLURtrv0w+QEt1gRIKwp9mxnwOcL5nsOTkk2Q==
dargs@^7.0.0:
version "7.0.0"
@@ -9017,7 +9047,7 @@ dart-style@1.3.2-dev:
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
- integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=
+ integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==
dependencies:
assert-plus "^1.0.0"
@@ -9069,12 +9099,12 @@ debug@^3.2.7:
debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
- integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
+ integrity sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==
decamelize-keys@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
- integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=
+ integrity sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==
dependencies:
decamelize "^1.1.0"
map-obj "^1.0.0"
@@ -9082,7 +9112,7 @@ decamelize-keys@^1.1.0:
decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
- integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
+ integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
decamelize@^5.0.0:
version "5.0.1"
@@ -9097,12 +9127,12 @@ decimal.js@^10.2.1:
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
- integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
+ integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
decompress-response@^3.2.0:
version "3.3.0"
resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
- integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
+ integrity sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==
dependencies:
mimic-response "^1.0.0"
@@ -9145,7 +9175,7 @@ decompress-targz@^4.0.0:
decompress-unzip@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
- integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
+ integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==
dependencies:
file-type "^3.8.0"
get-stream "^2.2.0"
@@ -9169,7 +9199,7 @@ decompress@^4.0.0:
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
- integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
+ integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==
deep-diff@^1.0.2:
version "1.0.2"
@@ -9194,7 +9224,7 @@ deepmerge@4.2.2, deepmerge@^4.2.2:
defaults@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
- integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
+ integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==
dependencies:
clone "^1.0.2"
@@ -9214,14 +9244,14 @@ define-properties@^1.1.3, define-properties@^1.1.4:
define-property@^0.2.5:
version "0.2.5"
resolved "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
- integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=
+ integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==
dependencies:
is-descriptor "^0.1.0"
define-property@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
- integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY=
+ integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==
dependencies:
is-descriptor "^1.0.0"
@@ -9233,7 +9263,7 @@ define-property@^2.0.2:
is-descriptor "^1.0.2"
isobject "^3.0.1"
-degenerator@^3.0.1:
+degenerator@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/degenerator/-/degenerator-3.0.2.tgz#6a61fcc42a702d6e50ff6023fe17bff435f68235"
integrity sha512-c0mef3SNQo56t6urUU6tdQAs+ThoD0o9B9MJ8HEt7NQcGEILCRFqQb7ZbP9JAv+QF1Ky5plydhMR/IrqWDm+TQ==
@@ -9246,12 +9276,12 @@ degenerator@^3.0.1:
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
- integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
- integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+ integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
depd@2.0.0:
version "2.0.0"
@@ -9261,7 +9291,7 @@ depd@2.0.0:
depd@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
- integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+ integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==
dependency-graph@0.8.1:
version "0.8.1"
@@ -9271,7 +9301,7 @@ dependency-graph@0.8.1:
deprecated-decorator@^0.1.6:
version "0.1.6"
resolved "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37"
- integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=
+ integrity sha512-MHidOOnCHGlZDKsI21+mbIIhf4Fff+hhCTB7gtVg4uoIqjcrTZc5v6M+GS2zVI0sV7PqK415rb8XaOSQsQkHOw==
deprecation@^2.0.0, deprecation@^2.3.1:
version "2.3.1"
@@ -9286,14 +9316,14 @@ destroy@1.2.0:
detect-indent@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
- integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
+ integrity sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==
dependencies:
repeating "^2.0.0"
detect-indent@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
- integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
+ integrity sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==
detect-indent@^6.0.0:
version "6.1.0"
@@ -9303,7 +9333,7 @@ detect-indent@^6.0.0:
detect-libc@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
- integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
+ integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==
detect-newline@^3.0.0:
version "3.1.0"
@@ -9359,7 +9389,7 @@ diff@^5.0.0:
difflib@~0.2.1:
version "0.2.4"
resolved "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz#b5e30361a6db023176d562892db85940a718f47e"
- integrity sha1-teMDYabbAjF21WKJLbhZQKcY9H4=
+ integrity sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==
dependencies:
heap ">= 0.2.0"
@@ -9402,7 +9432,7 @@ domexception@^2.0.1:
dot-case@^2.1.0:
version "2.1.1"
resolved "https://registry.npmjs.org/dot-case/-/dot-case-2.1.1.tgz#34dcf37f50a8e93c2b3bca8bb7fb9155c7da3bee"
- integrity sha1-NNzzf1Co6TwrO8qLt/uRVcfaO+4=
+ integrity sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==
dependencies:
no-case "^2.2.0"
@@ -9461,7 +9491,7 @@ download@^6.2.1:
dreamopt@~0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/dreamopt/-/dreamopt-0.6.0.tgz#d813ccdac8d39d8ad526775514a13dda664d6b4b"
- integrity sha1-2BPM2sjTnYrVJndVFKE92mZNa0s=
+ integrity sha512-KRJa47iBEK0y6ZtgCgy2ykuvMT8c9gj3ua9Dv7vCkclFJJeH2FjhGY2xO5qBoWGahsjCGMlk4Cq9wJYeWxuYhQ==
dependencies:
wordwrap ">=0.0.2"
@@ -9473,7 +9503,7 @@ dset@^3.1.0:
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
- integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
+ integrity sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==
duplexer@^0.1.1:
version "0.1.2"
@@ -9493,7 +9523,7 @@ duplexify@^3.5.1:
ecc-jsbn@~0.1.1:
version "0.1.2"
resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
- integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=
+ integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==
dependencies:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
@@ -9508,12 +9538,12 @@ ecdsa-sig-formatter@1.0.11:
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
- integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.4.118:
- version "1.4.137"
- resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.137.tgz#186180a45617283f1c012284458510cd99d6787f"
- integrity sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==
+ version "1.4.146"
+ resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.146.tgz#fd20970c3def2f9e6b32ac13a2e7a6b64e1b0c48"
+ integrity sha512-4eWebzDLd+hYLm4csbyMU2EbBnqhwl8Oe9eF/7CBDPWcRxFmqzx4izxvHH+lofQxzieg8UbB8ZuzNTxeukzfTg==
emittery@^0.7.1:
version "0.7.2"
@@ -9548,7 +9578,7 @@ enabled@2.0.x:
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
- integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encoding@^0.1.12:
version "0.1.13"
@@ -9651,7 +9681,7 @@ es-to-primitive@^1.2.1:
es5-ext@0.8.x:
version "0.8.2"
resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.8.2.tgz#aba8d9e1943a895ac96837a62a39b3f55ecd94ab"
- integrity sha1-q6jZ4ZQ6iVrJaDemKjmz9V7NlKs=
+ integrity sha512-H19ompyhnKiBdjHR1DPHvf5RHgHPmJaY9JNzFGbMbPgdsUkvnUCN1Ke8J4Y0IMyTwFM2M9l4h2GoHwzwpSmXbA==
escalade@^3.1.1:
version "3.1.1"
@@ -9661,12 +9691,12 @@ escalade@^3.1.1:
escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
- integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
- integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
escape-string-regexp@^2.0.0:
version "2.0.0"
@@ -9767,9 +9797,9 @@ eslint-plugin-import@^2.22.1:
tsconfig-paths "^3.14.1"
eslint-plugin-jest@^26.1.1:
- version "26.2.2"
- resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.2.2.tgz#74e000544259f1ef0462a609a3fc9e5da3768f6c"
- integrity sha512-etSFZ8VIFX470aA6kTqDPhIq7YWe0tjBcboFNV3WeiC18PJ/AVonGhuTwlmuz2fBkH8FJHA7JQ4k7GsQIj1Gew==
+ version "26.5.3"
+ resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.5.3.tgz#a3ceeaf4a757878342b8b00eca92379b246e5505"
+ integrity sha512-sICclUqJQnR1bFRZGLN2jnSVsYOsmPYYnroGCIMVSvTS3y8XR3yjzy1EcTQmk6typ5pRgyIWzbjqxK6cZHEZuQ==
dependencies:
"@typescript-eslint/utils" "^5.10.0"
@@ -9973,7 +10003,7 @@ esutils@^2.0.2:
etag@^1.8.1, etag@~1.8.1:
version "1.8.1"
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
- integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
event-target-shim@^5.0.0:
version "5.0.1"
@@ -9988,7 +10018,7 @@ eventemitter3@^4.0.4:
events@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
- integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
+ integrity sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==
events@3.3.0, events@^3.1.0:
version "3.3.0"
@@ -10046,12 +10076,12 @@ execa@^5.0.0, execa@^5.1.1:
exit@^0.1.2:
version "0.1.2"
resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
- integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=
+ integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
expand-brackets@^2.1.4:
version "2.1.4"
resolved "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
- integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI=
+ integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==
dependencies:
debug "^2.3.3"
define-property "^0.2.5"
@@ -10133,14 +10163,14 @@ ext-name@^5.0.0:
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
- integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=
+ integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==
dependencies:
is-extendable "^0.1.0"
extend-shallow@^3.0.0, extend-shallow@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
- integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=
+ integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==
dependencies:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
@@ -10192,7 +10222,7 @@ extract-zip@^2.0.1:
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
- integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
+ integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==
extsprintf@^1.2.0:
version "1.4.1"
@@ -10240,7 +10270,7 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
- integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-redact@^3.0.0:
version "3.1.1"
@@ -10255,7 +10285,7 @@ fast-safe-stringify@^2.0.8:
fast-url-parser@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
- integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=
+ integrity sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==
dependencies:
punycode "^1.3.2"
@@ -10274,7 +10304,7 @@ fast-xml-parser@^3.16.0:
fastfall@^1.5.0:
version "1.5.1"
resolved "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz#3fee03331a49d1d39b3cdf7a5e9cd66f475e7b94"
- integrity sha1-P+4DMxpJ0dObPN96XpzWb0dee5Q=
+ integrity sha512-KH6p+Z8AKPXnmA7+Iz2Lh8ARCMr+8WNPVludm1LGkZoD2MjY6LVnRMtTKhkdzI+jr0RzQWXKzKyBJm1zoHEL4Q==
dependencies:
reusify "^1.0.0"
@@ -10296,7 +10326,7 @@ fastq@^1.3.0, fastq@^1.6.0:
fastseries@^1.7.0:
version "1.7.2"
resolved "https://registry.npmjs.org/fastseries/-/fastseries-1.7.2.tgz#d22ce13b9433dff3388d91dbd6b8bda9b21a0f4b"
- integrity sha1-0izhO5Qz3/M4jZHb1ri9qbIaD0s=
+ integrity sha512-dTPFrPGS8SNSzAt7u/CbMKCJ3s01N04s4JFbORHcmyvVfVKmbhMD1VtRbh5enGHxkaQDqWyLefiKOGGmohGDDQ==
dependencies:
reusify "^1.0.0"
xtend "^4.0.0"
@@ -10330,7 +10360,7 @@ fbjs@^1.0.0:
fd-slicer@~1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
- integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+ integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
dependencies:
pend "~1.2.0"
@@ -10342,7 +10372,7 @@ fecha@^4.2.0:
figures@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
- integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
+ integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==
dependencies:
escape-string-regexp "^1.0.5"
@@ -10370,12 +10400,12 @@ file-stream-rotator@^0.6.1:
file-type@5.2.0, file-type@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
- integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
+ integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==
file-type@^3.8.0:
version "3.9.0"
resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
- integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
+ integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==
file-type@^6.1.0:
version "6.2.0"
@@ -10390,7 +10420,7 @@ file-uri-to-path@2:
filename-reserved-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
- integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik=
+ integrity sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==
filenamify@^2.0.0:
version "2.1.0"
@@ -10404,7 +10434,7 @@ filenamify@^2.0.0:
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
- integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=
+ integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==
dependencies:
extend-shallow "^2.0.1"
is-number "^3.0.0"
@@ -10421,7 +10451,7 @@ fill-range@^7.0.1:
filter-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
- integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs=
+ integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==
finalhandler@1.2.0:
version "1.2.0"
@@ -10448,7 +10478,7 @@ find-cache-dir@^3.3.1:
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
- integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=
+ integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==
dependencies:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
@@ -10456,7 +10486,7 @@ find-up@^1.0.0:
find-up@^2.0.0, find-up@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
- integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
+ integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==
dependencies:
locate-path "^2.0.0"
@@ -10516,19 +10546,19 @@ folder-hash@^4.0.2:
minimatch "~5.0.0"
follow-redirects@^1.14.0, follow-redirects@^1.14.8:
- version "1.15.0"
- resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4"
- integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==
+ version "1.15.1"
+ resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
+ integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
- integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
+ integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
- integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
+ integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==
form-data-encoder@^1.7.1:
version "1.7.2"
@@ -10569,19 +10599,19 @@ forwarded@0.2.0:
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
- integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=
+ integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==
dependencies:
map-cache "^0.2.2"
fresh@0.5.2:
version "0.5.2"
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
- integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+ integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
from2@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
- integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
+ integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==
dependencies:
inherits "^2.0.1"
readable-stream "^2.0.0"
@@ -10641,7 +10671,7 @@ fs-readdir-recursive@^1.1.0:
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
- integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@^2.1.2, fsevents@~2.3.2:
version "2.3.2"
@@ -10651,7 +10681,7 @@ fsevents@^2.1.2, fsevents@~2.3.2:
ftp@^0.3.10:
version "0.3.10"
resolved "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d"
- integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=
+ integrity sha512-faFVML1aBx2UoDStmLwv2Wptt4vw5x03xxX172nhA5Y5HBshW5JweqQ2W4xL4dezQTG8inJsuYcpPHHU3X5OTQ==
dependencies:
readable-stream "1.1.x"
xregexp "2.0.0"
@@ -10674,7 +10704,7 @@ function.prototype.name@^1.1.5:
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
- integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
+ integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==
functions-have-names@^1.2.2:
version "1.2.3"
@@ -10684,7 +10714,7 @@ functions-have-names@^1.2.2:
gauge@~2.7.3:
version "2.7.4"
resolved "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
- integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
+ integrity sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==
dependencies:
aproba "^1.0.3"
console-control-strings "^1.0.0"
@@ -10744,7 +10774,7 @@ get-proxy@^2.0.0:
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
- integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
+ integrity sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==
get-stdin@^7.0.0:
version "7.0.0"
@@ -10754,7 +10784,7 @@ get-stdin@^7.0.0:
get-stream@^2.2.0:
version "2.3.1"
resolved "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
- integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
+ integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==
dependencies:
object-assign "^4.0.1"
pinkie-promise "^2.0.0"
@@ -10762,7 +10792,7 @@ get-stream@^2.2.0:
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
- integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+ integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==
get-stream@^4.0.0:
version "4.1.0"
@@ -10806,12 +10836,12 @@ get-uri@3:
get-value@^2.0.3, get-value@^2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
- integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
+ integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==
getpass@^0.1.1:
version "0.1.7"
resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
- integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
+ integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==
dependencies:
assert-plus "^1.0.0"
@@ -10829,7 +10859,7 @@ git-raw-commits@^2.0.0, git-raw-commits@^2.0.8:
git-remote-origin-url@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f"
- integrity sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=
+ integrity sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==
dependencies:
gitconfiglocal "^1.0.0"
pify "^2.3.0"
@@ -10860,14 +10890,14 @@ git-url-parse@^11.4.4:
gitconfiglocal@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b"
- integrity sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=
+ integrity sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==
dependencies:
ini "^1.3.2"
github-from-package@0.0.0:
version "0.0.0"
resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
- integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
+ integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
glob-all@^3.1.0:
version "3.3.0"
@@ -10887,7 +10917,7 @@ glob-parent@^3.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@^6.0.2,
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
- integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
+ integrity sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig==
glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0:
version "7.2.3"
@@ -10901,10 +10931,10 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, gl
once "^1.3.0"
path-is-absolute "^1.0.0"
-global-dirs@^0.1.1:
+global-dirs@^0.1.0, global-dirs@^0.1.1:
version "0.1.1"
resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
- integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=
+ integrity sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==
dependencies:
ini "^1.3.4"
@@ -11055,7 +11085,7 @@ graphql@^14.5.8:
growly@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
- integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
+ integrity sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==
handlebars@4.7.7, handlebars@^4.7.7:
version "4.7.7"
@@ -11072,7 +11102,7 @@ handlebars@4.7.7, handlebars@^4.7.7:
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
- integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
+ integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==
har-validator@~5.1.3:
version "5.1.5"
@@ -11095,7 +11125,7 @@ has-bigints@^1.0.1, has-bigints@^1.0.2:
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
- integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+ integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
has-flag@^4.0.0:
version "4.0.0"
@@ -11136,12 +11166,12 @@ has-tostringtag@^1.0.0:
has-unicode@^2.0.0, has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
- integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+ integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
- integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=
+ integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==
dependencies:
get-value "^2.0.3"
has-values "^0.1.4"
@@ -11150,7 +11180,7 @@ has-value@^0.3.1:
has-value@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
- integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=
+ integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==
dependencies:
get-value "^2.0.6"
has-values "^1.0.0"
@@ -11159,12 +11189,12 @@ has-value@^1.0.0:
has-values@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
- integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E=
+ integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==
has-values@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
- integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=
+ integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==
dependencies:
is-number "^3.0.0"
kind-of "^4.0.0"
@@ -11179,7 +11209,7 @@ has@^1.0.3:
header-case@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/header-case/-/header-case-1.0.1.tgz#9535973197c144b09613cd65d317ef19963bd02d"
- integrity sha1-lTWXMZfBRLCWE81l0xfvGZY70C0=
+ integrity sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==
dependencies:
no-case "^2.2.0"
upper-case "^1.1.3"
@@ -11210,7 +11240,7 @@ hjson@^3.2.1:
hogan.js@3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
- integrity sha1-TNnhq9QpQUbnZ55B14mHMrAse/0=
+ integrity sha512-RqGs4wavGYJWE07t35JQccByczmNUXQT0E12ZYV1VKYu5UiAU9lsos/yBAcf840+zrUQQxgVduCR5/B8nNtibg==
dependencies:
mkdirp "0.3.0"
nopt "1.0.10"
@@ -11267,7 +11297,7 @@ http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1:
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
- integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
+ integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==
dependencies:
assert-plus "^1.0.0"
jsprim "^1.2.2"
@@ -11294,14 +11324,14 @@ human-signals@^2.1.0:
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
- integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=
+ integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==
dependencies:
ms "^2.0.0"
hunspell-spellchecker@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/hunspell-spellchecker/-/hunspell-spellchecker-1.0.2.tgz#a10b0bd2fa00a65ab62a4c6b734ce496d318910e"
- integrity sha1-oQsL0voAplq2Kkxrc0zkltMYkQ4=
+ integrity sha512-4DwmFAvlz+ChsqLDsZT2cwBsYNXh+oWboemxXtafwKIyItq52xfR4e4kr017sLAoPaSYVofSOvPUfmOAhXyYvw==
husky@^3.0.3:
version "3.1.0"
@@ -11379,12 +11409,12 @@ immutable-tuple@^0.4.9:
immutable@~3.7.6:
version "3.7.6"
resolved "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
- integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks=
+ integrity sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==
import-fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
- integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
+ integrity sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==
dependencies:
caller-path "^2.0.0"
resolve-from "^3.0.0"
@@ -11397,7 +11427,7 @@ import-fresh@^3.0.0, import-fresh@^3.2.1:
parent-module "^1.0.0"
resolve-from "^4.0.0"
-import-from@3.0.0:
+import-from@3.0.0, import-from@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966"
integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==
@@ -11409,6 +11439,13 @@ import-from@4.0.0:
resolved "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz#2710b8d66817d232e16f4166e319248d3d5492e2"
integrity sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==
+import-global@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmjs.org/import-global/-/import-global-0.1.0.tgz#97b38fd444114eec16824a935f8da575b57aa1ce"
+ integrity sha512-8+hPJLML+m1ym9NSeZXTXFkY5+ml0fYFAzO5yhZiaFQvk9kO0NkE7vd7e7kCVjkTmAxsDPbrWwLQACMwGTDgIg==
+ dependencies:
+ global-dirs "^0.1.0"
+
import-local@^3.0.2:
version "3.1.0"
resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
@@ -11420,12 +11457,12 @@ import-local@^3.0.2:
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
- integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
indent-string@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
- integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
+ integrity sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==
dependencies:
repeating "^2.0.0"
@@ -11447,7 +11484,7 @@ inflected@^2.0.4:
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
- integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
dependencies:
once "^1.3.0"
wrappy "1"
@@ -11554,7 +11591,7 @@ ipaddr.js@1.9.1:
is-accessor-descriptor@^0.1.6:
version "0.1.6"
resolved "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
- integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=
+ integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==
dependencies:
kind-of "^3.0.2"
@@ -11568,7 +11605,7 @@ is-accessor-descriptor@^1.0.0:
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
- integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
+ integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
is-arrayish@^0.3.1:
version "0.3.2"
@@ -11624,7 +11661,7 @@ is-core-module@2.9.0, is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-modu
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
- integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=
+ integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==
dependencies:
kind-of "^3.0.2"
@@ -11663,7 +11700,7 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
is-directory@^0.3.1:
version "0.3.1"
resolved "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
- integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
+ integrity sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
@@ -11673,7 +11710,7 @@ is-docker@^2.0.0, is-docker@^2.1.1:
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
- integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=
+ integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==
is-extendable@^1.0.1:
version "1.0.1"
@@ -11685,7 +11722,7 @@ is-extendable@^1.0.1:
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
- integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-finite@^1.0.0:
version "1.1.0"
@@ -11695,14 +11732,14 @@ is-finite@^1.0.0:
is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
- integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
+ integrity sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==
dependencies:
number-is-nan "^1.0.0"
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
- integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+ integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
@@ -11729,19 +11766,19 @@ is-interactive@^1.0.0:
is-lambda@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5"
- integrity sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=
+ integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==
is-lower-case@^1.1.0:
version "1.1.3"
resolved "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393"
- integrity sha1-fhR75HaNxGbbO/shzGCzHmrWk5M=
+ integrity sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==
dependencies:
lower-case "^1.1.0"
is-natural-number@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
- integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
+ integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==
is-negative-zero@^2.0.2:
version "2.0.2"
@@ -11758,7 +11795,7 @@ is-number-object@^1.0.4:
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
- integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=
+ integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==
dependencies:
kind-of "^3.0.2"
@@ -11780,7 +11817,7 @@ is-object@^1.0.1:
is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
- integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
+ integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==
is-plain-obj@^2.0.0:
version "2.1.0"
@@ -11834,7 +11871,7 @@ is-ssh@^1.3.0:
is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
- integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+ integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
is-stream@^2.0.0:
version "2.0.1"
@@ -11858,26 +11895,26 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
is-text-path@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e"
- integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=
+ integrity sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==
dependencies:
text-extensions "^1.0.0"
is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
- integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
+ integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
is-upper-case@^1.1.0:
version "1.1.2"
resolved "https://registry.npmjs.org/is-upper-case/-/is-upper-case-1.1.2.tgz#8d0b1fa7e7933a1e58483600ec7d9661cbaf756f"
- integrity sha1-jQsfp+eTOh5YSDYA7H2WYcuvdW8=
+ integrity sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==
dependencies:
upper-case "^1.1.0"
is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
- integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
+ integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==
is-weakref@^1.0.2:
version "1.0.2"
@@ -11901,34 +11938,34 @@ is-wsl@^2.2.0:
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
- integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+ integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
- integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+ integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
- integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
isobject@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
- integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
+ integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==
dependencies:
isarray "1.0.0"
isobject@^3.0.0, isobject@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
- integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
+ integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
isomorphic-fetch@^2.1.1:
version "2.2.1"
resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
- integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=
+ integrity sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==
dependencies:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
@@ -11957,7 +11994,7 @@ isomorphic-ws@^4.0.1:
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
- integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
+ integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
version "3.2.0"
@@ -12457,7 +12494,7 @@ js-cookie@^2.2.1:
js-string-escape@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
- integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=
+ integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
@@ -12482,7 +12519,7 @@ js-yaml@^4.0.0:
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
- integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+ integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
jsdoc-type-pratt-parser@~2.2.3:
version "2.2.5"
@@ -12525,7 +12562,7 @@ jsdom@^16.4.0:
jsesc@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
- integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s=
+ integrity sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==
jsesc@^2.5.1:
version "2.5.2"
@@ -12569,12 +12606,12 @@ json-schema@0.4.0:
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
- integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
- integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+ integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==
json5@2.x, json5@^2.1.2, json5@^2.2.1:
version "2.2.1"
@@ -12591,7 +12628,7 @@ json5@^1.0.1:
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
- integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
+ integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
optionalDependencies:
graceful-fs "^4.1.6"
@@ -12612,7 +12649,7 @@ jsonminify@^0.4.1:
jsonparse@^1.2.0, jsonparse@^1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
- integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
+ integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
jsonschema@^1.4.0:
version "1.4.1"
@@ -12683,19 +12720,19 @@ jws@^3.2.2:
jwt-decode@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
- integrity sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=
+ integrity sha512-86GgN2vzfUu7m9Wcj63iUkuDzFNYFVmjeDm2GzWpUk+opB0pEpMsw6ePCMrhYkumz2C1ihqtZzOMAg7FiXcNoQ==
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
- integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
+ integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==
dependencies:
is-buffer "^1.1.5"
kind-of@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
- integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc=
+ integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==
dependencies:
is-buffer "^1.1.5"
@@ -12727,7 +12764,7 @@ language-subtag-registry@~0.3.2:
language-tags@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a"
- integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=
+ integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==
dependencies:
language-subtag-registry "~0.3.2"
@@ -12778,7 +12815,7 @@ levn@^0.4.1:
levn@~0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
- integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
+ integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==
dependencies:
prelude-ls "~1.1.2"
type-check "~0.3.2"
@@ -12817,7 +12854,7 @@ lines-and-columns@^1.1.6:
lnk@1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/lnk/-/lnk-1.1.0.tgz#acee59b91ca9a0a8fc12a3dbc53b9edc5f353dd7"
- integrity sha1-rO5ZuRypoKj8EqPbxTue3F81Pdc=
+ integrity sha512-m3q+8W+stYiJYCBwefzrU+inN9h2KxofWuxhNH1iS+MdG9MIKfDpBtbmmIKstVwussx48PXALwonyzuzZUXjQQ==
dependencies:
arrify "^1.0.1"
mkdirp "^0.5.0"
@@ -12827,7 +12864,7 @@ lnk@1.1.0:
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
- integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
+ integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==
dependencies:
graceful-fs "^4.1.2"
parse-json "^2.2.0"
@@ -12838,7 +12875,7 @@ load-json-file@^1.0.0:
load-json-file@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
- integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs=
+ integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==
dependencies:
graceful-fs "^4.1.2"
parse-json "^4.0.0"
@@ -12867,7 +12904,7 @@ loader-utils@^2.0.0:
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
- integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
+ integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==
dependencies:
p-locate "^2.0.0"
path-exists "^3.0.0"
@@ -12902,62 +12939,62 @@ lodash-es@^4.17.4, lodash-es@^4.2.1:
lodash._reinterpolate@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
- integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
+ integrity sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==
lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
- integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
+ integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
lodash.difference@^4.5.0:
version "4.5.0"
resolved "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c"
- integrity sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=
+ integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
- integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
+ integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
- integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
+ integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
- integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
+ integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
- integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
+ integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
- integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
+ integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
lodash.ismatch@^4.4.0:
version "4.4.0"
resolved "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37"
- integrity sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=
+ integrity sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
- integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
+ integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
- integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
+ integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
- integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
+ integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
lodash.merge@^4.6.2:
version "4.6.2"
@@ -12967,7 +13004,7 @@ lodash.merge@^4.6.2:
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
- integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
+ integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lodash.template@^4.5.0:
version "4.5.0"
@@ -12987,17 +13024,17 @@ lodash.templatesettings@^4.0.0:
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
- integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
+ integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
lodash.truncate@^4.4.2:
version "4.4.2"
resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
- integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
+ integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==
lodash.union@^4.6.0:
version "4.6.0"
resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
- integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
+ integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==
lodash@4.17.15, lodash@4.x, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.7.0, lodash@~4.17.0:
version "4.17.21"
@@ -13039,7 +13076,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
loud-rejection@^1.0.0:
version "1.6.0"
resolved "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
- integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
+ integrity sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==
dependencies:
currently-unhandled "^0.4.1"
signal-exit "^3.0.0"
@@ -13047,7 +13084,7 @@ loud-rejection@^1.0.0:
lower-case-first@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/lower-case-first/-/lower-case-first-1.0.2.tgz#e5da7c26f29a7073be02d52bac9980e5922adfa1"
- integrity sha1-5dp8JvKacHO+AtUrrJmA5ZIq36E=
+ integrity sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==
dependencies:
lower-case "^1.1.2"
@@ -13068,7 +13105,7 @@ lower-case@2.0.1:
lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2:
version "1.1.4"
resolved "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
- integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
+ integrity sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==
lower-case@^2.0.2:
version "2.0.2"
@@ -13176,12 +13213,12 @@ makeerror@1.0.12:
map-cache@^0.2.2:
version "0.2.2"
resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
- integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
+ integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==
map-obj@^1.0.0, map-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
- integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
+ integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==
map-obj@^4.0.0:
version "4.3.0"
@@ -13191,7 +13228,7 @@ map-obj@^4.0.0:
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
- integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=
+ integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==
dependencies:
object-visit "^1.0.0"
@@ -13207,12 +13244,12 @@ md5@^2.2.1, md5@^2.3.0:
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
- integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
meow@^3.3.0:
version "3.7.0"
resolved "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
- integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
+ integrity sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==
dependencies:
camelcase-keys "^2.0.0"
decamelize "^1.1.2"
@@ -13263,7 +13300,7 @@ meow@^9.0.0:
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
- integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+ integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
merge-stream@^2.0.0:
version "2.0.0"
@@ -13283,7 +13320,7 @@ meros@^1.1.4:
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
- integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
@@ -13496,7 +13533,7 @@ mkdirp-infer-owner@^2.0.0:
mkdirp@0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
- integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
+ integrity sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==
mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
@@ -13555,7 +13592,7 @@ mqtt-packet@^6.0.0:
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
- integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
ms@2.1.2:
version "2.1.2"
@@ -13589,7 +13626,7 @@ multistream@^4.1.0:
mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
- integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
+ integrity sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==
mute-stream@0.0.8, mute-stream@~0.0.4:
version "0.0.8"
@@ -13597,9 +13634,9 @@ mute-stream@0.0.8, mute-stream@~0.0.4:
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nan@^2.14.0:
- version "2.15.0"
- resolved "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
- integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
+ version "2.16.0"
+ resolved "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916"
+ integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==
nanoid@^3.3.1:
version "3.3.4"
@@ -13631,7 +13668,7 @@ napi-build-utils@^1.0.1:
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
- integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
negotiator@0.6.3, negotiator@^0.6.2:
version "0.6.3"
@@ -13643,7 +13680,7 @@ neo-async@^2.6.0:
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
-netmask@^2.0.1, netmask@^2.0.2:
+netmask@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
@@ -13734,7 +13771,7 @@ node-gyp@^7.1.0:
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
- integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
+ integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
node-notifier@^8.0.0:
version "8.0.2"
@@ -13763,7 +13800,7 @@ node-releases@^2.0.3:
noms@0.0.0:
version "0.0.0"
resolved "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
- integrity sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=
+ integrity sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==
dependencies:
inherits "^2.0.1"
readable-stream "~1.0.31"
@@ -13771,7 +13808,7 @@ noms@0.0.0:
nopt@1.0.10:
version "1.0.10"
resolved "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
- integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
+ integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==
dependencies:
abbrev "1"
@@ -13813,7 +13850,7 @@ normalize-package-data@^3.0.0, normalize-package-data@^3.0.2:
normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
- integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
+ integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==
dependencies:
remove-trailing-separator "^1.0.1"
@@ -13926,7 +13963,7 @@ npm-registry-fetch@^9.0.0:
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
- integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
+ integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==
dependencies:
path-key "^2.0.0"
@@ -13962,7 +13999,7 @@ nullthrows@^1.1.1:
number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
- integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+ integrity sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==
nwsapi@^2.2.0:
version "2.2.0"
@@ -13977,12 +14014,12 @@ oauth-sign@~0.9.0:
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
- integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
object-copy@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
- integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw=
+ integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==
dependencies:
copy-descriptor "^0.1.0"
define-property "^0.2.5"
@@ -13999,9 +14036,9 @@ object-hash@^3.0.0:
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
object-inspect@^1.12.0, object-inspect@^1.9.0:
- version "1.12.1"
- resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.1.tgz#28a661153bad7e470e4b01479ef1cb91ce511191"
- integrity sha512-Y/jF6vnvEtOPGiKD1+q+X0CiUYRQtEHp89MLLUJ7TUivtH8Ugn2+3A7Rynqk7BRsAoqeOQWnFnjpDrKSxDgIGA==
+ version "1.12.2"
+ resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
+ integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
object-keys@^1.1.1:
version "1.1.1"
@@ -14020,7 +14057,7 @@ object-to-xml@^2.0.0:
object-visit@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
- integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=
+ integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==
dependencies:
isobject "^3.0.0"
@@ -14073,7 +14110,7 @@ object.hasown@^1.1.1:
object.pick@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
- integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=
+ integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==
dependencies:
isobject "^3.0.1"
@@ -14096,7 +14133,7 @@ on-finished@2.4.1:
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
- integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
@@ -14110,7 +14147,7 @@ one-time@^1.0.0:
onetime@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
- integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
+ integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==
dependencies:
mimic-fn "^1.0.0"
@@ -14183,12 +14220,12 @@ ora@^4.0.3:
os-homedir@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
- integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
+ integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==
os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
- integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+ integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
osenv@^0.1.4:
version "0.1.5"
@@ -14211,14 +14248,14 @@ p-each-series@^2.1.0:
p-event@^1.0.0:
version "1.3.0"
resolved "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085"
- integrity sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU=
+ integrity sha512-hV1zbA7gwqPVFcapfeATaNjQ3J0NuzorHPyG8GPL9g/Y/TplWVBVoCKCXL6Ej2zscrCEv195QNWJXuBH6XZuzA==
dependencies:
p-timeout "^1.1.1"
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
- integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+ integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
p-is-promise@^3.0.0:
version "3.0.0"
@@ -14249,7 +14286,7 @@ p-limit@^2.0.0, p-limit@^2.2.0:
p-locate@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
- integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
+ integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==
dependencies:
p-limit "^1.1.0"
@@ -14307,7 +14344,7 @@ p-reduce@^2.0.0, p-reduce@^2.1.0:
p-timeout@^1.1.1:
version "1.2.1"
resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386"
- integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=
+ integrity sha512-gb0ryzr+K2qFqFv6qi3khoeqMZF/+ajxQipEF6NteZVnvz9tzdsfAVj3lYtn1gAXvH5lfLwfxEII799gt/mRIA==
dependencies:
p-finally "^1.0.0"
@@ -14321,7 +14358,7 @@ p-timeout@^3.2.0:
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
- integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
+ integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==
p-try@^2.0.0:
version "2.2.0"
@@ -14351,13 +14388,13 @@ pac-proxy-agent@^5.0.0:
socks-proxy-agent "5"
pac-resolver@^5.0.0:
- version "5.0.0"
- resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0"
- integrity sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA==
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-5.0.1.tgz#c91efa3a9af9f669104fa2f51102839d01cde8e7"
+ integrity sha512-cy7u00ko2KVgBAjuhevqpPeHIkCIqPe1v24cydhWjmeuzaBfmUWFCZJ1iAh5TuVzVZoUzXIW7K8sMYOZ84uZ9Q==
dependencies:
- degenerator "^3.0.1"
+ degenerator "^3.0.2"
ip "^1.1.5"
- netmask "^2.0.1"
+ netmask "^2.0.2"
pacote@^11.2.6:
version "11.3.5"
@@ -14400,7 +14437,7 @@ param-case@3.0.3:
param-case@^2.1.0:
version "2.1.1"
resolved "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
- integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc=
+ integrity sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==
dependencies:
no-case "^2.2.0"
@@ -14422,14 +14459,14 @@ parent-module@^1.0.0:
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
- integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
+ integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==
dependencies:
error-ex "^1.2.0"
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
- integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=
+ integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==
dependencies:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
@@ -14485,7 +14522,7 @@ pascal-case@3.1.1:
pascal-case@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-2.0.1.tgz#2d578d3455f660da65eca18ef95b4e0de912761e"
- integrity sha1-LVeNNFX2YNpl7KGO+VtODekSdh4=
+ integrity sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==
dependencies:
camel-case "^3.0.0"
upper-case-first "^1.1.0"
@@ -14501,12 +14538,12 @@ pascal-case@^3.1.1, pascal-case@^3.1.2:
pascalcase@^0.1.1:
version "0.1.1"
resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
- integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
+ integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==
path-case@^2.1.0:
version "2.1.1"
resolved "https://registry.npmjs.org/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5"
- integrity sha1-lLgDfDctP+KQbkZbtF4l0ibo7qU=
+ integrity sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==
dependencies:
no-case "^2.2.0"
@@ -14521,14 +14558,14 @@ path-case@^3.0.4:
path-exists@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
- integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=
+ integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==
dependencies:
pinkie-promise "^2.0.0"
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
- integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
+ integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==
path-exists@^4.0.0:
version "4.0.0"
@@ -14538,12 +14575,12 @@ path-exists@^4.0.0:
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
- integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
- integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
+ integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
@@ -14558,7 +14595,7 @@ path-parse@^1.0.6, path-parse@^1.0.7:
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
- integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+ integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path-to-regexp@^1.7.0:
version "1.8.0"
@@ -14570,7 +14607,7 @@ path-to-regexp@^1.7.0:
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
- integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
+ integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==
dependencies:
graceful-fs "^4.1.2"
pify "^2.0.0"
@@ -14591,12 +14628,12 @@ path-type@^4.0.0:
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
- integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
+ integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
- integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
+ integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
picocolors@^1.0.0:
version "1.0.0"
@@ -14611,12 +14648,12 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
pify@^2.0.0, pify@^2.3, pify@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
- integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+ integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
pify@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
- integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+ integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
pify@^4.0.1:
version "4.0.1"
@@ -14631,14 +14668,14 @@ pify@^5.0.0:
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
- integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
+ integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
- integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
+ integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
pino-std-serializers@^3.1.0:
version "3.2.0"
@@ -14719,7 +14756,7 @@ pluralize@8.0.0, pluralize@^8.0.0:
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
- integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
+ integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==
prebuild-install@6.1.4:
version "6.1.4"
@@ -14748,12 +14785,12 @@ prelude-ls@^1.2.1:
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
- integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
+ integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
prepend-http@^1.0.1:
version "1.0.4"
resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
- integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
+ integrity sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==
prettier@^1.19.1:
version "1.19.1"
@@ -14788,7 +14825,7 @@ progress@^2.0.0, progress@^2.0.3:
promise-inflight@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
- integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
+ integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==
promise-retry@^2.0.1:
version "2.0.1"
@@ -14801,7 +14838,7 @@ promise-retry@^2.0.1:
promise-sequential@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/promise-sequential/-/promise-sequential-1.1.1.tgz#f79e8950ef86e7a7a85bf320452643592f6d2fb2"
- integrity sha1-956JUO+G56eoW/MgRSZDWS9tL7I=
+ integrity sha512-gpziThEJPnPEeaHNqjy9f4PE1JwXb07fX3dz+41nhALpIAaEI9VZU8Q89eyBBAa2xAhnz0KgUTDtCA7FZYbZQQ==
promise-toolbox@^0.20.0:
version "0.20.0"
@@ -14835,7 +14872,7 @@ prompts@^2.0.1:
promzard@^0.3.0:
version "0.3.0"
resolved "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
- integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=
+ integrity sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw==
dependencies:
read "1"
@@ -14851,7 +14888,7 @@ prop-types@^15.8.1:
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
- integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
+ integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
protocols@^1.1.0, protocols@^1.4.0:
version "1.4.8"
@@ -14901,12 +14938,12 @@ pump@^3.0.0:
punycode@1.3.2:
version "1.3.2"
resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
- integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
+ integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
punycode@^1.3.2:
version "1.4.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
- integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
+ integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
@@ -14916,7 +14953,7 @@ punycode@^2.1.0, punycode@^2.1.1:
q@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
- integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
+ integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==
qlobber@3.1.0:
version "3.1.0"
@@ -14948,7 +14985,7 @@ query-string@^6.13.8:
querystring@0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
- integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
+ integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
queue-microtask@^1.2.2:
version "1.2.3"
@@ -15849,9 +15886,9 @@ socks-proxy-agent@5, socks-proxy-agent@^5.0.0:
socks "^2.3.3"
socks-proxy-agent@^6.0.0:
- version "6.2.0"
- resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz#f6b5229cc0cbd6f2f202d9695f09d871e951c85e"
- integrity sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ==
+ version "6.2.1"
+ resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce"
+ integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==
dependencies:
agent-base "^6.0.2"
debug "^4.3.3"
@@ -15936,9 +15973,9 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3:
- version "0.7.3"
- resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
- integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+ version "0.7.4"
+ resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
+ integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
spdx-correct@^3.0.0:
version "3.1.1"
@@ -16671,10 +16708,10 @@ ts-jest@^26.4.4:
semver "7.x"
yargs-parser "20.x"
-ts-node@^10.2.1, ts-node@^10.7.0:
- version "10.8.0"
- resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.8.0.tgz#3ceb5ac3e67ae8025c1950626aafbdecb55d82ce"
- integrity sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==
+ts-node@^10.2.1, ts-node@^10.8.0:
+ version "10.8.1"
+ resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066"
+ integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==
dependencies:
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
@@ -16848,9 +16885,9 @@ typescript@^3.8.3:
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
typescript@^4.4.3, typescript@^4.5.5:
- version "4.6.4"
- resolved "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
- integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
+ version "4.7.3"
+ resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
+ integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
typescript@~4.4.4:
version "4.4.4"
@@ -16863,9 +16900,9 @@ ua-parser-js@^0.7.18:
integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
uglify-js@^3.1.4:
- version "3.15.5"
- resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.5.tgz#2b10f9e0bfb3f5c15a8e8404393b6361eaeb33b3"
- integrity sha512-hNM5q5GbBRB5xB+PMqVRcgYe4c8jbyZ1pzZhS6jbq54/4F2gFK869ZheiE5A8/t+W5jtTNpWef/5Q9zk639FNQ==
+ version "3.16.0"
+ resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.0.tgz#b778ba0831ca102c1d8ecbdec2d2bdfcc7353190"
+ integrity sha512-FEikl6bR30n0T3amyBh3LoiBdqHRy/f4H80+My34HOesOKyHfOsxAPAxOoqC0JUnC1amnO0IwkYC3sko51caSw==
uid-number@0.0.6:
version "0.0.6"
@@ -16901,9 +16938,9 @@ unbzip2-stream@^1.0.9:
through "^2.3.8"
undici@^5.1.0:
- version "5.3.0"
- resolved "https://registry.npmjs.org/undici/-/undici-5.3.0.tgz#869d47bafa7f72ccaf8738258f0283bf3dd179ca"
- integrity sha512-8LxC/xmR2GCE4q1heE1sJxVnnf5S6yQ2dObvMFBBWkB8aQlaqNuWovgRFWRMB7KUdLPGZfOTTmUeeLEJYX56iQ==
+ version "5.4.0"
+ resolved "https://registry.npmjs.org/undici/-/undici-5.4.0.tgz#c474fae02743d4788b96118d46008a24195024d2"
+ integrity sha512-A1SRXysDg7J+mVP46jF+9cKANw0kptqSFZ8tGyL+HBiv0K1spjxPX8Z4EGu+Eu6pjClJUBdnUPlxrOafR668/g==
unfetch@^4.2.0:
version "4.2.0"
@@ -17099,6 +17136,11 @@ uuid@3.x, uuid@^3.0.0, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2:
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c"
+ integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
+
uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
@@ -17304,9 +17346,9 @@ wide-align@^1.1.0:
string-width "^1.0.2 || 2 || 3 || 4"
winston-daily-rotate-file@^4.5.0:
- version "4.6.1"
- resolved "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.6.1.tgz#35c9db5669c381ed32acdb5849de69ab046a7a71"
- integrity sha512-Ycch4LZmTycbhgiI2eQXBKI1pKcEQgAqmBjyq7/dC6Dk77nasdxvhLKraqTdCw7wNDSs8/M0jXaLATHquG7xYg==
+ version "4.7.1"
+ resolved "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz#f60a643af87f8867f23170d8cd87dbe3603a625f"
+ integrity sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==
dependencies:
file-stream-rotator "^0.6.1"
object-hash "^2.0.1"
@@ -17433,14 +17475,14 @@ write-pkg@^4.0.0:
write-json-file "^3.2.0"
ws@^7.4.6, ws@^7.5.7:
- version "7.5.7"
- resolved "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"
- integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==
+ version "7.5.8"
+ resolved "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a"
+ integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==
ws@^8.3.0:
- version "8.6.0"
- resolved "https://registry.npmjs.org/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
- integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==
+ version "8.7.0"
+ resolved "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz#eaf9d874b433aa00c0e0d8752532444875db3957"
+ integrity sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==
xml-js@^1.6.11:
version "1.6.11"
@@ -17666,4 +17708,4 @@ zip-stream@^4.1.0:
dependencies:
archiver-utils "^2.1.0"
compress-commons "^4.1.0"
- readable-stream "^3.6.0"
+ readable-stream "^3.6.0"
\ No newline at end of file