├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .terranextrc ├── .versionrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── terranext.js ├── integration ├── .DS_Store ├── .gitignore ├── app │ ├── build.js │ ├── next.config.js │ ├── pages │ │ ├── [pageId] │ │ │ └── index.js │ │ ├── about-us │ │ │ └── index.js │ │ ├── blogPost.js │ │ ├── contact.js │ │ └── index.js │ └── routes.js ├── aws │ └── api.tf └── terraform ├── package.json ├── pnpm-lock.yaml ├── rome.json ├── src ├── compatLayer.js ├── configuration.js ├── constants.js ├── errors │ ├── emptyConfigurationError.js │ ├── errors.d.ts │ ├── folderNotFoundError.js │ ├── incorretRoutesError.js │ ├── invalidMemorySize.js │ ├── invalidTimeout.js │ ├── missingKeyError.js │ ├── providerNotSupported.js │ └── validationError.js ├── index.d.ts ├── index.js ├── providers │ ├── aws │ │ ├── aws.declarations.ts │ │ ├── awsConfig.js │ │ ├── declarations.d.ts │ │ ├── index.js │ │ └── resources │ │ │ ├── gateway.js │ │ │ ├── gatewayIntegration.js │ │ │ ├── gatewayMethod.js │ │ │ ├── gatewayResource.js │ │ │ ├── lambda.js │ │ │ ├── lambdaPermission.js │ │ │ ├── lambdaProperties.js │ │ │ └── lambdaZip.js │ └── baseProvider.js ├── shared.js └── utils.js ├── tests ├── configuration.test.js ├── providers │ └── aws │ │ ├── __fixtures__ │ │ ├── .next │ │ │ └── serverless │ │ │ │ └── pages │ │ │ │ ├── boar.js │ │ │ │ ├── contact-us │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ ├── next9 │ │ │ ├── .next │ │ │ │ └── serverless │ │ │ │ │ └── pages │ │ │ │ │ ├── boar.js │ │ │ │ │ ├── contact-us │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ └── pages │ │ │ │ └── [foo] │ │ │ │ ├── [deep] │ │ │ │ └── index.js │ │ │ │ ├── [query].js │ │ │ │ ├── bar.js │ │ │ │ └── fixed │ │ │ │ └── index.js │ │ └── pages │ │ │ ├── boar.js │ │ │ ├── contact-us │ │ │ └── index.js │ │ │ └── index.js │ │ ├── __snapshots__ │ │ └── awsResources.test.js.snap │ │ ├── awsResources.test.js │ │ ├── gatewayIntegration.test.js │ │ ├── gatewayMethod.test.js │ │ ├── gatewayResource.test.js │ │ ├── lambdaPermission.test.js │ │ ├── lambdaProperties.test.js │ │ ├── lambdaZip.test.js │ │ └── shared.test.js └── terranext.test.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | tab_width = 2 4 | 5 | indent_style = tab 6 | 7 | end_of_line = lf 8 | 9 | [*.{md}] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Issue 11 | 12 | ## Expected Behavior 13 | 14 | ## Actual Behavior 15 | 16 | ## Steps to Reproduce the Problem 17 | 18 | 19 | ## Specifications 20 | 21 | - Version: 22 | - Platform: 23 | - Subsystem: 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Feature 2 | 3 | Fixes # 4 | 5 | ## Proposed Changes 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [16.x, 14.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v1 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v1 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | - name: Install pnpm 26 | run: curl -L https://pnpm.js.org/pnpm.js | node - add --global pnpm 27 | - name: pnpm install 28 | run: pnpm install 29 | - name: Linting 30 | run: pnpm run ci 31 | - name: Unit Test 32 | run: pnpm run test:ci-unix 33 | env: 34 | CI: true 35 | - name: Install terraform 36 | run: | 37 | apt-get update && apt-get install -y jq 38 | curl -o terraform.zip https://releases.hashicorp.com/terraform/0.11.13/terraform_0.11.13_linux_amd64.zip && unzip terraform.zip && mv terraform ./integration/aws 39 | - name: Run integration and copy files 40 | run: | 41 | pnpm run integration:build 42 | cp ./integration/app/gateway.terraform.tf.json ./integration/aws; 43 | cp ./integration/app/lambdas.terraform.tf.json ./integration/aws; 44 | - name: Validate resources 45 | run: | 46 | terraform init 47 | terraform validate 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea/ 61 | .DS_Store 62 | 63 | -------------------------------------------------------------------------------- /.terranextrc: -------------------------------------------------------------------------------- 1 | { 2 | "routes": { 3 | "prefix": "", 4 | "mappings": [ 5 | { 6 | "page": "/blogPost", 7 | "route": "/blog/:url" 8 | }, 9 | { 10 | "page": "/contact", 11 | "route": "/contact-us" 12 | } 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { "type": "feat", "section": "Features" }, 4 | { "type": "fix", "section": "Bug Fixes" }, 5 | { "type": "chore", "section": "Chores", "hidden": false }, 6 | { "type": "docs", "section": "Docs", "hidden": false }, 7 | { "type": "style", "hidden": true }, 8 | { "type": "refactor", "hidden": true }, 9 | { "type": "perf", "hidden": true }, 10 | { "type": "test", "hidden": true } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [4.0.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v3.0.0...v4.0.0) (2020-09-12) 6 | 7 | 8 | ### ⚠ BREAKING CHANGES 9 | 10 | * min node version is 12 11 | 12 | ### Features 13 | 14 | * updated to node 12 ([12834e4](https://github.com/ematipico/terraform-nextjs-plugin/commit/12834e455e2fdf6f0f263f27ed4a8fee9ff1dcac)) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * typo (nextDirApp => nextAppDir) ([#295](https://github.com/ematipico/terraform-nextjs-plugin/issues/295)) ([97c4709](https://github.com/ematipico/terraform-nextjs-plugin/commit/97c47096fdbe89b311938b5e29b1338251fde2fb)) 20 | 21 | 22 | ### Chores 23 | 24 | * updated various dev dependencies ([76369bf](https://github.com/ematipico/terraform-nextjs-plugin/commit/76369bfd850a73d5ee2d80a253881898223789e7)) 25 | * **deps-dev:** bump standard-version from 8.0.0 to 8.0.1 ([#293](https://github.com/ematipico/terraform-nextjs-plugin/issues/293)) ([f95bcd5](https://github.com/ematipico/terraform-nextjs-plugin/commit/f95bcd515fc66563fb17e7d9fd6726beae1992b4)) 26 | * fixed prettier warnings ([6e8c34b](https://github.com/ematipico/terraform-nextjs-plugin/commit/6e8c34bf42551d79939c658580da9fb927ddf544)) 27 | 28 | ## [3.0.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v2.0.0...v3.0.0) (2020-06-11) 29 | 30 | 31 | ### ⚠ BREAKING CHANGES 32 | 33 | * minimum version of Next.js is now 9.3.2 34 | 35 | ### Features 36 | 37 | * dropped Node.js v8 ([56dbea5](https://github.com/ematipico/terraform-nextjs-plugin/commit/56dbea5aca11ab16bdcdb066610540009d877ebb)) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * updated dependencies ([#199](https://github.com/ematipico/terraform-nextjs-plugin/issues/199)) ([2bd8e66](https://github.com/ematipico/terraform-nextjs-plugin/commit/2bd8e669b323dfa3cf562afa38525fc3b0b8abb5)) 43 | * updated libs to fix security problems ([9cc3c96](https://github.com/ematipico/terraform-nextjs-plugin/commit/9cc3c968b9f9ec1e22ba53a7c9910bd30bc35081)) 44 | 45 | 46 | ### Chores 47 | 48 | * **release:** 2.1.0 ([801797a](https://github.com/ematipico/terraform-nextjs-plugin/commit/801797a2624a0c713334fb61e90ce0dd8baff2fe)) 49 | 50 | ## [2.1.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v2.0.0...v2.1.0) (2020-02-21) 51 | 52 | 53 | ### Features 54 | 55 | * dropped Node.js v8 ([56dbea5](https://github.com/ematipico/terraform-nextjs-plugin/commit/56dbea5aca11ab16bdcdb066610540009d877ebb)) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * case where there's a generated folder by next.js ([e5a638e](https://github.com/ematipico/terraform-nextjs-plugin/commit/e5a638ee42c017335a2d4cf9cf38885f02b52627)) 61 | * updated dependencies ([515d184](https://github.com/ematipico/terraform-nextjs-plugin/commit/515d1849f98ded668af03b99a0a5baeee5f8063e)) 62 | 63 | 64 | ### Chores 65 | 66 | * moving binary inside integration folder ([2f829a6](https://github.com/ematipico/terraform-nextjs-plugin/commit/2f829a68cbffeaf581d56ac3226c21b3a2bc2cd2)) 67 | * removed azure pipelines ([d0c8b90](https://github.com/ematipico/terraform-nextjs-plugin/commit/d0c8b90b1c3d1a7b7983b6fdb4c77531aaa6f9dc)) 68 | 69 | ## [2.0.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v1.3.0...v2.0.0) (2020-01-17) 70 | 71 | * BREAKING CHANGE: dropped Node.js v8 72 | 73 | ## [1.3.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v1.2.1...v1.3.0) (2019-11-21) 74 | 75 | 76 | ### Features 77 | 78 | * Env vars and more configuration for lambdas ([#137](https://github.com/ematipico/terraform-nextjs-plugin/issues/137)) ([1439426](https://github.com/ematipico/terraform-nextjs-plugin/commit/1439426dee54a481ef96b8db6c5bc2aad42592e5)) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * **ci:** fixed command ([9fb5500](https://github.com/ematipico/terraform-nextjs-plugin/commit/9fb5500f6dbd5dcb377440ab54d4091213bad77a)) 84 | 85 | 86 | ### Docs 87 | 88 | * updated documentation ([e0edcdf](https://github.com/ematipico/terraform-nextjs-plugin/commit/e0edcdf363336addf18448cf8f5488f2410c0c79)) 89 | 90 | 91 | ### Chores 92 | 93 | * **deps:** [security] bump https-proxy-agent from 2.2.2 to 2.2.4 ([#131](https://github.com/ematipico/terraform-nextjs-plugin/issues/131)) ([3004c41](https://github.com/ematipico/terraform-nextjs-plugin/commit/3004c417c17068bac774e956dc1bbd6f89fdff66)) 94 | 95 | ### [1.2.1](https://github.com/ematipico/terraform-nextjs-plugin/compare/v1.2.0...v1.2.1) (2019-11-13) 96 | 97 | 98 | ### Bug Fixes 99 | 100 | * restored the compact layer ([#126](https://github.com/ematipico/terraform-nextjs-plugin/issues/126)) ([96d7923](https://github.com/ematipico/terraform-nextjs-plugin/commit/96d7923)) 101 | 102 | 103 | ### Chores 104 | 105 | * **deps-dev:** bump @types/node from 12.7.11 to 12.11.7 ([#107](https://github.com/ematipico/terraform-nextjs-plugin/issues/107)) ([37e1271](https://github.com/ematipico/terraform-nextjs-plugin/commit/37e1271)) 106 | * added CI build on Github => lint and unit test ([5236da2](https://github.com/ematipico/terraform-nextjs-plugin/commit/5236da2)) 107 | 108 | ## [1.2.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v1.1.0...v1.2.0) (2019-10-04) 109 | 110 | 111 | ### Chores 112 | 113 | * **deps-dev:** bump react from 16.10.1 to 16.10.2 ([#90](https://github.com/ematipico/terraform-nextjs-plugin/issues/90)) ([f363711](https://github.com/ematipico/terraform-nextjs-plugin/commit/f363711)) 114 | 115 | 116 | ### Features 117 | 118 | * writing files using native methods ([#92](https://github.com/ematipico/terraform-nextjs-plugin/issues/92)) ([118b30c](https://github.com/ematipico/terraform-nextjs-plugin/commit/118b30c)) 119 | 120 | ## [1.1.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v1.0.1...v1.1.0) (2019-10-02) 121 | 122 | 123 | ### Chores 124 | 125 | * **deps-dev:** bump jest from 24.8.0 to 24.9.0 ([#54](https://github.com/ematipico/terraform-nextjs-plugin/issues/54)) ([b0a7468](https://github.com/ematipico/terraform-nextjs-plugin/commit/b0a7468)) 126 | * added config to include more commits ([b03db8c](https://github.com/ematipico/terraform-nextjs-plugin/commit/b03db8c)) 127 | * **deps:** [security] bump eslint-utils from 1.3.1 to 1.4.2 ([#64](https://github.com/ematipico/terraform-nextjs-plugin/issues/64)) ([37388fb](https://github.com/ematipico/terraform-nextjs-plugin/commit/37388fb)) 128 | * **deps:** [security] bump mixin-deep from 1.3.1 to 1.3.2 ([#66](https://github.com/ematipico/terraform-nextjs-plugin/issues/66)) ([12a0070](https://github.com/ematipico/terraform-nextjs-plugin/commit/12a0070)) 129 | * **deps-dev:** bump @types/jest from 24.0.15 to 24.0.16 ([#45](https://github.com/ematipico/terraform-nextjs-plugin/issues/45)) ([760da5e](https://github.com/ematipico/terraform-nextjs-plugin/commit/760da5e)) 130 | * **deps-dev:** bump @types/jest from 24.0.16 to 24.0.17 ([0fe3020](https://github.com/ematipico/terraform-nextjs-plugin/commit/0fe3020)) 131 | * **deps-dev:** bump @types/jest from 24.0.16 to 24.0.17 ([#48](https://github.com/ematipico/terraform-nextjs-plugin/issues/48)) ([87c356e](https://github.com/ematipico/terraform-nextjs-plugin/commit/87c356e)) 132 | * **deps-dev:** bump @types/jest from 24.0.17 to 24.0.18 ([#57](https://github.com/ematipico/terraform-nextjs-plugin/issues/57)) ([ac4f280](https://github.com/ematipico/terraform-nextjs-plugin/commit/ac4f280)) 133 | * **deps-dev:** bump babel-eslint from 10.0.1 to 10.0.2 ([#52](https://github.com/ematipico/terraform-nextjs-plugin/issues/52)) ([dccc496](https://github.com/ematipico/terraform-nextjs-plugin/commit/dccc496)) 134 | * **deps-dev:** bump babel-eslint from 10.0.2 to 10.0.3 ([#63](https://github.com/ematipico/terraform-nextjs-plugin/issues/63)) ([8d1433e](https://github.com/ematipico/terraform-nextjs-plugin/commit/8d1433e)) 135 | * **deps-dev:** bump eslint from 5.16.0 to 6.2.2 ([#60](https://github.com/ematipico/terraform-nextjs-plugin/issues/60)) ([81756b1](https://github.com/ematipico/terraform-nextjs-plugin/commit/81756b1)) 136 | * **deps-dev:** bump eslint from 6.2.2 to 6.3.0 ([#68](https://github.com/ematipico/terraform-nextjs-plugin/issues/68)) ([00944af](https://github.com/ematipico/terraform-nextjs-plugin/commit/00944af)) 137 | * **deps-dev:** bump eslint-config-prettier from 5.1.0 to 6.0.0 ([#53](https://github.com/ematipico/terraform-nextjs-plugin/issues/53)) ([db028c6](https://github.com/ematipico/terraform-nextjs-plugin/commit/db028c6)) 138 | * **deps-dev:** bump eslint-config-prettier from 6.0.0 to 6.1.0 ([#58](https://github.com/ematipico/terraform-nextjs-plugin/issues/58)) ([c79ef3a](https://github.com/ematipico/terraform-nextjs-plugin/commit/c79ef3a)) 139 | * **deps-dev:** bump eslint-config-prettier from 6.1.0 to 6.2.0 ([#69](https://github.com/ematipico/terraform-nextjs-plugin/issues/69)) ([d0b73cc](https://github.com/ematipico/terraform-nextjs-plugin/commit/d0b73cc)) 140 | * **deps-dev:** bump eslint-config-prettier from 6.2.0 to 6.3.0 ([#71](https://github.com/ematipico/terraform-nextjs-plugin/issues/71)) ([1fda150](https://github.com/ematipico/terraform-nextjs-plugin/commit/1fda150)) 141 | * **deps-dev:** bump eslint-plugin-node from 9.1.0 to 9.2.0 ([#67](https://github.com/ematipico/terraform-nextjs-plugin/issues/67)) ([bbe7869](https://github.com/ematipico/terraform-nextjs-plugin/commit/bbe7869)) 142 | * **deps-dev:** bump eslint-plugin-node from 9.2.0 to 10.0.0 ([#70](https://github.com/ematipico/terraform-nextjs-plugin/issues/70)) ([e35deb4](https://github.com/ematipico/terraform-nextjs-plugin/commit/e35deb4)) 143 | * **deps-dev:** bump eslint-plugin-prettier from 3.1.0 to 3.1.1 ([#76](https://github.com/ematipico/terraform-nextjs-plugin/issues/76)) ([75214e5](https://github.com/ematipico/terraform-nextjs-plugin/commit/75214e5)) 144 | * **deps-dev:** bump eslint-plugin-unicorn from 9.1.0 to 9.1.1 ([#51](https://github.com/ematipico/terraform-nextjs-plugin/issues/51)) ([39f20d1](https://github.com/ematipico/terraform-nextjs-plugin/commit/39f20d1)) 145 | * **deps-dev:** bump eslint-plugin-unicorn from 9.1.1 to 10.0.0 ([#62](https://github.com/ematipico/terraform-nextjs-plugin/issues/62)) ([5328438](https://github.com/ematipico/terraform-nextjs-plugin/commit/5328438)) 146 | * **deps-dev:** bump jest-junit from 6.4.0 to 7.0.0 ([661e7e9](https://github.com/ematipico/terraform-nextjs-plugin/commit/661e7e9)) 147 | * **deps-dev:** bump jest-junit from 6.4.0 to 7.0.0 ([#43](https://github.com/ematipico/terraform-nextjs-plugin/issues/43)) ([321d855](https://github.com/ematipico/terraform-nextjs-plugin/commit/321d855)) 148 | * **deps-dev:** bump jest-junit from 7.0.0 to 8.0.0 ([#65](https://github.com/ematipico/terraform-nextjs-plugin/issues/65)) ([a768faf](https://github.com/ematipico/terraform-nextjs-plugin/commit/a768faf)) 149 | * **deps-dev:** bump react from 16.8.6 to 16.9.0 ([#50](https://github.com/ematipico/terraform-nextjs-plugin/issues/50)) ([6533c71](https://github.com/ematipico/terraform-nextjs-plugin/commit/6533c71)) 150 | * **deps-dev:** bump react-dom from 16.8.6 to 16.9.0 ([#49](https://github.com/ematipico/terraform-nextjs-plugin/issues/49)) ([78eec10](https://github.com/ematipico/terraform-nextjs-plugin/commit/78eec10)) 151 | * **deps-dev:** bump standard-version from 6.0.1 to 7.0.0 ([71bd550](https://github.com/ematipico/terraform-nextjs-plugin/commit/71bd550)) 152 | * **deps-dev:** bump standard-version from 6.0.1 to 7.0.0 ([#44](https://github.com/ematipico/terraform-nextjs-plugin/issues/44)) ([638066c](https://github.com/ematipico/terraform-nextjs-plugin/commit/638066c)) 153 | 154 | 155 | ### Features 156 | 157 | * added support for next 9 ([#88](https://github.com/ematipico/terraform-nextjs-plugin/issues/88)) ([ee0a1b7](https://github.com/ematipico/terraform-nextjs-plugin/commit/ee0a1b7)) 158 | 159 | ### [1.0.1](https://github.com/ematipico/terraform-nextjs-plugin/compare/v1.0.0...v1.0.1) (2019-07-11) 160 | 161 | 162 | 163 | ## [1.0.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.9.0...v1.0.0) (2019-07-08) 164 | 165 | 166 | ### Bug Fixes 167 | 168 | * set next app dir default value ([#35](https://github.com/ematipico/terraform-nextjs-plugin/issues/35)) ([7b02c66](https://github.com/ematipico/terraform-nextjs-plugin/commit/7b02c66)) 169 | 170 | 171 | 172 | ## [0.9.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.8.0...v0.9.0) (2019-07-06) 173 | 174 | 175 | * BREAKING CHANGE: renamed CLI params (#33) ([24efe81](https://github.com/ematipico/terraform-nextjs-plugin/commit/24efe81)), closes [#33](https://github.com/ematipico/terraform-nextjs-plugin/issues/33) 176 | 177 | 178 | ### BREAKING CHANGES 179 | 180 | * renamed CLI params 181 | 182 | - gatewayKey => gateway-key 183 | - lambdaPath => next-app-dir 184 | 185 | * chore: removed next build app 186 | chore: udpated test 187 | 188 | * fix: tests 189 | 190 | * fix: added more info on the error msg 191 | 192 | * fix: path resolution 193 | 194 | * fix: requrie the correct build func 195 | 196 | * fix: correctly call build 197 | 198 | 199 | 200 | ## [0.8.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.6.1...v0.8.0) (2019-07-04) 201 | 202 | 203 | * BREAKING CHANGE: routes not mandatory (#29) ([e5f6e78](https://github.com/ematipico/terraform-nextjs-plugin/commit/e5f6e78)), closes [#29](https://github.com/ematipico/terraform-nextjs-plugin/issues/29) 204 | 205 | 206 | ### BREAKING CHANGES 207 | 208 | * routes not mandatory 209 | 210 | - routes is not mandatory 211 | - added more types to the code 212 | - create routes based on next pages 213 | 214 | * fix: removed .next from ignored files 215 | 216 | * fix: path resolution of lambdas 217 | 218 | - fix path resolution of where the lambdas are 219 | - removed default value from CLI 220 | - handling schema validation in a different way 221 | 222 | * fix: test reviewed 223 | 224 | * fix: path to lambda 225 | 226 | 227 | 228 | ## [0.7.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.6.1...v0.7.0) (2019-07-04) 229 | 230 | 231 | * BREAKING CHANGE: routes not mandatory (#29) ([e5f6e78](https://github.com/ematipico/terraform-nextjs-plugin/commit/e5f6e78)), closes [#29](https://github.com/ematipico/terraform-nextjs-plugin/issues/29) 232 | 233 | 234 | ### BREAKING CHANGES 235 | 236 | * routes not mandatory 237 | 238 | - routes is not mandatory 239 | - added more types to the code 240 | - create routes based on next pages 241 | 242 | * fix: removed .next from ignored files 243 | 244 | * fix: path resolution of lambdas 245 | 246 | - fix path resolution of where the lambdas are 247 | - removed default value from CLI 248 | - handling schema validation in a different way 249 | 250 | * fix: test reviewed 251 | 252 | * fix: path to lambda 253 | 254 | 255 | 256 | ### [0.6.1](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.4.1...v0.6.1) (2019-06-27) 257 | 258 | 259 | ### Bug Fixes 260 | 261 | * added provider to integration test ([2d3dfdb](https://github.com/ematipico/terraform-nextjs-plugin/commit/2d3dfdb)) 262 | * better styling ([371efee](https://github.com/ematipico/terraform-nextjs-plugin/commit/371efee)) 263 | * compatLayer path ([a222bba](https://github.com/ematipico/terraform-nextjs-plugin/commit/a222bba)) 264 | * export correct functions ([e0d6115](https://github.com/ematipico/terraform-nextjs-plugin/commit/e0d6115)) 265 | * typing for gateway integration ([598d256](https://github.com/ematipico/terraform-nextjs-plugin/commit/598d256)) 266 | 267 | 268 | ### Features 269 | 270 | * added more types ([6e263b3](https://github.com/ematipico/terraform-nextjs-plugin/commit/6e263b3)) 271 | * added typings to the functions ([a9d2d63](https://github.com/ematipico/terraform-nextjs-plugin/commit/a9d2d63)) 272 | * fixed declarations in lambdas ([da752f0](https://github.com/ematipico/terraform-nextjs-plugin/commit/da752f0)) 273 | * reactor based on providers ([786957b](https://github.com/ematipico/terraform-nextjs-plugin/commit/786957b)) 274 | 275 | 276 | 277 | ### [0.5.1](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.4.1...v0.5.1) (2019-06-27) 278 | 279 | 280 | ### Bug Fixes 281 | 282 | * added provider to integration test ([2d3dfdb](https://github.com/ematipico/terraform-nextjs-plugin/commit/2d3dfdb)) 283 | * better styling ([371efee](https://github.com/ematipico/terraform-nextjs-plugin/commit/371efee)) 284 | * compatLayer path ([a222bba](https://github.com/ematipico/terraform-nextjs-plugin/commit/a222bba)) 285 | * export correct functions ([e0d6115](https://github.com/ematipico/terraform-nextjs-plugin/commit/e0d6115)) 286 | * typing for gateway integration ([598d256](https://github.com/ematipico/terraform-nextjs-plugin/commit/598d256)) 287 | 288 | 289 | ### Features 290 | 291 | * added more types ([6e263b3](https://github.com/ematipico/terraform-nextjs-plugin/commit/6e263b3)) 292 | * added typings to the functions ([a9d2d63](https://github.com/ematipico/terraform-nextjs-plugin/commit/a9d2d63)) 293 | * fixed declarations in lambdas ([da752f0](https://github.com/ematipico/terraform-nextjs-plugin/commit/da752f0)) 294 | * reactor based on providers ([786957b](https://github.com/ematipico/terraform-nextjs-plugin/commit/786957b)) 295 | 296 | 297 | 298 | ## [0.5.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.4.1...v0.5.0) (2019-06-21) 299 | 300 | 301 | ### Bug Fixes 302 | 303 | * better styling ([d7825fd](https://github.com/ematipico/terraform-nextjs-plugin/commit/d7825fd)) 304 | * typing for gateway integration ([94c4e17](https://github.com/ematipico/terraform-nextjs-plugin/commit/94c4e17)) 305 | 306 | 307 | ### Features 308 | 309 | * added more types ([eaa526d](https://github.com/ematipico/terraform-nextjs-plugin/commit/eaa526d)) 310 | * added typings to the functions ([ad6ec11](https://github.com/ematipico/terraform-nextjs-plugin/commit/ad6ec11)) 311 | * fixed declarations in lambdas ([2f1ab5e](https://github.com/ematipico/terraform-nextjs-plugin/commit/2f1ab5e)) 312 | * TypeScript types for the main function ([#18](https://github.com/ematipico/terraform-nextjs-plugin/issues/18)) ([2610b7e](https://github.com/ematipico/terraform-nextjs-plugin/commit/2610b7e)) 313 | 314 | 315 | 316 | ### [0.4.1](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.4.0...v0.4.1) (2019-06-20) 317 | 318 | 319 | ### Bug Fixes 320 | 321 | * added bash example ([05002b8](https://github.com/ematipico/terraform-nextjs-plugin/commit/05002b8)) 322 | 323 | 324 | 325 | ## [0.4.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.3.0...v0.4.0) (2019-06-20) 326 | 327 | 328 | ### Bug Fixes 329 | 330 | * menu documentation ([57b2ae1](https://github.com/ematipico/terraform-nextjs-plugin/commit/57b2ae1)) 331 | 332 | 333 | ### Features 334 | 335 | * create CLI utility ([0475466](https://github.com/ematipico/terraform-nextjs-plugin/commit/0475466)) 336 | * create CLI utility ([#15](https://github.com/ematipico/terraform-nextjs-plugin/issues/15)) ([2162101](https://github.com/ematipico/terraform-nextjs-plugin/commit/2162101)) 337 | 338 | 339 | 340 | ## [0.3.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.2.0...v0.3.0) (2019-06-10) 341 | 342 | 343 | ### Bug Fixes 344 | 345 | * copy files ([653f868](https://github.com/ematipico/terraform-nextjs-plugin/commit/653f868)) 346 | * copy single files ([200b3cf](https://github.com/ematipico/terraform-nextjs-plugin/commit/200b3cf)) 347 | * correct gateway key ([10d0cd1](https://github.com/ematipico/terraform-nextjs-plugin/commit/10d0cd1)) 348 | * correct name ([fd5156f](https://github.com/ematipico/terraform-nextjs-plugin/commit/fd5156f)) 349 | * correct path to nextjs build ([cc0838e](https://github.com/ematipico/terraform-nextjs-plugin/commit/cc0838e)) 350 | * execute file to generate files ([e80549d](https://github.com/ematipico/terraform-nextjs-plugin/commit/e80549d)) 351 | * integration name and how to validate files ([9c0a33a](https://github.com/ematipico/terraform-nextjs-plugin/commit/9c0a33a)) 352 | * run next cli ([023236f](https://github.com/ematipico/terraform-nextjs-plugin/commit/023236f)) 353 | * script folder ([094f034](https://github.com/ematipico/terraform-nextjs-plugin/commit/094f034)) 354 | * split up based on type of tests ([dbd32e2](https://github.com/ematipico/terraform-nextjs-plugin/commit/dbd32e2)) 355 | 356 | 357 | ### Features 358 | 359 | * added bash to run ([b66da4a](https://github.com/ematipico/terraform-nextjs-plugin/commit/b66da4a)) 360 | * added task via template ([f13b577](https://github.com/ematipico/terraform-nextjs-plugin/commit/f13b577)) 361 | * added terraform init command ([45f77c7](https://github.com/ematipico/terraform-nextjs-plugin/commit/45f77c7)) 362 | * integration test ([44d2303](https://github.com/ematipico/terraform-nextjs-plugin/commit/44d2303)) 363 | * integration test ([#13](https://github.com/ematipico/terraform-nextjs-plugin/issues/13)) ([e19544e](https://github.com/ematipico/terraform-nextjs-plugin/commit/e19544e)) 364 | * split integration from normal tests ([4160305](https://github.com/ematipico/terraform-nextjs-plugin/commit/4160305)) 365 | 366 | 367 | 368 | ## [0.2.0](https://github.com/ematipico/terraform-nextjs-plugin/compare/v0.1.0...v0.2.0) (2019-06-08) 369 | 370 | 371 | ### Features 372 | 373 | * read configuration from config file ([d75cde3](https://github.com/ematipico/terraform-nextjs-plugin/commit/d75cde3)) 374 | 375 | 376 | * BREAKING CHANGE: refactored how params are passed (#9) ([efe1cd7](https://github.com/ematipico/terraform-nextjs-plugin/commit/efe1cd7)), closes [#9](https://github.com/ematipico/terraform-nextjs-plugin/issues/9) 377 | 378 | 379 | ### BREAKING CHANGES 380 | 381 | * refactored how params are passed 382 | 383 | 384 | 385 | ## 0.1.0 (2019-06-07) 386 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Emanuele Stoppa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform nextjs plugin 2 | 3 |

4 | 5 | 6 |

7 | 8 | A plugin to generate terraform configuration from nextjs pages 9 | 10 | [![Build Status][build-status-azure]][build-status-azure-url] 11 | [![Codacy Badge][code-quality]][code-quality-url] 12 | [![npm][npm]][npm-url] 13 | [![Conventional Commits][conventional]][conventional-url] 14 | [![codecov][coverage]][coverage-url] 15 | 16 | ## The reason 17 | 18 | Nextjs supports serverless pages, where it creates files that can be used by some lambdas to render the pages. 19 | Unfortunately, here you are left alone. So here a solution for your troubles. 20 | 21 | - [Installation](#installation) 22 | - [Usage](#usage) 23 | - [Via CLI](#via-cli) 24 | - [Via API](#via-api) 25 | - [Configuration](#configuration) 26 | - [Mapping explained](#mapping-explained) 27 | - [Providers](#providers) 28 | - [AWS](#aws) 29 | 30 | ## Installation 31 | 32 | ```bash 33 | npm i --save-dev @ematipico/terraform-nextjs-plugin 34 | ``` 35 | 36 | Or 37 | 38 | ```bash 39 | yarn add --dev @ematipico/terraform-nextjs-plugin 40 | ``` 41 | 42 | **This package requires at least Next v8.** 43 | 44 | ## Usage 45 | 46 | ```bash 47 | terranext --provider=AWS 48 | ``` 49 | 50 | This library supports [cosmiconfig](https://github.com/davidtheclark/cosmiconfig): you just need to have a file called `terranextrc` that matches the criteria. This repository has [one](./terranextrc). 51 | 52 | ### Via CLI 53 | 54 | You can use the simple CLI available. At moment you _can't_ pass the `routes` parameter, you will need to use the config object or use the [API](#via-api). 55 | 56 | Using the CLI will automatically emit the configuration files. 57 | 58 | _**Arguments passed via CLI will *override* the ones that are defined inside the config file**_. 59 | 60 | ```bash 61 | terranext --provider=AWS --gateway-key=CustomKey --next-dir-app=../../nextjs-project/ 62 | ``` 63 | 64 | Or you can use the aliases: 65 | 66 | ```bash 67 | terranext --provider=AWS -g=CustomKey -p=../../nextjs-project/ 68 | ``` 69 | 70 | ### Help section 71 | 72 | ```block 73 | 74 | Usage 75 | $ terranext 76 | 77 | Options 78 | --gateway-key, -g The API Gateway key of the project. Default is "Terranext" 79 | --next-app-dir, -d The path that Terraform CLI has to follow to reach the nextjs project. 80 | --provider The Cloud provider to use when exporting the configuration 81 | --env A way for passing environment variables to the lambdas 82 | 83 | 84 | Examples 85 | $ terranext 86 | $ terranext --gateway-key=CustomKey --next-app-dir=../../nextjs-project/ 87 | $ terranext --provider=AWS --next-app-dir=../../nextjs-project/ 88 | $ terranext -g=CustomKey -d=../../nextjs-project/ 89 | $ terranext --env="DEBUG,express:*" --env="API_KEY,1234" 90 | ``` 91 | 92 | ### Via API 93 | 94 | ```js 95 | const generateResources = require("@ematipico/terraform-nextjs-plugin"); 96 | 97 | const configuration = { 98 | gatewayKey: "AmazingWebsite", 99 | lambdaPath: "../../project/build", 100 | provider: "AWS", 101 | env: [ 102 | { 103 | key: "KEY", 104 | value: "2940" 105 | } 106 | ] 107 | }; 108 | 109 | const resources = generateResources(configuration); // inside resources you have the terraform json configuration 110 | generateResources(configuration, true); // it creates two files 111 | ``` 112 | 113 | If the second argument is a boolean and it's `true`, the library will create two files: 114 | 115 | - `gateway.terraform.tf.json` 116 | - `lambdas.terraform.tf.json` 117 | 118 | Having a suffix with `.tf.` will tell automatically to `terraform` that should be validated and planned. 119 | It will be up to you to consume them in a proper way. 120 | 121 | ## Configuration 122 | 123 | | Name | Type | Default | Description | 124 | | ------------ | ------------------------------ | ------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 125 | | `gatewayKey` | `string` | Terranext | A name that will be prefixed to your resources. Usually it's the project name. _Default value: `Terranext`_. | 126 | | `provider` | `string` | Must be provided | The Cloud Provider. Based on the value, a different configuration will be exported. Supported providers: `AWS` | 127 | | `nextAppDir` | `string` | Must be provided | This is the path where your Next.js project is. Usually you will run `terraform` CLI from a different project/folder. So you need to tell `terraform` where this folder is. The library will take care of the rest. _Default value: `"./"`_ | 128 | | `routes` | `Array`, `Mapping` | Optional | This is the structure of the routes that describe your pages. | 129 | | `env` | `Array` | Optional | Environments passed via CLI have to be split using `,`: `--env="KEY,VALUE"`. When using the API, you always have to pass an array of objects `{ key: "MyKeyName", "value": "MyKeyValue" }`. **_Environment variables are applied to all the lambdas_** | 130 | | `nodeVersion` | `10` or `12` | `10` | Runtime to use 131 | 132 | ### Mapping explained 133 | 134 | These mappings are only needed if you have custom routes. If you don't, `routes` is not needed as this library is able to create mappings from the files that Nextjs generates. 135 | 136 | Let's say we want to describe the following URLs: 137 | 138 | - `/about-us/contacts` 139 | - `/about-us/the-company` 140 | - `/blog/first-blog-post` 141 | - `/blog/second-blog-post` 142 | - `/credits?hideComments`: here, `hideComments` is not mandatory. If it is mandatory, it will be marked `true` in the configuration 143 | 144 | ```js 145 | const routes = [ 146 | { 147 | prefix: "/about-us", 148 | mappings: [ 149 | { 150 | route: "/contacts", // the URL 151 | page: "/companyContacts" // the nextjs file, inside pages folder, that is responsible to render this page 152 | }, 153 | { 154 | route: "/the-company", 155 | page: "/aboutTheCompany" 156 | } 157 | ] 158 | }, 159 | { 160 | prefix: "", 161 | mappings: [ 162 | { 163 | route: "/blog/:url", 164 | page: "/blogPost" 165 | }, 166 | { 167 | route: "/credits", 168 | page: "/credits", 169 | params: { 170 | hideComments: false 171 | } 172 | } 173 | ] 174 | } 175 | ]; 176 | ``` 177 | 178 | ## Providers 179 | 180 | At the moment the project supports only AWS but it's up to support more providers in the future. 181 | 182 | ### AWS 183 | 184 | Once you generate the resource files, you will need to consume them. Also, you will need to create the following resource: 185 | 186 | ```hcl 187 | resource "aws_api_gateway_rest_api" "CustomKey" { 188 | name = "WebApi" 189 | description = "Web API" 190 | } 191 | 192 | locals { 193 | groupname = "WebApi" 194 | lambda_iam_role = "arn:aws:iam::202020202020:role/lambda_execution_role" 195 | aws_region = "${data.aws_region.current.name}" 196 | } 197 | ``` 198 | 199 | Please check the [integration](/integration/aws/api.tf) testing to see how to consume the configuration. 200 | 201 | [build-status-azure]: https://myburning.visualstudio.com/terraform-nextjs-plugin/_apis/build/status/ematipico.terraform-nextjs-plugin?branchName=master 202 | [build-status-azure-url]: https://myburning.visualstudio.com/terraform-nextjs-plugin/_build/latest?definitionId=1&branchName=master 203 | [npm]: https://img.shields.io/npm/v/@ematipico/terraform-nextjs-plugin.svg 204 | [npm-url]: https://www.npmjs.com/package/@ematipico/terraform-nextjs-plugin 205 | [code-quality]: https://api.codacy.com/project/badge/Grade/f77ac77e550449ffb821cd6e7cc4fd72 206 | [code-quality-url]: https://www.codacy.com/app/ematipico/terraform-nextjs-plugin?utm_source=github.com&utm_medium=referral&utm_content=ematipico/terraform-nextjs-plugin&utm_campaign=Badge_Grade 207 | [conventional]: https://img.shields.io/badge/Conventional%20Commits-1.0.0-green.svg 208 | [conventional-url]: https://conventionalcommits.org 209 | [coverage]: https://codecov.io/gh/ematipico/terraform-nextjs-plugin/branch/master/graph/badge.svg 210 | [coverage-url]: https://codecov.io/gh/ematipico/terraform-nextjs-plugin 211 | -------------------------------------------------------------------------------- /bin/terranext.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { cosmiconfig } = require("cosmiconfig"); 4 | const meow = require("meow"); 5 | const generateResources = require("../src"); 6 | 7 | const explorer = cosmiconfig("terranext"); 8 | 9 | const cli = meow( 10 | ` 11 | Usage 12 | $ terranext 13 | 14 | Options 15 | --gateway-key, -g The API Gateway key of the project. Default is "Terranext" 16 | --next-app-dir, -d The path that Terraform CLI has to follow to reach the nextjs project. 17 | --provider The Cloud provider to use when exporting the configuration 18 | --env A way for passing environment variables to the lambdas 19 | 20 | Examples 21 | $ terranext 22 | $ terranext --gateway-key=CustomKey --next-app-dir=../../nextjs-project/ 23 | $ terranext --provider=AWS --next-app-dir=../../nextjs-project/ 24 | $ terranext -g=CustomKey -d=../../nextjs-project/ 25 | $ terranext --env="DEBUG,express:*" --env="API_KEY,1234" 26 | `, 27 | { 28 | flags: { 29 | gatewayKey: { 30 | type: "string", 31 | default: "Terranext", 32 | alias: "g", 33 | }, 34 | // eslint-disable-next-line unicorn/prevent-abbreviations 35 | nextAppDir: { 36 | type: "string", 37 | alias: "d", 38 | default: "./", 39 | }, 40 | provider: { 41 | type: "string", 42 | }, 43 | env: { 44 | type: "string", 45 | isMultiple: true, 46 | }, 47 | }, 48 | }, 49 | ); 50 | 51 | explorer 52 | .search() 53 | .then(async (result) => { 54 | const { gatewayKey, nextAppDir, provider, env } = cli.flags; 55 | let parsedEnvs; 56 | if (env) { 57 | parsedEnvs = parseEnv(env, {}); 58 | } 59 | const options = { 60 | ...result.config, 61 | gatewayKey, 62 | nextAppDir, 63 | provider, 64 | env: parsedEnvs, 65 | }; 66 | await generateResources(options, true); 67 | }) 68 | .catch((error) => { 69 | // eslint-disable-next-line no-console 70 | console.error(error); 71 | process.exit(1); 72 | // Do something constructive. 73 | }); 74 | 75 | function parseEnv(unparsedEnv) { 76 | if (Array.isArray(unparsedEnv)) { 77 | return unparsedEnv.map((env) => { 78 | const splitEnv = env.split(","); 79 | return { 80 | key: splitEnv[0], 81 | value: splitEnv[1], 82 | }; 83 | }); 84 | } 85 | 86 | const splitEnv = unparsedEnv.split(","); 87 | 88 | return [ 89 | { 90 | key: splitEnv[0], 91 | value: splitEnv[1], 92 | }, 93 | ]; 94 | } 95 | -------------------------------------------------------------------------------- /integration/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/integration/.DS_Store -------------------------------------------------------------------------------- /integration/.gitignore: -------------------------------------------------------------------------------- 1 | .next/ 2 | .terraform/ 3 | gateway.terraform.tf.json 4 | lambdas.terraform.tf.json -------------------------------------------------------------------------------- /integration/app/build.js: -------------------------------------------------------------------------------- 1 | const generateResources = require("../../src"); 2 | const routes = require("./routes"); 3 | generateResources( 4 | { 5 | gatewayKey: "CustomKey", 6 | nextAppDir: "../app/", 7 | provider: "AWS", 8 | routes, 9 | env: [ 10 | { 11 | key: "DEBUG", 12 | value: "express:*", 13 | }, 14 | ], 15 | memorySize: "1024", 16 | timeout: "180", 17 | }, 18 | true, 19 | ); 20 | -------------------------------------------------------------------------------- /integration/app/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | target: "serverless", 3 | }; 4 | -------------------------------------------------------------------------------- /integration/app/pages/[pageId]/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function BlogPage() { 4 | return

Blog post page

; 5 | } 6 | 7 | BlogPage.getInitialProps = function () { 8 | return { 9 | name: "foo", 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /integration/app/pages/about-us/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function AboutUs() { 4 | return

Contacts page

; 5 | } 6 | 7 | AboutUs.getInitialProps = function () { 8 | return { 9 | name: "foo", 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /integration/app/pages/blogPost.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function BlogPost() { 4 | return

Blog post page

; 5 | } 6 | 7 | BlogPost.getInitialProps = function () { 8 | return { 9 | name: "foo", 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /integration/app/pages/contact.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Contacts() { 4 | return

Contacts page

; 5 | } 6 | 7 | Contacts.getInitialProps = function () { 8 | return { 9 | name: "foo", 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /integration/app/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Index() { 4 | return

Index page

; 5 | } 6 | 7 | Index.getInitialProps = function () { 8 | return { 9 | name: "foo", 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /integration/app/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | prefix: "", 3 | mappings: [ 4 | { 5 | page: "/blogPost", 6 | route: "/blog/:url", 7 | }, 8 | { 9 | page: "/contact", 10 | route: "/contact-us", 11 | }, 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /integration/aws/api.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | profile = "token" 3 | region = "eu-west-1" 4 | version = "~> 2.0" 5 | } 6 | 7 | locals { 8 | groupname = "WebApi" 9 | 10 | lambda_iam_role = "arn:aws:iam::202020202020:role/lambda_execution_role" 11 | 12 | aws_region = "${data.aws_region.current.name}" 13 | } 14 | 15 | variable "env" { 16 | default = "dev" 17 | } 18 | 19 | resource "aws_api_gateway_rest_api" "CustomKey" { 20 | name = "WebApi" 21 | description = "Web API" 22 | } 23 | 24 | resource "aws_api_gateway_stage" "CustomKey" { 25 | stage_name = "dev" 26 | rest_api_id = "${aws_api_gateway_rest_api.CustomKey.id}" 27 | deployment_id = "${aws_api_gateway_deployment.CustomKey.id}" 28 | } 29 | 30 | resource "aws_api_gateway_deployment" "CustomKey" { 31 | rest_api_id = "${aws_api_gateway_rest_api.CustomKey.id}" 32 | stage_name = "dev" 33 | } 34 | -------------------------------------------------------------------------------- /integration/terraform: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/integration/terraform -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ematipico/terraform-nextjs-plugin", 3 | "version": "4.0.0", 4 | "description": "A nextjs plugin to generate cloud providers configuration for Terraform", 5 | "main": "src/index.js", 6 | "bin": "bin/terranext.js", 7 | "scripts": { 8 | "test": "jest --watch", 9 | "test:ci-unix": "CI=true jest --colors --coverage --maxWorkers=4 --reporters=default --reporters=jest-junit && codecov -t 260305d4-6357-42d2-a73c-4c9b255d278a", 10 | "test:ci-win": "jest --colors --coverage --maxWorkers=4 --reporters=default --reporters=jest-junit", 11 | "release": "standard-version", 12 | "ci": "rome ci . && pnpm run lint:types", 13 | "lint": "pnpm run lint:files && pnpm run lint:types", 14 | "lint:files": "rome check ./src ./bin ./tests", 15 | "lint:fix": "pnpm run lint:files -- --apply-suggested", 16 | "lint:types": "tsc", 17 | "integration:build": "cd ./integration/app; node ./build.js" 18 | }, 19 | "typings": "./src/index.d.ts", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/ematipico/terraform-nextjs-plugin.git" 23 | }, 24 | "keywords": [ 25 | "nextjs", 26 | "terraform", 27 | "serverless", 28 | "plugin", 29 | "aws", 30 | "cloud", 31 | "providers" 32 | ], 33 | "author": "Emanuele Stoppa", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/ematipico/terraform-nextjs-plugin/issues" 37 | }, 38 | "homepage": "https://github.com/ematipico/terraform-nextjs-plugin#readme", 39 | "devDependencies": { 40 | "@types/jest": "26.0.20", 41 | "@types/node": "14.14.28", 42 | "codecov": "3.8.1", 43 | "jest": "26.6.3", 44 | "jest-junit": "12.0.0", 45 | "next": "9.4.4", 46 | "react": "16.13.1", 47 | "react-dom": "16.13.1", 48 | "rome": "0.10.1-next", 49 | "standard-version": "9.1.0", 50 | "typescript": "4.1.5" 51 | }, 52 | "dependencies": { 53 | "cosmiconfig": "7.0.0", 54 | "meow": "9.0.0" 55 | }, 56 | "engines": { 57 | "node": ">= 12.x.x" 58 | }, 59 | "peerDependencies": { 60 | "next": ">=9.3.2" 61 | }, 62 | "files": [ 63 | "bin/*", 64 | "src/*" 65 | ] 66 | } -------------------------------------------------------------------------------- /rome.json: -------------------------------------------------------------------------------- 1 | { 2 | "linter": { 3 | "enabled": true, 4 | "ignore": [ 5 | "integration/app/.next" 6 | ], 7 | "rules": { 8 | "recommended": true 9 | } 10 | }, 11 | "formatter": { 12 | "lineWidth": 140, 13 | "ignore": [ 14 | "integration/app/.next" 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /src/compatLayer.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /** 3 | * Credits to https://github.com/danielcondemarin 4 | * https://github.com/danielcondemarin/serverless-nextjs-plugin/blob/master/packages/next-aws-lambda/lib/compatLayer.js 5 | */ 6 | const Stream = require("stream"); 7 | const queryString = require("querystring"); 8 | 9 | const requestResponseMapper = (event, callback) => { 10 | const base64Support = process.env.BINARY_SUPPORT === "yes"; 11 | const response = { 12 | body: Buffer.from(""), 13 | isBase64Encoded: base64Support, 14 | statusCode: 200, 15 | multiValueHeaders: {}, 16 | }; 17 | 18 | const request = new Stream.Readable(); 19 | request.url = (event.requestContext.path || event.path || "").replace(new RegExp(`^/${event.requestContext.stage}`), ""); 20 | 21 | let qs = ""; 22 | 23 | if (event.multiValueQueryStringParameters) { 24 | qs += queryString.stringify(event.multiValueQueryStringParameters); 25 | } 26 | 27 | if (event.pathParameters) { 28 | const pathParametersQs = queryString.stringify(event.pathParameters); 29 | 30 | if (qs.length > 0) { 31 | qs += `&${pathParametersQs}`; 32 | } else { 33 | qs += pathParametersQs; 34 | } 35 | } 36 | 37 | const hasQueryString = qs.length > 0; 38 | 39 | if (hasQueryString) { 40 | request.url += `?${qs}`; 41 | } 42 | 43 | request.method = event.httpMethod; 44 | request.rawHeaders = []; 45 | request.headers = {}; 46 | 47 | const headers = event.multiValueHeaders || {}; 48 | 49 | for (const key of Object.keys(headers)) { 50 | for (const value of headers[key]) { 51 | request.rawHeaders.push(key); 52 | request.rawHeaders.push(value); 53 | } 54 | request.headers[key.toLowerCase()] = headers[key].toString(); 55 | } 56 | 57 | request.getHeader = (name) => { 58 | return request.headers[name.toLowerCase()]; 59 | }; 60 | request.getHeaders = () => { 61 | return request.headers; 62 | }; 63 | 64 | request.connection = {}; 65 | 66 | // eslint-disable-next-line unicorn/prevent-abbreviations 67 | const res = new Stream(); 68 | Object.defineProperty(res, "statusCode", { 69 | get() { 70 | return response.statusCode; 71 | }, 72 | set(statusCode) { 73 | response.statusCode = statusCode; 74 | }, 75 | }); 76 | res.headers = {}; 77 | res.writeHead = (status, headers) => { 78 | response.statusCode = status; 79 | if (headers) { 80 | res.headers = Object.assign(res.headers, headers); 81 | } 82 | }; 83 | res.write = (chunk) => { 84 | response.body = Buffer.concat([response.body, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]); 85 | }; 86 | res.setHeader = (name, value) => { 87 | res.headers[name] = value; 88 | }; 89 | res.removeHeader = (name) => { 90 | res.headers[name] = undefined; 91 | }; 92 | res.getHeader = (name) => { 93 | return res.headers[name.toLowerCase()]; 94 | }; 95 | res.getHeaders = () => { 96 | return res.headers; 97 | }; 98 | res.end = (text) => { 99 | if (text) { 100 | res.write(text); 101 | } 102 | response.body = Buffer.from(response.body).toString(base64Support ? "base64" : undefined); 103 | response.multiValueHeaders = res.headers; 104 | res.writeHead(response.statusCode); 105 | fixApiGatewayMultipleHeaders(); 106 | callback(undefined, response); 107 | }; 108 | if (event.body) { 109 | request.push(event.body, event.isBase64Encoded ? "base64" : undefined); 110 | request.push(); 111 | } 112 | 113 | // eslint-disable-next-line unicorn/consistent-function-scoping 114 | function fixApiGatewayMultipleHeaders() { 115 | for (const key of Object.keys(response.multiValueHeaders)) { 116 | if (!Array.isArray(response.multiValueHeaders[key])) { 117 | response.multiValueHeaders[key] = [response.multiValueHeaders[key]]; 118 | } 119 | } 120 | } 121 | 122 | // eslint-disable-next-line unicorn/prevent-abbreviations 123 | return { req: request, res }; 124 | }; 125 | 126 | module.exports = requestResponseMapper; 127 | -------------------------------------------------------------------------------- /src/configuration.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/prevent-abbreviations */ 2 | const MissingKeyError = require("./errors/missingKeyError"); 3 | const ProviderNotSupported = require("./errors/providerNotSupported"); 4 | const IncorrectRoutesError = require("./errors/incorretRoutesError"); 5 | const EmptyConfigurationError = require("./errors/emptyConfigurationError"); 6 | const InvalidMemorySize = require("./errors/invalidMemorySize"); 7 | const InvalidTimeout = require("./errors/invalidTimeout"); 8 | const { PROVIDERS, NEXT_CONFIG } = require("./constants"); 9 | const path = require("path"); 10 | const fs = require("fs"); 11 | 12 | /** 13 | * @typedef {import('./errors/errors').ValidationError} ValidationError 14 | * @typedef {import('./index').Route} Route 15 | * @typedef {import('./index').Configuration} GlobalConfiguration 16 | */ 17 | 18 | class Configuration { 19 | /** 20 | * 21 | * @param {GlobalConfiguration} config 22 | */ 23 | constructor(config) { 24 | Configuration.checkConfiguration(config); 25 | const { gatewayKey, nextAppDir, routes, provider, buildPath } = config; 26 | this.properties = { 27 | ...config, 28 | gatewayKey: gatewayKey || "Terranext", 29 | buildPath: buildPath || ".next", 30 | provider, 31 | nextAppDir: nextAppDir ? path.resolve(process.cwd(), nextAppDir) : "./", 32 | routes, 33 | }; 34 | } 35 | 36 | /** 37 | * 38 | * 39 | * @param {GlobalConfiguration=} config 40 | * @returns {Boolean|ValidationError[]} 41 | */ 42 | static checkConfiguration(config) { 43 | let errors = []; 44 | if (!config) { 45 | errors.push(new EmptyConfigurationError()); 46 | return errors; 47 | } 48 | const { gatewayKey, nextAppDir, routes, provider, memorySize, timeout } = config; 49 | 50 | if (!gatewayKey) { 51 | errors.push(new MissingKeyError("gatewayKey")); 52 | } 53 | if (!nextAppDir) { 54 | errors.push(new MissingKeyError("nextAppDir")); 55 | } 56 | 57 | if (routes) { 58 | if (Array.isArray(routes)) { 59 | const isInvalid = routes.some((r) => Configuration.checkRoutes(r) === false); 60 | if (isInvalid === true) { 61 | errors.push(new IncorrectRoutesError()); 62 | } 63 | } else { 64 | if (!Configuration.checkRoutes(routes)) { 65 | errors.push(new IncorrectRoutesError()); 66 | } 67 | } 68 | } 69 | 70 | if (!provider) { 71 | errors.push(new MissingKeyError("provider")); 72 | } 73 | 74 | if (!Object.keys(PROVIDERS).includes(provider)) { 75 | errors.push(new ProviderNotSupported(provider)); 76 | } 77 | 78 | if (memorySize) { 79 | Number.isNaN(Number(memorySize)) && errors.push(new InvalidMemorySize()); 80 | } 81 | 82 | if (timeout) { 83 | Number.isNaN(Number(timeout)) && errors.push(new InvalidTimeout()); 84 | } 85 | 86 | if (errors.length > 0) { 87 | return errors; 88 | } 89 | 90 | return true; 91 | } 92 | 93 | get getConfiguration() { 94 | return this.properties; 95 | } 96 | 97 | static checkRoutes(routes) { 98 | let valid = true; 99 | 100 | if (typeof routes.prefix === "undefined" || typeof routes.mappings === "undefined") { 101 | return false; 102 | } 103 | 104 | if (typeof routes.prefix !== "string") { 105 | return false; 106 | } 107 | 108 | valid = routes.mappings.every((mapping) => { 109 | return mapping.route && mapping.page; 110 | }); 111 | 112 | return valid; 113 | } 114 | 115 | /** 116 | * @returns {string} 117 | */ 118 | getLambdaPath() { 119 | return path.resolve(this.properties.nextAppDir, this.properties.buildPath, "lambdas"); 120 | } 121 | 122 | /** 123 | * @returns {string} 124 | */ 125 | getServerlessPagesPath() { 126 | return path.resolve(this.properties.nextAppDir, this.properties.buildPath, "serverless", "pages"); 127 | } 128 | 129 | /** 130 | * @returns {string} 131 | */ 132 | getGatewayKey() { 133 | return this.properties.gatewayKey; 134 | } 135 | 136 | /** 137 | * 138 | * @returns {Route[]} 139 | */ 140 | getRoutes() { 141 | if (Array.isArray(this.properties.routes)) { 142 | return this.properties.routes; 143 | } 144 | return [this.properties.routes]; 145 | } 146 | 147 | /** 148 | * @returns {string} 149 | */ 150 | getBuildPath() { 151 | return path.resolve(process.cwd(), this.properties.buildPath); 152 | } 153 | 154 | /** 155 | * 156 | * @returns {string} 157 | */ 158 | getServerlessBuildPath() { 159 | return path.resolve(process.cwd(), this.properties.buildPath, "serverless/pages"); 160 | } 161 | 162 | /** 163 | * 164 | * @returns {any} 165 | */ 166 | getNextConfig() { 167 | const nextConfigFilePath = path.resolve(this.properties.nextAppDir, NEXT_CONFIG); 168 | if (fs.existsSync(nextConfigFilePath)) { 169 | return require(nextConfigFilePath); 170 | } 171 | throw new Error(`Missing config file inside the Next.js folder: ${nextConfigFilePath}`); 172 | } 173 | 174 | getNextAppDir() { 175 | return this.properties.nextAppDir; 176 | } 177 | 178 | getNodeVersion() { 179 | switch (this.properties.nodeVersion) { 180 | case "8": { 181 | return "nodejs8.10"; 182 | } 183 | case "10": { 184 | return "nodejs10.x"; 185 | } 186 | 187 | case "12": { 188 | return "nodejs12.x"; 189 | } 190 | 191 | default: 192 | return "nodejs8.10"; 193 | } 194 | } 195 | 196 | getMemorySize() { 197 | return this.properties.memorySize || "1024"; 198 | } 199 | 200 | getTimeout() { 201 | return this.properties.timeout || "180"; 202 | } 203 | 204 | hasEnvs() { 205 | return Boolean(this.properties.env); 206 | } 207 | 208 | getEnvs() { 209 | return this.properties.env.reduce((result, env) => { 210 | result[env.key] = env.value; 211 | return result; 212 | }, {}); 213 | } 214 | } 215 | 216 | module.exports = Configuration; 217 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const PROVIDERS = { 4 | AWS: "AWS", 5 | }; 6 | 7 | const FILE_NAMES = { 8 | LAMBDAS: "lambdas.terraform.tf.json", 9 | GATEWAY: "gateway.terraform.tf.json", 10 | }; 11 | 12 | const NEXT_CONFIG = "next.config.js"; 13 | const COMPAT_LAYER_PATH = path.resolve(__dirname); 14 | 15 | module.exports = { 16 | PROVIDERS, 17 | FILE_NAMES, 18 | NEXT_CONFIG, 19 | COMPAT_LAYER_PATH, 20 | }; 21 | -------------------------------------------------------------------------------- /src/errors/emptyConfigurationError.js: -------------------------------------------------------------------------------- 1 | const ValidationError = require("./validationError"); 2 | 3 | class EmptyConfigurationError extends ValidationError { 4 | constructor() { 5 | super(); 6 | this.type = "EmptyConfigurationError"; 7 | this.message = "Empty configuration, cannot proceed."; 8 | } 9 | } 10 | 11 | module.exports = EmptyConfigurationError; 12 | -------------------------------------------------------------------------------- /src/errors/errors.d.ts: -------------------------------------------------------------------------------- 1 | export declare class ValidationError extends Error {} 2 | -------------------------------------------------------------------------------- /src/errors/folderNotFoundError.js: -------------------------------------------------------------------------------- 1 | class FolderNotFoundError extends Error { 2 | constructor(folderName, error) { 3 | super(); 4 | this.name = "FolderNotFoundError"; 5 | this.message = `${folderName} doesn't exist`; 6 | this.stack = error.stack; 7 | } 8 | } 9 | 10 | module.exports = FolderNotFoundError; 11 | -------------------------------------------------------------------------------- /src/errors/incorretRoutesError.js: -------------------------------------------------------------------------------- 1 | const ValidationError = require("./validationError"); 2 | 3 | class IncorrectRoutesError extends ValidationError { 4 | constructor() { 5 | super(); 6 | this.type = "IncorrectRoutesError"; 7 | this.message = "The object containing the routes is not correct"; 8 | } 9 | } 10 | 11 | module.exports = IncorrectRoutesError; 12 | -------------------------------------------------------------------------------- /src/errors/invalidMemorySize.js: -------------------------------------------------------------------------------- 1 | const ValidationError = require("./validationError"); 2 | 3 | class InvalidMemorySize extends ValidationError { 4 | constructor() { 5 | super(); 6 | this.type = "InvalidMemorySize"; 7 | this.message = "memorySize value is invalid, if it is provided, it must be a string containing a number between 128 and 10240"; 8 | } 9 | } 10 | 11 | module.exports = InvalidMemorySize; 12 | -------------------------------------------------------------------------------- /src/errors/invalidTimeout.js: -------------------------------------------------------------------------------- 1 | const ValidationError = require("./validationError"); 2 | 3 | class InvalidTimeout extends ValidationError { 4 | constructor() { 5 | super(); 6 | this.type = "InvalidTimeout"; 7 | this.message = "timeout value is invalid, if it is provided, it must be a string containing a number smaller than 900"; 8 | } 9 | } 10 | 11 | module.exports = InvalidTimeout; 12 | -------------------------------------------------------------------------------- /src/errors/missingKeyError.js: -------------------------------------------------------------------------------- 1 | const ValidationError = require("./validationError"); 2 | 3 | class MissingKeyError extends ValidationError { 4 | constructor(key) { 5 | super(); 6 | this.key = key; 7 | this.type = "MissingKeyError"; 8 | this.message = `${key} is missing, it must be provided`; 9 | } 10 | } 11 | 12 | module.exports = MissingKeyError; 13 | -------------------------------------------------------------------------------- /src/errors/providerNotSupported.js: -------------------------------------------------------------------------------- 1 | const { PROVIDERS } = require("../constants"); 2 | const ValidationError = require("./validationError"); 3 | 4 | class ProviderNotSupported extends ValidationError { 5 | constructor(provider) { 6 | super(); 7 | this.provider = provider; 8 | this.type = "ProviderNotSupported"; 9 | this.message = `${provider} provider is not supported. Choose between: ${Object.keys(PROVIDERS).join(", ")}`; 10 | } 11 | } 12 | 13 | module.exports = ProviderNotSupported; 14 | -------------------------------------------------------------------------------- /src/errors/validationError.js: -------------------------------------------------------------------------------- 1 | class ValidationError extends Error { 2 | constructor() { 3 | super(); 4 | this.name = "ValidationError"; 5 | } 6 | } 7 | 8 | module.exports = ValidationError; 9 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from "./providers/aws/aws.declarations"; 2 | import { Param } from "./providers/aws/declarations"; 3 | 4 | export interface Configuration { 5 | gatewayKey?: string; 6 | nextAppDir?: string; 7 | routes?: Route[] | Route; 8 | buildPath?: string; 9 | provider: "AWS"; 10 | memorySize?: string; 11 | timeout?: string; 12 | nodeVersion?: "8" | "10" | "12"; 13 | env?: EnvParam[]; 14 | } 15 | 16 | export interface EnvParam { 17 | key: string; 18 | value: string; 19 | } 20 | 21 | export interface Result { 22 | gateway: G; 23 | lambdas: L; 24 | } 25 | 26 | export interface Route { 27 | prefix?: string; 28 | 29 | mappings: Mapping[]; 30 | } 31 | 32 | export interface Mapping { 33 | page: string; 34 | 35 | route: string; 36 | 37 | params?: Param[]; 38 | } 39 | 40 | export interface GatewayResources { 41 | resource: { 42 | aws_api_gateway_resource: AWS.Resource; 43 | aws_api_gateway_method: AWS.Method; 44 | aws_api_gateway_integration: AWS.Integration; 45 | }; 46 | variable: { 47 | integrationList: { 48 | default: string[]; 49 | }; 50 | }; 51 | } 52 | 53 | declare function terranext(configuration: Configuration, write: boolean): Promise; 54 | declare function terranext(configuration: Configuration): Promise>; 55 | declare function terranext(): Promise>; 56 | 57 | export default terranext; 58 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const AwsConfig = require("./providers/aws/awsConfig"); 2 | // @ts-ignore 3 | const AwsResources = require("./providers/aws"); 4 | // @ts-ignore 5 | const { cosmiconfig } = require("cosmiconfig"); 6 | // @ts-ignore 7 | const build = require("next/dist/build").default; 8 | 9 | /** 10 | * @typedef {import("./index").Configuration} Configuration 11 | * @typedef {import("./index").Result} Result 12 | */ 13 | 14 | /** 15 | * 16 | * @param {Configuration} configuration The configuration needed to generate the resources 17 | * @param {boolean} [write=false] 18 | * @returns {Promise} 19 | */ 20 | async function terranext(configuration, write = false) { 21 | try { 22 | /** 23 | * @type {Configuration} 24 | */ 25 | const fileConfiguration = await retrieveConfiguration(); 26 | /** 27 | * 28 | * @type {Configuration} 29 | */ 30 | const finalConfiguration = { 31 | ...fileConfiguration, 32 | ...configuration, 33 | }; 34 | const config = new AwsConfig(finalConfiguration); 35 | const nextConfig = config.getNextConfig(); 36 | // @ts-ignore 37 | nextConfig.target = "serverless"; 38 | // @ts-ignore 39 | await build(config.getNextAppDir(), nextConfig); 40 | const aws = new AwsResources(config); 41 | 42 | if (write === true) { 43 | await aws.generateGatewayResources(write); 44 | await aws.generateLambdaResources(write); 45 | } else { 46 | const lambdas = aws.generateLambdaResources(); 47 | const gateway = await aws.generateGatewayResources(); 48 | return { 49 | gateway, 50 | lambdas, 51 | }; 52 | } 53 | } catch (error) { 54 | // eslint-disable-next-line no-console 55 | console.error(error); 56 | process.exit(1); 57 | } 58 | } 59 | 60 | async function retrieveConfiguration() { 61 | const explorer = cosmiconfig("terranext"); 62 | try { 63 | const result = await explorer.search(); 64 | return result.config; 65 | } catch { 66 | return; 67 | } 68 | } 69 | 70 | module.exports = terranext; 71 | -------------------------------------------------------------------------------- /src/providers/aws/aws.declarations.ts: -------------------------------------------------------------------------------- 1 | export declare namespace AWS { 2 | interface Method { 3 | [key: string]: AWS.GatewayMethod; 4 | } 5 | 6 | interface Resource { 7 | [key: string]: AWS.GatewayResource; 8 | } 9 | 10 | interface Integration { 11 | [key: string]: AWS.GatewayIntegration; 12 | } 13 | 14 | interface RequestParameter { 15 | [key: string]: { name: string }; 16 | } 17 | 18 | interface Lambdas { 19 | aws_lambda_function: AWS.LambdaFunction; 20 | aws_lambda_permission: AWS.LambdaPermission; 21 | } 22 | 23 | interface LambdaData { 24 | [key: string]: AWS.Data; 25 | } 26 | 27 | interface Data { 28 | output_path: string; 29 | type: string; 30 | source_dir: string; 31 | } 32 | 33 | interface GatewayMethod { 34 | rest_api_id: string; 35 | resource_id: string; 36 | http_method: string; 37 | authorization: string; 38 | request_parameters?: AWS.RequestParameter; 39 | } 40 | 41 | interface GatewayIntegration { 42 | rest_api_id: string; 43 | resource_id: string; 44 | http_method: string; 45 | integration_http_method: string; 46 | type: string; 47 | uri: string; 48 | request_parameters?: AWS.RequestParameter; 49 | } 50 | 51 | interface GatewayResource { 52 | rest_api_id: string; 53 | parent_id: string; 54 | path_part: string; 55 | } 56 | 57 | interface LambdaFunction { 58 | [key: string]: AWS.Function; 59 | } 60 | 61 | interface Function { 62 | filename: string; 63 | function_name: string; 64 | source_code_hash: string; 65 | handler: string; 66 | runtime: string; 67 | memory_size: string; 68 | timeout: string; 69 | role: string; 70 | environment?: Environment; 71 | } 72 | 73 | interface Environment { 74 | variables: { 75 | [key: string]: string; 76 | }; 77 | } 78 | 79 | interface LambdaPermission { 80 | [key: string]: Permission; 81 | } 82 | 83 | interface Permission { 84 | statement_id: string; 85 | action: string; 86 | function_name: string; 87 | principal: string; 88 | source_arn: string; 89 | } 90 | 91 | interface LambdaResources { 92 | resource?: AWS.Lambdas; 93 | data: { 94 | archive_file: AWS.LambdaData; 95 | }; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/providers/aws/awsConfig.js: -------------------------------------------------------------------------------- 1 | const Configuration = require("../../configuration"); 2 | 3 | /** 4 | * @typedef {import('../../index').Configuration} GlobalConfiguration 5 | */ 6 | class AwsConfig extends Configuration { 7 | /** 8 | * 9 | * @param {GlobalConfiguration} configuration 10 | */ 11 | constructor(configuration) { 12 | super(configuration); 13 | } 14 | 15 | /** 16 | * @returns {string} 17 | */ 18 | getLambdaPrefix() { 19 | return `lambdaFor${this.getGatewayKey()}`; 20 | } 21 | 22 | /** 23 | * @returns {string} 24 | */ 25 | getGatewayResourceId() { 26 | return `\${aws_api_gateway_rest_api.${this.getGatewayKey()}.id}`; 27 | } 28 | 29 | /** 30 | * @returns {string} 31 | */ 32 | getRootResource() { 33 | return `\${aws_api_gateway_rest_api.${this.getGatewayKey()}.root_resource_id}`; 34 | } 35 | } 36 | 37 | module.exports = AwsConfig; 38 | -------------------------------------------------------------------------------- /src/providers/aws/declarations.d.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from "./aws.declarations"; 2 | 3 | export interface Param { 4 | name: string; 5 | mandatory?: boolean; 6 | } 7 | 8 | export interface AwsGatewayOptions { 9 | parentId?: string; 10 | id: string; 11 | isUrlParameter?: boolean; 12 | pathname: string; 13 | params?: Param[]; 14 | queryStringParams?: Param[]; 15 | lambdaName: string; 16 | } 17 | 18 | export interface GenerateLambdaResource { 19 | resourceUniqueId: string; 20 | resource: AWS.Function; 21 | } 22 | 23 | export interface LambdaPermission { 24 | permissionUniqueId: string; 25 | resource: AWS.Permission; 26 | } 27 | 28 | export interface GenerateGatewayResource { 29 | uniqueId: string; 30 | resource: AWS.GatewayResource; 31 | } 32 | 33 | export interface HandleResource { 34 | pathPart: string; 35 | index: number; 36 | parts: string[]; 37 | pathname: string; 38 | lambdaName: string; 39 | params: Param[]; 40 | } 41 | 42 | export interface GenerateGatewayResourcePayload { 43 | id: string; 44 | pathname: string; 45 | parentId?: string; 46 | isUrlParam?: boolean; 47 | } 48 | 49 | export interface GenerateGatewayIntegrationPayload { 50 | id: string; 51 | gatewayResourceId: string; 52 | lambdaName: string; 53 | params?: Param[]; 54 | queryStringParams?: Param[]; 55 | } 56 | 57 | export interface GenerateGatewayMethodPayload { 58 | uniqueName: string; 59 | gatewayResourceId: string; 60 | params?: Param[]; 61 | queryStringParams?: Param[]; 62 | } 63 | 64 | export interface LambdaOptions { 65 | id: string; 66 | directoryName: string; 67 | } 68 | -------------------------------------------------------------------------------- /src/providers/aws/index.js: -------------------------------------------------------------------------------- 1 | const Lambda = require("./resources/lambda"); 2 | const BaseProvider = require("../baseProvider"); 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const { generateMappingsFromFiles, getLambdaFiles } = require("../../shared"); 6 | const Gateway = require("./resources/gateway"); 7 | const { generateUniqueName } = require("../../utils"); 8 | const { FILE_NAMES, COMPAT_LAYER_PATH } = require("../../constants"); 9 | const FolderNotFoundError = require("../../errors/folderNotFoundError"); 10 | 11 | class AwsResources extends BaseProvider { 12 | constructor(config) { 13 | super(config); 14 | this.terraformConfiguration = {}; 15 | this.apiGatewayResource = {}; 16 | this.apiGatewayMethod = {}; 17 | this.apiGatewayIntegration = {}; 18 | this.lambdasResources = {}; 19 | this.lambdasPermissions = {}; 20 | this.lambdaZip = {}; 21 | } 22 | 23 | parseParameters(parameters) { 24 | return Object.keys(parameters).map((parameterKey) => { 25 | return { 26 | name: parameterKey, 27 | mandatory: parameters[parameterKey], 28 | }; 29 | }); 30 | } 31 | 32 | getParametersFromPath(pathname) { 33 | return pathname 34 | .split("/") 35 | .map((pathPart) => { 36 | if (pathPart.includes(":")) { 37 | return { 38 | name: pathPart.replace(":", ""), 39 | mandatory: true, 40 | }; 41 | } 42 | return; 43 | }) 44 | .filter(Boolean); 45 | } 46 | 47 | /** 48 | * 49 | * @param {import("./declarations").HandleResource} payload 50 | */ 51 | handleResource({ pathPart, index, parts, pathname, lambdaName, params }) { 52 | const isUrlParameter = pathPart.includes(":"); 53 | const currentPathName = pathPart.replace(":", ""); 54 | let urlParameters = []; 55 | let queryStringParameters = []; 56 | if (isUrlParameter) { 57 | urlParameters = this.getParametersFromPath(pathname); 58 | } 59 | if (params) { 60 | queryStringParameters = this.parseParameters(params); 61 | } 62 | const gateway = new Gateway(this.config, { 63 | parentId: index > 0 ? generateUniqueName(parts.slice(0, index)) : undefined, 64 | pathname: currentPathName, 65 | isUrlParameter, 66 | id: generateUniqueName(parts.slice(0, index + 1)), 67 | params: urlParameters, 68 | queryStringParams: queryStringParameters, 69 | lambdaName, 70 | }); 71 | 72 | const gatewayResource = gateway.generate(); 73 | 74 | this.apiGatewayResource[gatewayResource.resource.uniqueId] = gatewayResource.resource.resource; 75 | this.apiGatewayMethod[gatewayResource.method.uniqueId] = gatewayResource.method.resource; 76 | this.apiGatewayIntegration[gatewayResource.integration.uniqueId] = gatewayResource.integration.resource; 77 | } 78 | 79 | /* 80 | * 81 | * @param {Route} routeObject 82 | */ 83 | generateResourcesFromRoute(routeObject) { 84 | routeObject.mappings.forEach((currentRoute) => { 85 | const { params, page, route } = currentRoute; 86 | const prefix = routeObject.prefix ? routeObject.prefix : ""; 87 | const pathname = prefix + route; 88 | const lambdaName = page.replace("/", ""); 89 | pathname 90 | .split("/") 91 | .filter(Boolean) 92 | .forEach((pathPart, index, parts) => { 93 | this.handleResource({ 94 | pathPart, 95 | index, 96 | parts, 97 | pathname, 98 | lambdaName, 99 | params, 100 | }); 101 | }); 102 | }); 103 | } 104 | 105 | generateResources(routesObject) { 106 | if (Array.isArray(routesObject)) { 107 | routesObject.forEach((routeObject) => this.generateResourcesFromRoute(routeObject)); 108 | } else { 109 | this.generateResourcesFromRoute(routesObject); 110 | } 111 | } 112 | 113 | deleteDir(pathToDelete) { 114 | if (fs.existsSync(pathToDelete)) { 115 | fs.readdirSync(pathToDelete).forEach((file) => { 116 | const curPath = path.join(pathToDelete, file); 117 | if (fs.lstatSync(curPath).isDirectory()) { 118 | // recurse 119 | this.deleteDir(curPath); 120 | } else { 121 | // delete file 122 | fs.unlinkSync(curPath); 123 | } 124 | }); 125 | fs.rmdirSync(pathToDelete); 126 | } 127 | } 128 | 129 | /** 130 | * 131 | * @param {boolean} write 132 | * @returns {Promise<{}|*>} 133 | */ 134 | async generateGatewayResources(write = false) { 135 | try { 136 | const routes = this.config.getRoutes(); 137 | const lambdaPath = this.config.getServerlessPagesPath(); 138 | const files = await getLambdaFiles(lambdaPath); 139 | const nextRoutes = generateMappingsFromFiles(files); 140 | const finalRoutes = routes ? [...routes, nextRoutes] : nextRoutes; 141 | this.generateResources(finalRoutes); 142 | 143 | this.terraformConfiguration = { 144 | resource: { 145 | aws_api_gateway_resource: this.apiGatewayResource, 146 | aws_api_gateway_method: this.apiGatewayMethod, 147 | aws_api_gateway_integration: this.apiGatewayIntegration, 148 | }, 149 | variable: { 150 | integrationList: { 151 | default: Object.keys(this.apiGatewayIntegration).map((key) => `aws_api_gateway_integration.${key}`), 152 | }, 153 | }, 154 | }; 155 | 156 | if (write) { 157 | // eslint-disable-next-line no-console 158 | console.log(`Generating file ${FILE_NAMES.GATEWAY}`); 159 | fs.writeFileSync(path.join(process.cwd(), FILE_NAMES.GATEWAY), JSON.stringify(this.terraformConfiguration, undefined, 4), { 160 | encoding: "utf-8", 161 | }); 162 | } else { 163 | return this.terraformConfiguration; 164 | } 165 | } catch (error) { 166 | throw new Error(error); 167 | } 168 | } 169 | 170 | generateLambdaResources(write = false) { 171 | const buildPath = this.config.getBuildPath(); 172 | const serverlessBuildPath = this.config.getServerlessBuildPath(); 173 | 174 | if (fs.existsSync(`${buildPath}/lambdas`)) { 175 | this.deleteDir(`${buildPath}/lambdas`); 176 | } 177 | // it creates the folder that will contain the lambdas 178 | fs.mkdirSync(`${buildPath}/lambdas`); 179 | 180 | return getLambdaFiles(serverlessBuildPath) 181 | .then((files) => { 182 | files.forEach((file) => { 183 | const pathToFile = path.resolve(serverlessBuildPath, file); 184 | if (!fs.lstatSync(pathToFile).isDirectory()) { 185 | /** 186 | * 1. create a folder with name of the file 187 | * 2. copy the next file with a suffix .original.js 188 | * 3. create the lambda from the template 189 | * 4. copy the compact layer 190 | * 5. generate the lambda resource 191 | * 6. generate the zip file resource 192 | */ 193 | // 1. 194 | const lambdaName = file.replace(".js", ""); 195 | const lambdaPath = `${path.resolve(buildPath, "lambdas")}/${lambdaName}`; 196 | fs.mkdirSync(lambdaPath); 197 | 198 | // 2. 199 | const newFilename = file.replace(".js", ".original.js"); 200 | fs.copyFileSync(pathToFile, path.resolve(buildPath, "lambdas", lambdaName, newFilename)); 201 | 202 | const lambda = new Lambda(this.config, { 203 | id: lambdaName, 204 | directoryName: lambdaName, 205 | }); 206 | // 3. 207 | lambda.emitLambdaFile(lambdaName, buildPath); 208 | 209 | // 4. 210 | fs.copyFileSync( 211 | path.resolve(COMPAT_LAYER_PATH, "./compatLayer.js"), 212 | path.resolve(buildPath, "lambdas", lambdaName, "compatLayer.js"), 213 | ); 214 | 215 | // 5. 216 | const lambdaResource = lambda.generate(); 217 | this.lambdasResources[lambdaResource.properties.resourceUniqueId] = lambdaResource.properties.resource; 218 | this.lambdasPermissions[lambdaResource.permissions.permissionUniqueId] = lambdaResource.permissions.resource; 219 | this.lambdaZip[lambdaResource.zip.uniqueId] = lambdaResource.zip.resource; 220 | } 221 | }); 222 | // it gets files that are inside the serverless folder created by next 223 | fs.readdirSync(serverlessBuildPath); 224 | 225 | let lambdaResources = { 226 | resource: { 227 | aws_lambda_function: this.lambdasResources, 228 | aws_lambda_permission: this.lambdasPermissions, 229 | }, 230 | data: { 231 | archive_file: this.lambdaZip, 232 | }, 233 | }; 234 | 235 | if (write === true) { 236 | // eslint-disable-next-line no-console 237 | console.log(`Generating file ${FILE_NAMES.LAMBDAS}`); 238 | fs.writeFileSync(path.join(process.cwd(), FILE_NAMES.LAMBDAS), JSON.stringify(lambdaResources, undefined, 4), { 239 | encoding: "utf-8", 240 | }); 241 | } else { 242 | return lambdaResources; 243 | } 244 | }) 245 | .catch((error) => { 246 | throw new FolderNotFoundError(serverlessBuildPath, error); 247 | }); 248 | } 249 | } 250 | 251 | module.exports = AwsResources; 252 | -------------------------------------------------------------------------------- /src/providers/aws/resources/gateway.js: -------------------------------------------------------------------------------- 1 | const GatewayIntegration = require("./gatewayIntegration"); 2 | const GatewayMethod = require("./gatewayMethod"); 3 | const GatewayResource = require("./gatewayResource"); 4 | 5 | /** @typedef {import('../aws.declarations').AWS.GatewayIntegration} GatewayIntegrationObject */ 6 | /** @typedef {import('../declarations').Param} Param */ 7 | /** @typedef {import('../declarations').AwsGatewayOptions} AwsGatewayOptions */ 8 | /** 9 | * @typef {} AwsConfig 10 | */ 11 | 12 | class Gateway { 13 | /** 14 | * 15 | * @param config 16 | * @param {AwsGatewayOptions} options 17 | */ 18 | constructor(config, options) { 19 | this.gatewayIntegration = new GatewayIntegration(config, options); 20 | this.gatewayMethod = new GatewayMethod(config, options); 21 | this.gatewayResource = new GatewayResource(config, options); 22 | } 23 | 24 | /** 25 | * @returns 26 | */ 27 | getResource() { 28 | return this.gatewayResource.generateGatewayResource(); 29 | } 30 | 31 | /** 32 | * 33 | * @param {string} gatewayResourceId 34 | * @returns {{resource: {authorization: string, rest_api_id: *, http_method: string, resource_id: string}, uniqueId: string}} 35 | */ 36 | getMethod(gatewayResourceId) { 37 | return this.gatewayMethod.generateGatewayMethod(gatewayResourceId); 38 | } 39 | 40 | /** 41 | * 42 | * @param {string} gatewayResourceId 43 | * @returns {{uniqueId: string, resource: GatewayIntegrationObject}} 44 | */ 45 | getIntegration(gatewayResourceId) { 46 | return this.gatewayIntegration.generateGatewayIntegration(gatewayResourceId); 47 | } 48 | 49 | /** 50 | * @returns {object} 51 | */ 52 | generate() { 53 | const resource = this.getResource(); 54 | const method = this.getMethod(resource.uniqueId); 55 | const integration = this.getIntegration(resource.uniqueId); 56 | 57 | return { 58 | resource, 59 | method, 60 | integration, 61 | }; 62 | } 63 | } 64 | 65 | module.exports = Gateway; 66 | -------------------------------------------------------------------------------- /src/providers/aws/resources/gatewayIntegration.js: -------------------------------------------------------------------------------- 1 | /** @typedef {import('../aws.declarations').AWS.GatewayIntegration} GatewayIntegrationObject */ 2 | /** @typedef {import('../declarations').Param} Param */ 3 | /** @typedef {import('../declarations').AwsGatewayOptions} AwsGatewayOptions */ 4 | /** 5 | * @typedef {import('../awsConfig')} AwsConfig 6 | */ 7 | 8 | class GatewayIntegration { 9 | /** 10 | * 11 | * @param {AwsConfig} config 12 | * @param {AwsGatewayOptions} options 13 | */ 14 | constructor(config, options) { 15 | this.config = config; 16 | this.options = options; 17 | } 18 | /** 19 | * It generates the integration resource 20 | * 21 | * @param {string} gatewayResourceId 22 | * @returns {{ uniqueId: string, resource: GatewayIntegrationObject }} 23 | */ 24 | generateGatewayIntegration(gatewayResourceId) { 25 | return { 26 | uniqueId: `${this.config.getGatewayKey()}-${this.options.id}`, 27 | resource: this.generateResource(gatewayResourceId), 28 | }; 29 | } 30 | 31 | /** 32 | * 33 | * 34 | * @param {string} gatewayResourceId 35 | * @returns {GatewayIntegrationObject} 36 | */ 37 | generateResource(gatewayResourceId) { 38 | const resource = { 39 | rest_api_id: `\${aws_api_gateway_rest_api.${this.config.getGatewayKey()}.id}`, 40 | resource_id: `\${aws_api_gateway_resource.${gatewayResourceId}.id}`, 41 | http_method: "GET", 42 | integration_http_method: "POST", 43 | type: "AWS_PROXY", 44 | uri: `arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.${this.config.getLambdaPrefix()}-${ 45 | this.options.lambdaName 46 | }.arn}/invocations`, 47 | }; 48 | 49 | if (this.options.params && this.options.params.length > 0) { 50 | resource.request_parameters = this.options.params.reduce((result, parameter) => { 51 | result[`integration.request.path.${parameter.name}`] = `method.request.path.${parameter.name}`; 52 | 53 | return result; 54 | }, resource.request_parameters || {}); 55 | } 56 | 57 | if (this.options.queryStringParams && this.options.queryStringParams.length > 0) { 58 | resource.request_parameters = this.options.queryStringParams.reduce((result, parameter) => { 59 | result[`integration.request.querystring.${parameter.name}`] = `method.request.querystring.${parameter.name}`; 60 | 61 | return result; 62 | }, resource.request_parameters || {}); 63 | } 64 | 65 | return resource; 66 | } 67 | } 68 | 69 | module.exports = GatewayIntegration; 70 | -------------------------------------------------------------------------------- /src/providers/aws/resources/gatewayMethod.js: -------------------------------------------------------------------------------- 1 | /** @typedef {import('../declarations').AwsGatewayOptions} AwsGatewayOptions */ 2 | /** 3 | * @typedef {import('../awsConfig')} AwsConfig 4 | */ 5 | class GatewayMethod { 6 | /** 7 | * 8 | * @param {AwsConfig} config 9 | * @param {AwsGatewayOptions} options 10 | */ 11 | constructor(config, options) { 12 | this.config = config; 13 | this.options = options; 14 | } 15 | 16 | /** 17 | * It generates the resource for the single method 18 | * 19 | * @returns 20 | */ 21 | generateGatewayMethod(gatewayResourceId) { 22 | return { 23 | uniqueId: `${this.config.getGatewayKey()}-${this.options.id}`, 24 | resource: this.generateResource(gatewayResourceId), 25 | }; 26 | } 27 | 28 | /** 29 | * 30 | * @param {string} resourceId 31 | */ 32 | generateResource(resourceId) { 33 | const resource = { 34 | rest_api_id: this.config.getGatewayResourceId(), 35 | resource_id: `\${aws_api_gateway_resource.${resourceId}.id}`, 36 | http_method: "GET", 37 | authorization: "NONE", 38 | }; 39 | if (this.options.params && this.options.params.length > 0) { 40 | resource.request_parameters = this.options.params.reduce((result, parameter) => { 41 | result[`method.request.path.${parameter.name}`] = parameter.mandatory; 42 | 43 | return result; 44 | }, resource.request_parameters || {}); 45 | } 46 | if (this.options.queryStringParams && this.options.queryStringParams.length > 0) { 47 | resource.request_parameters = this.options.queryStringParams.reduce((result, parameter) => { 48 | result[`method.request.querystring.${parameter.name}`] = parameter.mandatory; 49 | 50 | return result; 51 | }, resource.request_parameters || {}); 52 | } 53 | return resource; 54 | } 55 | } 56 | 57 | module.exports = GatewayMethod; 58 | -------------------------------------------------------------------------------- /src/providers/aws/resources/gatewayResource.js: -------------------------------------------------------------------------------- 1 | /** @typedef {import('../declarations').AwsGatewayOptions} AwsGatewayOptions */ 2 | /** 3 | * @typedef {import('../awsConfig')} AwsConfig 4 | */ 5 | class GatewayResource { 6 | /** 7 | * 8 | * @param {AwsConfig} config 9 | * @param {AwsGatewayOptions} options 10 | */ 11 | constructor(config, options) { 12 | this.config = config; 13 | this.options = options; 14 | this.parentResourceName = this.createUniqueId(this.options.parentId); 15 | } 16 | 17 | /** 18 | * It generates the ApiGateway resource 19 | * @returns 20 | */ 21 | generateGatewayResource() { 22 | return { 23 | uniqueId: this.generateUniqueId(), 24 | resource: this.generateResource(), 25 | }; 26 | } 27 | 28 | /** 29 | * 30 | * @returns string 31 | */ 32 | generateUniqueId() { 33 | return `${this.config.getGatewayKey()}-${this.options.id}`; 34 | } 35 | 36 | /** 37 | * 38 | * @returns string 39 | */ 40 | createUniqueId(id) { 41 | if (!id) { 42 | return; 43 | } 44 | return `${this.config.getGatewayKey()}-${id}`; 45 | } 46 | 47 | /** 48 | * It generates the single resource 49 | */ 50 | generateResource() { 51 | return { 52 | rest_api_id: this.config.getGatewayResourceId(), 53 | parent_id: this.options.parentId ? `\${aws_api_gateway_resource.${this.parentResourceName}.id}` : this.config.getRootResource(), 54 | path_part: this.options.isUrlParameter ? `{${this.options.pathname}}` : this.options.pathname, 55 | }; 56 | } 57 | } 58 | 59 | module.exports = GatewayResource; 60 | -------------------------------------------------------------------------------- /src/providers/aws/resources/lambda.js: -------------------------------------------------------------------------------- 1 | const LambdaPermission = require("./lambdaPermission"); 2 | const LambdaZip = require("./lambdaZip"); 3 | const LambdaProperties = require("./lambdaProperties"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | 7 | /** 8 | * @typedef {import('../awsConfig')} AwsConfig 9 | */ 10 | 11 | class Lambda { 12 | /** 13 | * 14 | * @param {AwsConfig} config 15 | * @param options 16 | */ 17 | constructor(config, options) { 18 | this.config = config; 19 | this.options = options; 20 | this.properties = new LambdaProperties(this.config, this.options); 21 | this.zip = new LambdaZip(this.config, this.options); 22 | this.permisssions = new LambdaPermission(this.config, this.options); 23 | } 24 | 25 | emitLambdaFile(filename, thePath) { 26 | const lambdaTemplate = ` 27 | const compatLayer = require('./compatLayer.js'); 28 | const page = require('./${filename}.original.js'); 29 | 30 | exports.render = (event, context, callback) => { 31 | const { req, res } = compatLayer(event, callback); 32 | page.render(req, res); 33 | }`; 34 | 35 | fs.writeFileSync(path.resolve(thePath, "lambdas", filename, `${filename}.js`), lambdaTemplate, { 36 | encoding: "utf-8", 37 | }); 38 | } 39 | 40 | generate() { 41 | const properties = this.properties.generateLambdaProperties(); 42 | const zip = this.zip.generateZipResource(); 43 | const permissions = this.permisssions.generateLambdaPermissions(); 44 | 45 | return { 46 | properties, 47 | zip, 48 | permissions, 49 | }; 50 | } 51 | } 52 | module.exports = Lambda; 53 | -------------------------------------------------------------------------------- /src/providers/aws/resources/lambdaPermission.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('../awsConfig')} AwsConfig 3 | */ 4 | /** 5 | * @typedef {import('../declarations').LambdaOptions} LambdaOptions 6 | */ 7 | 8 | class LambdaPermission { 9 | /** 10 | * 11 | * @param {AwsConfig} config 12 | * @param {LambdaOptions} options 13 | */ 14 | constructor(config, options) { 15 | this.config = config; 16 | this.options = options; 17 | } 18 | 19 | /** 20 | * It generates the Lambda resource 21 | * 22 | * @returns {import('../declarations').LambdaPermission} 23 | */ 24 | generateLambdaPermissions() { 25 | const cleanedId = this.options.id.replace(/\[|]/g, ""); 26 | const lambdaId = `${this.config.getLambdaPrefix()}-${cleanedId}`; 27 | return { 28 | permissionUniqueId: lambdaId, 29 | resource: { 30 | statement_id: "AllowExecutionFromAPIGateway", 31 | action: "lambda:InvokeFunction", 32 | function_name: `\${aws_lambda_function.${lambdaId}.function_name}`, 33 | principal: "apigateway.amazonaws.com", 34 | source_arn: `\${aws_api_gateway_rest_api.${this.config.getGatewayKey()}.execution_arn}/*/*/*`, 35 | }, 36 | }; 37 | } 38 | } 39 | 40 | module.exports = LambdaPermission; 41 | -------------------------------------------------------------------------------- /src/providers/aws/resources/lambdaProperties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('../awsConfig')} AwsConfig 3 | */ 4 | /** 5 | * @typedef {import('../declarations').LambdaOptions} LambdaOptions 6 | */ 7 | class LambdaProperties { 8 | /** 9 | * 10 | * @param {AwsConfig} config 11 | * @param {LambdaOptions} options 12 | */ 13 | constructor(config, options) { 14 | this.config = config; 15 | this.options = options; 16 | } 17 | 18 | /** 19 | * It generates the Lambda resource 20 | * 21 | * @returns {import('../declarations').GenerateLambdaResource} 22 | */ 23 | generateLambdaProperties() { 24 | const cleanedId = this.options.id.replace(/\[|]/g, ""); 25 | const lambdaId = `${this.config.getLambdaPrefix()}-${cleanedId}`; 26 | const resource = { 27 | resourceUniqueId: lambdaId, 28 | resource: { 29 | filename: `\${data.archive_file.packLambda-${cleanedId}.output_path}`, 30 | function_name: `\${local.groupname}-${cleanedId}`, 31 | source_code_hash: `\${data.archive_file.packLambda-${cleanedId}.output_base64sha256}`, 32 | handler: `${this.options.id}.render`, 33 | runtime: this.config.getNodeVersion(), 34 | memory_size: this.config.getMemorySize(), 35 | timeout: this.config.getTimeout(), 36 | role: "${local.lambda_iam_role}", 37 | }, 38 | }; 39 | if (this.config.hasEnvs()) { 40 | resource.resource.environment = { 41 | variables: this.config.getEnvs(), 42 | }; 43 | } 44 | 45 | return resource; 46 | } 47 | } 48 | 49 | module.exports = LambdaProperties; 50 | -------------------------------------------------------------------------------- /src/providers/aws/resources/lambdaZip.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | /** 3 | * @typedef {import('../awsConfig')} AwsConfig 4 | */ 5 | /** @typedef {import('../aws.declarations').AWS.Data} Data */ 6 | /** @typedef {{ uniqueId: string; resource: Data }} Result */ 7 | /** 8 | * @typedef {import('../declarations').LambdaOptions} LambdaOptions 9 | */ 10 | 11 | class LambdaZip { 12 | /** 13 | * 14 | * @param {AwsConfig} config 15 | * @param {LambdaOptions} options 16 | */ 17 | constructor(config, options) { 18 | this.config = config; 19 | this.options = options; 20 | } 21 | 22 | /** 23 | * It generates the Lambda resource 24 | * @returns {Result} 25 | */ 26 | generateZipResource() { 27 | const cleanedId = this.options.id.replace(/\[|]/g, ""); 28 | 29 | return { 30 | uniqueId: `packLambda-${cleanedId}`, 31 | resource: { 32 | output_path: `files/\${local.groupname}-${this.options.id}.zip`, 33 | type: "zip", 34 | // eslint-disable-next-line unicorn/prevent-abbreviations 35 | source_dir: path.join(this.config.getLambdaPath(), this.options.directoryName), 36 | }, 37 | }; 38 | } 39 | } 40 | 41 | module.exports = LambdaZip; 42 | -------------------------------------------------------------------------------- /src/providers/baseProvider.js: -------------------------------------------------------------------------------- 1 | class BaseProvider { 2 | constructor(config) { 3 | this.config = config; 4 | } 5 | } 6 | 7 | module.exports = BaseProvider; 8 | -------------------------------------------------------------------------------- /src/shared.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const { promisify } = require("util"); 4 | 5 | /** @typedef {import('./index').Route} Route */ 6 | 7 | /** 8 | * 9 | * @param {string} fileName 10 | * @param {string} [pathPart] 11 | * @returns {string} 12 | */ 13 | function generatePathFromFile(fileName, pathPart) { 14 | if (fileName.includes("index") && pathPart) { 15 | const parts = pathPart.split("/"); 16 | return `/${parts[parts.length - 1].replace(".js", "").replace(":", "").replace(/\[/gm, "").replace(/]/gm, "")}`; 17 | } 18 | return `/${fileName.replace(".js", "").replace(/\[/gm, "").replace(/]/gm, "")}`; 19 | } 20 | 21 | /** 22 | * It returns the files inside a folder 23 | * 24 | * @param {string} lambdaPath The Path to the lambdas 25 | * @returns {Promise} 26 | */ 27 | async function getLambdaFiles(lambdaPath) { 28 | try { 29 | const readDirectory = promisify(fs.readdir); 30 | 31 | const files = await readDirectory(lambdaPath); 32 | 33 | return files.map((file) => { 34 | const pathToFile = path.resolve(lambdaPath, file); 35 | if (fs.existsSync(pathToFile) && !fs.lstatSync(pathToFile).isDirectory()) { 36 | return file; 37 | } 38 | return file; 39 | }); 40 | } catch { 41 | return []; 42 | } 43 | } 44 | 45 | /** 46 | * It returns a Route object from a list of files 47 | * 48 | * @param {string[]} files An array of file names 49 | * @returns {Route} 50 | */ 51 | function generateMappingsFromFiles(files) { 52 | const mappings = files.reduce((mappings, file) => { 53 | const path = generatePathFromFile(file); 54 | mappings.push({ 55 | route: path, 56 | page: path, 57 | }); 58 | 59 | return mappings; 60 | }, []); 61 | 62 | return { 63 | prefix: "", 64 | mappings, 65 | }; 66 | } 67 | 68 | /** 69 | * 70 | * @param {string} pathToPagesFolder 71 | * @returns {Route} 72 | */ 73 | function generateMappingsFromPagesFolder(pathToPagesFolder) { 74 | const mappings = []; 75 | 76 | recursiveBuildMappings(pathToPagesFolder, mappings); 77 | 78 | return { 79 | prefix: "", 80 | mappings, 81 | }; 82 | } 83 | 84 | function recursiveBuildMappings(directoryPath, mappings = [], pathPart = "") { 85 | // it starts by reading files inside the given folder 86 | const files = fs.readdirSync(directoryPath); 87 | files.forEach((file) => { 88 | const absoluteFilePath = path.join(directoryPath, file); 89 | const newPathPart = fromNextPathToQueryPath(pathPart, file); 90 | const hasIndex = directoryContainsIndexFile(absoluteFilePath); 91 | if (fs.statSync(absoluteFilePath).isDirectory() && !hasIndex) { 92 | recursiveBuildMappings(absoluteFilePath, mappings, newPathPart); 93 | } else { 94 | const mapping = { 95 | route: newPathPart, 96 | page: generatePathFromFile(file, newPathPart), 97 | }; 98 | // if () 99 | mappings.push(mapping); 100 | } 101 | }); 102 | } 103 | 104 | function isUrlPathname(string) { 105 | return /^\[.*[\dA-z]]$/gm.test(string); 106 | } 107 | 108 | function fromNextPathToQueryPath(pathPart, file) { 109 | const cleanedFile = file.replace(".js", ""); 110 | if (isUrlPathname(cleanedFile)) { 111 | return `${pathPart}/${`:${cleanedFile.replace(/\[/gm, "").replace(/]/gm, "")}`}`; 112 | } else { 113 | return `${pathPart}/${cleanedFile}`; 114 | } 115 | } 116 | 117 | /** 118 | * 119 | * @param {string} absoluteFilePath 120 | */ 121 | function directoryContainsIndexFile(absoluteFilePath) { 122 | if (fs.statSync(absoluteFilePath).isDirectory()) { 123 | return fs.existsSync(path.resolve(absoluteFilePath, "index.js")); 124 | } 125 | 126 | return false; 127 | } 128 | 129 | module.exports = { 130 | getLambdaFiles, 131 | generateMappingsFromFiles, 132 | generateMappingsFromPagesFolder, 133 | }; 134 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * From an array of parts of an URL, it generates a string 3 | * with only dash. 4 | * 5 | * It can be used inside a terraform configuration 6 | * 7 | * @param {string[]} pathParts 8 | * @returns {string} 9 | */ 10 | const generateUniqueName = (pathParts) => { 11 | return pathParts 12 | .filter(Boolean) 13 | .map((p) => p.replace(":", "").replace("?", "")) 14 | .join("-"); 15 | }; 16 | 17 | module.exports = { 18 | generateUniqueName, 19 | }; 20 | -------------------------------------------------------------------------------- /tests/configuration.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/prevent-abbreviations */ 2 | // @ts-nocheck 3 | 4 | const Configuration = require("../src/configuration"); 5 | const AwsConfig = require("../src/providers/aws/awsConfig"); 6 | const { PROVIDERS } = require("../src/constants"); 7 | 8 | describe("Configuration", () => { 9 | it("should throw an error when configuration is empty", () => { 10 | const errors = Configuration.checkConfiguration(); 11 | 12 | expect(errors).toHaveLength(1); 13 | const er = errors.find((error) => error.type === "EmptyConfigurationError"); 14 | expect(er.message).toBe("Empty configuration, cannot proceed."); 15 | }); 16 | 17 | it("should throw an error when gatewayKey is not provided", () => { 18 | const errors = Configuration.checkConfiguration({}); 19 | expect(errors.find((error) => error.message === "gatewayKey is missing, it must be provided")).toBeDefined(); 20 | }); 21 | 22 | it("should throw an error when lambdaPath is not provided", () => { 23 | const errors = Configuration.checkConfiguration({}); 24 | expect(errors.find((error) => error.message === "nextAppDir is missing, it must be provided")).toBeDefined(); 25 | }); 26 | 27 | it("should throw an error when routes is are malformed", () => { 28 | const errors = Configuration.checkConfiguration({ 29 | gatewayKey: "myTest", 30 | lambdaPath: "/path", 31 | routes: { 32 | prefix: "", 33 | mappings: [ 34 | { 35 | page: "/", 36 | }, 37 | ], 38 | }, 39 | }); 40 | 41 | expect(errors.find((error) => error.message === "The object containing the routes is not correct")).toBeDefined(); 42 | 43 | const errors2 = Configuration.checkConfiguration({ 44 | gatewayKey: "myTest", 45 | lambdaPath: "/path", 46 | routes: [ 47 | { 48 | prefix: "home", 49 | mappings: [ 50 | { 51 | page: "/content", 52 | route: "ehy", 53 | }, 54 | ], 55 | }, 56 | { 57 | prefix: "blog", 58 | mappings: [ 59 | { 60 | route: "ehy", 61 | }, 62 | ], 63 | }, 64 | ], 65 | }); 66 | expect(errors2.find((error) => error.message === "The object containing the routes is not correct")).toBeDefined(); 67 | }); 68 | 69 | it("should throw an error when memorySize value is invalid", () => { 70 | const errors = Configuration.checkConfiguration({ 71 | gatewayKey: "myTest", 72 | lambdaPath: "/path", 73 | memorySize: "abcd", 74 | }); 75 | expect( 76 | errors.find( 77 | (error) => 78 | error.message === "memorySize value is invalid, if it is provided, it must be a string containing a number between 128 and 10240", 79 | ), 80 | ).toBeDefined(); 81 | }); 82 | 83 | it("should throw an error when timeout value is invalid", () => { 84 | const errors = Configuration.checkConfiguration({ 85 | gatewayKey: "myTest", 86 | lambdaPath: "/path", 87 | timeout: "abcd", 88 | }); 89 | expect( 90 | errors.find( 91 | (error) => 92 | error.message === "timeout value is invalid, if it is provided, it must be a string containing a number smaller than 900", 93 | ), 94 | ).toBeDefined(); 95 | }); 96 | 97 | it("should return true when the configuration is correct", () => { 98 | expect( 99 | Configuration.checkConfiguration({ 100 | gatewayKey: "myTest", 101 | nextAppDir: "/path", 102 | routes: { 103 | prefix: "", 104 | mappings: [ 105 | { 106 | page: "/content", 107 | route: "ehy", 108 | }, 109 | ], 110 | }, 111 | provider: "AWS", 112 | }), 113 | ).toBe(true); 114 | 115 | expect( 116 | Configuration.checkConfiguration({ 117 | gatewayKey: "myTest", 118 | nextAppDir: "/path", 119 | routes: [ 120 | { 121 | prefix: "home", 122 | mappings: [ 123 | { 124 | page: "/content", 125 | route: "ehy", 126 | }, 127 | ], 128 | }, 129 | { 130 | prefix: "blog", 131 | mappings: [ 132 | { 133 | page: "/content", 134 | route: "ehy", 135 | }, 136 | ], 137 | }, 138 | ], 139 | 140 | provider: "AWS", 141 | }), 142 | ).toBe(true); 143 | }); 144 | 145 | it("should return the gateway key", () => { 146 | const c = new AwsConfig({ gatewayKey: "myTest", nextAppDir: "/path" }); 147 | expect(c.getGatewayResourceId()).toEqual("${aws_api_gateway_rest_api.myTest.id}"); 148 | }); 149 | 150 | it("should return the gateway key", () => { 151 | const c = new AwsConfig({ gatewayKey: "myTest", nextAppDir: "/path" }); 152 | expect(c.getGatewayKey()).toEqual("myTest"); 153 | }); 154 | 155 | it("should throw an error when provider is not passed", () => { 156 | const errors = Configuration.checkConfiguration({ 157 | gatewayKey: "myTest", 158 | nextAppDir: "/path", 159 | routes: { 160 | prefix: "", 161 | mappings: [ 162 | { 163 | page: "/content", 164 | route: "ehy", 165 | }, 166 | ], 167 | }, 168 | }); 169 | expect(errors.find((error) => error.message === "provider is missing, it must be provided")).toBeDefined(); 170 | }); 171 | 172 | it("should throw an error when provider is not supported", () => { 173 | const errors = Configuration.checkConfiguration({ 174 | gatewayKey: "myTest", 175 | nextAppDir: "/path", 176 | routes: { 177 | prefix: "", 178 | mappings: [ 179 | { 180 | page: "/content", 181 | route: "ehy", 182 | }, 183 | ], 184 | }, 185 | // @ts-ignore 186 | provider: "Azure", 187 | }); 188 | 189 | expect( 190 | errors.find((error) => error.message === `Azure provider is not supported. Choose between: ${Object.keys(PROVIDERS).join(", ")}`), 191 | ).toBeDefined(); 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/.next/serverless/pages/boar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/.next/serverless/pages/boar.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/.next/serverless/pages/contact-us/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/.next/serverless/pages/contact-us/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/.next/serverless/pages/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/.next/serverless/pages/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/next9/.next/serverless/pages/boar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/next9/.next/serverless/pages/boar.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/next9/.next/serverless/pages/contact-us/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/next9/.next/serverless/pages/contact-us/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/next9/.next/serverless/pages/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/next9/.next/serverless/pages/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/next9/pages/[foo]/[deep]/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/next9/pages/[foo]/[deep]/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/next9/pages/[foo]/[query].js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/next9/pages/[foo]/[query].js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/next9/pages/[foo]/bar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/next9/pages/[foo]/bar.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/next9/pages/[foo]/fixed/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/next9/pages/[foo]/fixed/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/pages/boar.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/pages/boar.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/pages/contact-us/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/pages/contact-us/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__fixtures__/pages/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ematipico/terraform-nextjs-plugin/a391cf3af56b8ff57025e60bfc822c88e1c3e9f6/tests/providers/aws/__fixtures__/pages/index.js -------------------------------------------------------------------------------- /tests/providers/aws/__snapshots__/awsResources.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AwsResources should generate the correct configuration 1`] = ` 4 | Object { 5 | "resource": Object { 6 | "aws_api_gateway_integration": Object { 7 | "CustomKey-blog": Object { 8 | "http_method": "GET", 9 | "integration_http_method": "POST", 10 | "request_parameters": Object { 11 | "integration.request.querystring.0": "method.request.querystring.0", 12 | "integration.request.querystring.1": "method.request.querystring.1", 13 | }, 14 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog.id}", 15 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 16 | "type": "AWS_PROXY", 17 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-blogPost.arn}/invocations", 18 | }, 19 | "CustomKey-blog-detail": Object { 20 | "http_method": "GET", 21 | "integration_http_method": "POST", 22 | "request_parameters": Object { 23 | "integration.request.querystring.0": "method.request.querystring.0", 24 | "integration.request.querystring.1": "method.request.querystring.1", 25 | }, 26 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog-detail.id}", 27 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 28 | "type": "AWS_PROXY", 29 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-blogPost.arn}/invocations", 30 | }, 31 | "CustomKey-blog-detail-url": Object { 32 | "http_method": "GET", 33 | "integration_http_method": "POST", 34 | "request_parameters": Object { 35 | "integration.request.path.url": "method.request.path.url", 36 | "integration.request.querystring.0": "method.request.querystring.0", 37 | "integration.request.querystring.1": "method.request.querystring.1", 38 | }, 39 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog-detail-url.id}", 40 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 41 | "type": "AWS_PROXY", 42 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-blogPost.arn}/invocations", 43 | }, 44 | "CustomKey-blog-url": Object { 45 | "http_method": "GET", 46 | "integration_http_method": "POST", 47 | "request_parameters": Object { 48 | "integration.request.path.url": "method.request.path.url", 49 | }, 50 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog-url.id}", 51 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 52 | "type": "AWS_PROXY", 53 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-singleBlogPost.arn}/invocations", 54 | }, 55 | "CustomKey-boar": Object { 56 | "http_method": "GET", 57 | "integration_http_method": "POST", 58 | "resource_id": "\${aws_api_gateway_resource.CustomKey-boar.id}", 59 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 60 | "type": "AWS_PROXY", 61 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-boar.arn}/invocations", 62 | }, 63 | "CustomKey-contact-us": Object { 64 | "http_method": "GET", 65 | "integration_http_method": "POST", 66 | "resource_id": "\${aws_api_gateway_resource.CustomKey-contact-us.id}", 67 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 68 | "type": "AWS_PROXY", 69 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-contact-us.arn}/invocations", 70 | }, 71 | "CustomKey-index": Object { 72 | "http_method": "GET", 73 | "integration_http_method": "POST", 74 | "resource_id": "\${aws_api_gateway_resource.CustomKey-index.id}", 75 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 76 | "type": "AWS_PROXY", 77 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-index.arn}/invocations", 78 | }, 79 | "CustomKey-new-blog-post-amazing": Object { 80 | "http_method": "GET", 81 | "integration_http_method": "POST", 82 | "resource_id": "\${aws_api_gateway_resource.CustomKey-new-blog-post-amazing.id}", 83 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 84 | "type": "AWS_PROXY", 85 | "uri": "arn:aws:apigateway:\${local.aws_region}:lambda:path/2015-03-31/functions/\${aws_lambda_function.lambdaForCustomKey-blog-post.arn}/invocations", 86 | }, 87 | }, 88 | "aws_api_gateway_method": Object { 89 | "CustomKey-blog": Object { 90 | "authorization": "NONE", 91 | "http_method": "GET", 92 | "request_parameters": Object { 93 | "method.request.querystring.0": Object { 94 | "mandatory": true, 95 | "name": "page", 96 | }, 97 | "method.request.querystring.1": Object { 98 | "mandatory": false, 99 | "name": "hideComments", 100 | }, 101 | }, 102 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog.id}", 103 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 104 | }, 105 | "CustomKey-blog-detail": Object { 106 | "authorization": "NONE", 107 | "http_method": "GET", 108 | "request_parameters": Object { 109 | "method.request.querystring.0": Object { 110 | "mandatory": true, 111 | "name": "page", 112 | }, 113 | "method.request.querystring.1": Object { 114 | "mandatory": false, 115 | "name": "hideComments", 116 | }, 117 | }, 118 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog-detail.id}", 119 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 120 | }, 121 | "CustomKey-blog-detail-url": Object { 122 | "authorization": "NONE", 123 | "http_method": "GET", 124 | "request_parameters": Object { 125 | "method.request.path.url": true, 126 | "method.request.querystring.0": Object { 127 | "mandatory": true, 128 | "name": "page", 129 | }, 130 | "method.request.querystring.1": Object { 131 | "mandatory": false, 132 | "name": "hideComments", 133 | }, 134 | }, 135 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog-detail-url.id}", 136 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 137 | }, 138 | "CustomKey-blog-url": Object { 139 | "authorization": "NONE", 140 | "http_method": "GET", 141 | "request_parameters": Object { 142 | "method.request.path.url": true, 143 | }, 144 | "resource_id": "\${aws_api_gateway_resource.CustomKey-blog-url.id}", 145 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 146 | }, 147 | "CustomKey-boar": Object { 148 | "authorization": "NONE", 149 | "http_method": "GET", 150 | "resource_id": "\${aws_api_gateway_resource.CustomKey-boar.id}", 151 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 152 | }, 153 | "CustomKey-contact-us": Object { 154 | "authorization": "NONE", 155 | "http_method": "GET", 156 | "resource_id": "\${aws_api_gateway_resource.CustomKey-contact-us.id}", 157 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 158 | }, 159 | "CustomKey-index": Object { 160 | "authorization": "NONE", 161 | "http_method": "GET", 162 | "resource_id": "\${aws_api_gateway_resource.CustomKey-index.id}", 163 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 164 | }, 165 | "CustomKey-new-blog-post-amazing": Object { 166 | "authorization": "NONE", 167 | "http_method": "GET", 168 | "resource_id": "\${aws_api_gateway_resource.CustomKey-new-blog-post-amazing.id}", 169 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 170 | }, 171 | }, 172 | "aws_api_gateway_resource": Object { 173 | "CustomKey-blog": Object { 174 | "parent_id": "\${aws_api_gateway_rest_api.CustomKey.root_resource_id}", 175 | "path_part": "blog", 176 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 177 | }, 178 | "CustomKey-blog-detail": Object { 179 | "parent_id": "\${aws_api_gateway_resource.CustomKey-blog.id}", 180 | "path_part": "detail", 181 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 182 | }, 183 | "CustomKey-blog-detail-url": Object { 184 | "parent_id": "\${aws_api_gateway_resource.CustomKey-blog-detail.id}", 185 | "path_part": "{url}", 186 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 187 | }, 188 | "CustomKey-blog-url": Object { 189 | "parent_id": "\${aws_api_gateway_resource.CustomKey-blog.id}", 190 | "path_part": "{url}", 191 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 192 | }, 193 | "CustomKey-boar": Object { 194 | "parent_id": "\${aws_api_gateway_rest_api.CustomKey.root_resource_id}", 195 | "path_part": "boar", 196 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 197 | }, 198 | "CustomKey-contact-us": Object { 199 | "parent_id": "\${aws_api_gateway_rest_api.CustomKey.root_resource_id}", 200 | "path_part": "contact-us", 201 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 202 | }, 203 | "CustomKey-index": Object { 204 | "parent_id": "\${aws_api_gateway_rest_api.CustomKey.root_resource_id}", 205 | "path_part": "index", 206 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 207 | }, 208 | "CustomKey-new-blog-post-amazing": Object { 209 | "parent_id": "\${aws_api_gateway_rest_api.CustomKey.root_resource_id}", 210 | "path_part": "new-blog-post-amazing", 211 | "rest_api_id": "\${aws_api_gateway_rest_api.CustomKey.id}", 212 | }, 213 | }, 214 | }, 215 | "variable": Object { 216 | "integrationList": Object { 217 | "default": Array [ 218 | "aws_api_gateway_integration.CustomKey-new-blog-post-amazing", 219 | "aws_api_gateway_integration.CustomKey-blog", 220 | "aws_api_gateway_integration.CustomKey-blog-url", 221 | "aws_api_gateway_integration.CustomKey-blog-detail", 222 | "aws_api_gateway_integration.CustomKey-blog-detail-url", 223 | "aws_api_gateway_integration.CustomKey-boar", 224 | "aws_api_gateway_integration.CustomKey-contact-us", 225 | "aws_api_gateway_integration.CustomKey-index", 226 | ], 227 | }, 228 | }, 229 | } 230 | `; 231 | -------------------------------------------------------------------------------- /tests/providers/aws/awsResources.test.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | const AwsResources = require("../../../src/providers/aws"); 3 | const AwsConfig = require("../../../src/providers/aws/awsConfig"); 4 | const path = require("path"); 5 | 6 | describe("AwsResources", () => { 7 | it("should generate the correct configuration", async () => { 8 | const config = new AwsConfig({ 9 | gatewayKey: "CustomKey", 10 | // eslint-disable-next-line unicorn/prevent-abbreviations 11 | nextAppDir: path.resolve(__dirname, "__fixtures__"), 12 | provider: "AWS", 13 | routes: { 14 | prefix: "", 15 | mappings: [ 16 | { 17 | page: "/blog-post", 18 | route: "/new-blog-post-amazing", 19 | }, 20 | { 21 | page: "/singleBlogPost", 22 | route: "/blog/:url", 23 | }, 24 | { 25 | page: "/blogPost", 26 | route: "/blog/detail/:url", 27 | params: [ 28 | { 29 | name: "page", 30 | mandatory: true, 31 | }, 32 | { 33 | name: "hideComments", 34 | mandatory: false, 35 | }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | }); 41 | 42 | const awsResources = new AwsResources(config); 43 | const result = await awsResources.generateGatewayResources(); 44 | 45 | expect(result).toMatchSnapshot(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/providers/aws/gatewayIntegration.test.js: -------------------------------------------------------------------------------- 1 | const AwsConfig = require("../../../src/providers/aws/awsConfig"); 2 | const GatewayIntegration = require("../../../src/providers/aws/resources/gatewayIntegration"); 3 | 4 | describe("Gateway integration", () => { 5 | it("should return the expected resource", () => { 6 | const config = new AwsConfig({ 7 | gatewayKey: "CustomKey", 8 | provider: "AWS", 9 | }); 10 | 11 | const resource = new GatewayIntegration(config, { 12 | id: "index", 13 | lambdaName: "index", 14 | pathname: "/", 15 | }); 16 | 17 | const result = resource.generateGatewayIntegration("CustomKey-index"); 18 | 19 | expect(result.uniqueId).toBe("CustomKey-index"); 20 | expect(result.resource).toStrictEqual({ 21 | rest_api_id: "${aws_api_gateway_rest_api.CustomKey.id}", 22 | resource_id: "${aws_api_gateway_resource.CustomKey-index.id}", 23 | http_method: "GET", 24 | integration_http_method: "POST", 25 | type: "AWS_PROXY", 26 | uri: "arn:aws:apigateway:${local.aws_region}:lambda:path/2015-03-31/functions/${aws_lambda_function.lambdaForCustomKey-index.arn}/invocations", 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/providers/aws/gatewayMethod.test.js: -------------------------------------------------------------------------------- 1 | const AwsConfig = require("../../../src/providers/aws/awsConfig"); 2 | const GatewayMethod = require("../../../src/providers/aws/resources/gatewayMethod"); 3 | 4 | describe("terraFormGatewayMethod", () => { 5 | it("should create a correct resource", () => { 6 | const config = new AwsConfig({ 7 | gatewayKey: "CustomKey", 8 | provider: "AWS", 9 | }); 10 | 11 | const method = new GatewayMethod(config, { 12 | id: "index", 13 | pathname: "/", 14 | lambdaName: "index", 15 | }); 16 | 17 | const result = method.generateGatewayMethod("index"); 18 | 19 | expect(result.uniqueId).toBe("CustomKey-index"); 20 | expect(result.resource).toStrictEqual({ 21 | rest_api_id: "${aws_api_gateway_rest_api.CustomKey.id}", 22 | resource_id: "${aws_api_gateway_resource.index.id}", 23 | http_method: "GET", 24 | authorization: "NONE", 25 | }); 26 | }); 27 | 28 | it("should create a correct resource when there are normal params", () => { 29 | const config = new AwsConfig({ 30 | gatewayKey: "CustomKey", 31 | provider: "AWS", 32 | }); 33 | 34 | const method = new GatewayMethod(config, { 35 | id: "index", 36 | pathname: "/", 37 | lambdaName: "index", 38 | params: [ 39 | { 40 | name: "accountId", 41 | mandatory: true, 42 | }, 43 | { 44 | name: "socialId", 45 | mandatory: true, 46 | }, 47 | ], 48 | }); 49 | 50 | const result = method.generateGatewayMethod("index"); 51 | 52 | expect(result.uniqueId).toBe("CustomKey-index"); 53 | expect(result.resource).toStrictEqual({ 54 | rest_api_id: "${aws_api_gateway_rest_api.CustomKey.id}", 55 | resource_id: "${aws_api_gateway_resource.index.id}", 56 | http_method: "GET", 57 | authorization: "NONE", 58 | request_parameters: { 59 | "method.request.path.accountId": true, 60 | "method.request.path.socialId": true, 61 | }, 62 | }); 63 | }); 64 | 65 | it("should create a correct resource when there are normal query string params", () => { 66 | const config = new AwsConfig({ 67 | gatewayKey: "CustomKey", 68 | provider: "AWS", 69 | }); 70 | 71 | const method = new GatewayMethod(config, { 72 | id: "index", 73 | pathname: "/", 74 | lambdaName: "index", 75 | queryStringParams: [ 76 | { 77 | name: "accountId", 78 | mandatory: false, 79 | }, 80 | { 81 | name: "socialId", 82 | mandatory: false, 83 | }, 84 | ], 85 | }); 86 | 87 | const result = method.generateGatewayMethod("index"); 88 | 89 | expect(result.uniqueId).toBe("CustomKey-index"); 90 | expect(result.resource).toStrictEqual({ 91 | rest_api_id: "${aws_api_gateway_rest_api.CustomKey.id}", 92 | resource_id: "${aws_api_gateway_resource.index.id}", 93 | http_method: "GET", 94 | authorization: "NONE", 95 | request_parameters: { 96 | "method.request.querystring.accountId": false, 97 | "method.request.querystring.socialId": false, 98 | }, 99 | }); 100 | }); 101 | 102 | it("should create a correct resource when there are both type of params", () => { 103 | const config = new AwsConfig({ 104 | gatewayKey: "CustomKey", 105 | provider: "AWS", 106 | }); 107 | 108 | const method = new GatewayMethod(config, { 109 | id: "index", 110 | pathname: "/", 111 | lambdaName: "index", 112 | queryStringParams: [ 113 | { 114 | name: "accountId", 115 | mandatory: false, 116 | }, 117 | { 118 | name: "socialId", 119 | mandatory: false, 120 | }, 121 | ], 122 | params: [ 123 | { 124 | name: "url", 125 | mandatory: true, 126 | }, 127 | ], 128 | }); 129 | 130 | const result = method.generateGatewayMethod("index"); 131 | 132 | expect(result.uniqueId).toBe("CustomKey-index"); 133 | expect(result.resource).toStrictEqual({ 134 | rest_api_id: "${aws_api_gateway_rest_api.CustomKey.id}", 135 | resource_id: "${aws_api_gateway_resource.index.id}", 136 | http_method: "GET", 137 | authorization: "NONE", 138 | request_parameters: { 139 | "method.request.querystring.accountId": false, 140 | "method.request.querystring.socialId": false, 141 | "method.request.path.url": true, 142 | }, 143 | }); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /tests/providers/aws/gatewayResource.test.js: -------------------------------------------------------------------------------- 1 | const GatewayResource = require("../../../src/providers/aws/resources/gatewayResource"); 2 | const AwsConfig = require("../../../src/providers/aws/awsConfig"); 3 | 4 | describe("Gateway integration", () => { 5 | it("should return the expected resource for a top level", () => { 6 | const c = new AwsConfig({ 7 | gatewayKey: "CustomKey", 8 | provider: "AWS", 9 | }); 10 | 11 | const method = new GatewayResource(c, { 12 | id: "myId", 13 | pathname: "personal-page", 14 | lambdaName: "myId", 15 | }); 16 | 17 | const result = method.generateGatewayResource(); 18 | 19 | expect(result.uniqueId).toBe("CustomKey-myId"); 20 | expect(result.resource).toStrictEqual({ 21 | rest_api_id: "${aws_api_gateway_rest_api.CustomKey.id}", 22 | parent_id: "${aws_api_gateway_rest_api.CustomKey.root_resource_id}", 23 | path_part: "personal-page", 24 | }); 25 | }); 26 | 27 | it("should return the expected resource when it has a parent", () => { 28 | const c = new AwsConfig({ 29 | gatewayKey: "CustomKey", 30 | provider: "AWS", 31 | }); 32 | 33 | const method = new GatewayResource(c, { 34 | id: "mySecondId", 35 | pathname: "personal-page", 36 | parentId: "myId", 37 | lambdaName: "mySecondId", 38 | }); 39 | 40 | const result = method.generateGatewayResource(); 41 | 42 | expect(result.uniqueId).toBe("CustomKey-mySecondId"); 43 | expect(result.resource).toStrictEqual({ 44 | rest_api_id: "${aws_api_gateway_rest_api.CustomKey.id}", 45 | parent_id: "${aws_api_gateway_resource.CustomKey-myId.id}", 46 | path_part: "personal-page", 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /tests/providers/aws/lambdaPermission.test.js: -------------------------------------------------------------------------------- 1 | const LambdaPermission = require("../../../src/providers/aws/resources/lambdaPermission"); 2 | const AwsConfig = require("../../../src/providers/aws/awsConfig"); 3 | 4 | describe("terraFormLambda", () => { 5 | it("should create the correct resource", () => { 6 | const c = new AwsConfig({ 7 | gatewayKey: "CustomKey", 8 | provider: "AWS", 9 | }); 10 | 11 | const properties = new LambdaPermission(c, { 12 | id: "index", 13 | directoryName: "/test", 14 | }); 15 | 16 | const result = properties.generateLambdaPermissions(); 17 | 18 | expect(result.permissionUniqueId).toBe("lambdaForCustomKey-index"); 19 | expect(result.resource).toStrictEqual({ 20 | statement_id: "AllowExecutionFromAPIGateway", 21 | action: "lambda:InvokeFunction", 22 | function_name: "${aws_lambda_function.lambdaForCustomKey-index.function_name}", 23 | principal: "apigateway.amazonaws.com", 24 | source_arn: "${aws_api_gateway_rest_api.CustomKey.execution_arn}/*/*/*", 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/providers/aws/lambdaProperties.test.js: -------------------------------------------------------------------------------- 1 | const LambdaProperties = require("../../../src/providers/aws/resources/lambdaProperties"); 2 | const AwsConfig = require("../../../src/providers/aws/awsConfig"); 3 | 4 | describe("terraFormLambda", () => { 5 | it("should create the correct resource", () => { 6 | const c = new AwsConfig({ 7 | gatewayKey: "CustomKey", 8 | provider: "AWS", 9 | }); 10 | 11 | const properties = new LambdaProperties(c, { 12 | id: "index", 13 | directoryName: "/test", 14 | }); 15 | 16 | const result = properties.generateLambdaProperties(); 17 | 18 | expect(result.resourceUniqueId).toBe("lambdaForCustomKey-index"); 19 | expect(result.resource).toStrictEqual({ 20 | filename: "${data.archive_file.packLambda-index.output_path}", 21 | function_name: "${local.groupname}-index", 22 | source_code_hash: "${data.archive_file.packLambda-index.output_base64sha256}", 23 | handler: "index.render", 24 | runtime: "nodejs8.10", 25 | memory_size: "1024", 26 | timeout: "180", 27 | role: "${local.lambda_iam_role}", 28 | }); 29 | }); 30 | 31 | it("return the correct node version 10", () => { 32 | const c = new AwsConfig({ 33 | gatewayKey: "CustomKey", 34 | nodeVersion: "10", 35 | provider: "AWS", 36 | }); 37 | 38 | const properties = new LambdaProperties(c, { 39 | id: "index", 40 | directoryName: "/test", 41 | }); 42 | 43 | const result = properties.generateLambdaProperties(); 44 | 45 | expect(result.resourceUniqueId).toBe("lambdaForCustomKey-index"); 46 | expect(result.resource.runtime).toEqual("nodejs10.x"); 47 | }); 48 | 49 | it("return the correct node version 12", () => { 50 | const c = new AwsConfig({ 51 | gatewayKey: "CustomKey", 52 | nodeVersion: "12", 53 | provider: "AWS", 54 | }); 55 | 56 | const properties = new LambdaProperties(c, { 57 | id: "index", 58 | directoryName: "/test", 59 | }); 60 | 61 | const result = properties.generateLambdaProperties(); 62 | 63 | expect(result.resourceUniqueId).toBe("lambdaForCustomKey-index"); 64 | expect(result.resource.runtime).toEqual("nodejs12.x"); 65 | }); 66 | 67 | it("return the provided memorySize value", () => { 68 | const c = new AwsConfig({ 69 | gatewayKey: "CustomKey", 70 | provider: "AWS", 71 | memorySize: "2048", 72 | }); 73 | 74 | const properties = new LambdaProperties(c, { 75 | id: "index", 76 | directoryName: "/test", 77 | }); 78 | 79 | const result = properties.generateLambdaProperties(); 80 | 81 | expect(result.resourceUniqueId).toBe("lambdaForCustomKey-index"); 82 | expect(result.resource.memory_size).toEqual("2048"); 83 | }); 84 | 85 | it("return the provided timeout value", () => { 86 | const c = new AwsConfig({ 87 | gatewayKey: "CustomKey", 88 | provider: "AWS", 89 | timeout: "120", 90 | }); 91 | 92 | const properties = new LambdaProperties(c, { 93 | id: "index", 94 | directoryName: "/test", 95 | }); 96 | 97 | const result = properties.generateLambdaProperties(); 98 | 99 | expect(result.resourceUniqueId).toBe("lambdaForCustomKey-index"); 100 | expect(result.resource.timeout).toEqual("120"); 101 | }); 102 | 103 | it("should return the environment variables", () => { 104 | const c = new AwsConfig({ 105 | gatewayKey: "CustomKey", 106 | nodeVersion: "12", 107 | provider: "AWS", 108 | env: [ 109 | { 110 | key: "PROD_KEY", 111 | value: "230402", 112 | }, 113 | ], 114 | }); 115 | 116 | const properties = new LambdaProperties(c, { 117 | id: "index", 118 | directoryName: "/test", 119 | }); 120 | 121 | const result = properties.generateLambdaProperties(); 122 | 123 | expect(result.resourceUniqueId).toBe("lambdaForCustomKey-index"); 124 | expect(result.resource.environment).toBeTruthy(); 125 | expect(result.resource.environment).toStrictEqual({ 126 | variables: { 127 | PROD_KEY: "230402", 128 | }, 129 | }); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /tests/providers/aws/lambdaZip.test.js: -------------------------------------------------------------------------------- 1 | const LambdaZip = require("../../../src/providers/aws/resources/lambdaZip"); 2 | const AwsConfig = require("../../../src/providers/aws/awsConfig"); 3 | 4 | describe("terraFormZip", () => { 5 | it("should create the correct resource", () => { 6 | const config = new AwsConfig({ 7 | gatewayKey: "Test", 8 | provider: "AWS", 9 | }); 10 | 11 | const zip = new LambdaZip(config, { 12 | id: "index", 13 | directoryName: "index", 14 | }); 15 | 16 | const result = zip.generateZipResource(); 17 | expect(result.uniqueId).toBe("packLambda-index"); 18 | expect(result.resource.output_path).toEqual("files/${local.groupname}-index.zip"); 19 | expect(result.resource.type).toEqual("zip"); 20 | expect(result.resource.source_dir).toContain("lambdas"); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/providers/aws/shared.test.js: -------------------------------------------------------------------------------- 1 | const { generateMappingsFromFiles, getLambdaFiles, generateMappingsFromPagesFolder } = require("../../../src/shared"); 2 | const path = require("path"); 3 | 4 | describe("shared.js", () => { 5 | describe("generateMappingsFromFiles", () => { 6 | it("should generate a routes object from files", () => { 7 | const routes = generateMappingsFromFiles(["file.js", "file2.js", "boar.js"]); 8 | 9 | expect(routes).toStrictEqual({ 10 | prefix: "", 11 | mappings: [ 12 | { 13 | page: "/file", 14 | route: "/file", 15 | }, 16 | { 17 | page: "/file2", 18 | route: "/file2", 19 | }, 20 | { 21 | page: "/boar", 22 | route: "/boar", 23 | }, 24 | ], 25 | }); 26 | }); 27 | }); 28 | 29 | it("should generate routes object from a real folder", async () => { 30 | const lambdaPath = path.resolve(__dirname, "__fixtures__", "pages"); 31 | const files = await getLambdaFiles(lambdaPath); 32 | const routes = generateMappingsFromFiles(files); 33 | expect(routes).toEqual({ 34 | prefix: "", 35 | mappings: [ 36 | { 37 | page: "/boar", 38 | route: "/boar", 39 | }, 40 | { 41 | page: "/contact-us", 42 | route: "/contact-us", 43 | }, 44 | { 45 | page: "/index", 46 | route: "/index", 47 | }, 48 | ], 49 | }); 50 | }); 51 | 52 | it("should generate routes from next 9 folder structure", async () => { 53 | const testPath = path.resolve(__dirname, "__fixtures__", "next9", "pages"); 54 | 55 | const routes = await generateMappingsFromPagesFolder(testPath); 56 | expect(routes).toEqual({ 57 | prefix: "", 58 | mappings: [ 59 | { 60 | page: "/deep", 61 | route: "/:foo/:deep", 62 | }, 63 | { 64 | page: "/query", 65 | route: "/:foo/:query", 66 | }, 67 | { 68 | page: "/bar", 69 | route: "/:foo/bar", 70 | }, 71 | { 72 | page: "/fixed", 73 | route: "/:foo/fixed", 74 | }, 75 | ], 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /tests/terranext.test.js: -------------------------------------------------------------------------------- 1 | describe.skip("", () => { 2 | it("expec", () => { 3 | expect(true).toBe(true); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "noEmit": true, 5 | "allowJs": true, 6 | "checkJs": true, 7 | "downlevelIteration": true, 8 | "declaration": true, 9 | "declarationDir": "./types", 10 | "skipLibCheck": true 11 | }, 12 | "include": ["./src", "./tests"], 13 | "exclude": ["./src/compatLayer.js"] 14 | } 15 | --------------------------------------------------------------------------------