├── .editorconfig ├── .github └── workflows │ ├── integrate.yml │ ├── publish.yml │ └── validate.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── COPYRIGHT ├── LICENSE ├── README.md ├── assets ├── deploy-debug-demo.gif ├── deploy-demo.gif ├── dev-demo.gif ├── info-demo.gif └── remove-demo.gif ├── package.json ├── prettier.config.js ├── scripts └── install-nested-packages.js ├── serverless.component.yml ├── src ├── _express │ ├── handler.js │ ├── package-lock.json │ └── package.json ├── _src │ ├── app.js │ ├── package-lock.json │ └── package.json ├── package-lock.json ├── package.json ├── serverless.js └── utils.js ├── templates ├── express-starter-typescript-tsc │ ├── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── serverless.yml │ ├── src │ │ └── app.ts │ └── tsconfig.json ├── express-starter-typescript-webpack │ ├── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── serverless.yml │ ├── src │ │ └── app.ts │ ├── tsconfig.json │ └── webpack.config.js └── express-starter │ ├── .gitignore │ ├── app.js │ ├── package-lock.json │ ├── package.json │ ├── serverless.template.yml │ └── serverless.yml └── test ├── integration.test.js ├── src ├── app.js └── package.json └── utils.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_size = 2 11 | indent_style = space 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.github/workflows/integrate.yml: -------------------------------------------------------------------------------- 1 | # master only 2 | 3 | name: Integrate 4 | 5 | on: 6 | push: 7 | branches: [master] 8 | 9 | env: 10 | FORCE_COLOR: 1 11 | 12 | jobs: 13 | validate: 14 | name: Validate 15 | runs-on: ubuntu-latest 16 | env: 17 | SERVERLESS_ACCESS_KEY: ${{ secrets.SERVERLESS_ACCESS_KEY }} 18 | steps: 19 | - name: Resolve last validated commit hash (for `git diff` purposes) 20 | env: 21 | # See https://github.com/serverlessinc/setup-cicd-resources 22 | GET_LAST_VALIDATED_COMMIT_HASH_URL: ${{ secrets.GET_LAST_VALIDATED_COMMIT_HASH_URL }} 23 | PUT_LAST_VALIDATED_COMMIT_HASH_URL: ${{ secrets.PUT_LAST_VALIDATED_COMMIT_HASH_URL }} 24 | run: | 25 | curl -f "$GET_LAST_VALIDATED_COMMIT_HASH_URL" -o /home/runner/last-validated-commit-hash || : 26 | curl -v -X PUT -H "User-Agent:" -H "Accept:" -H "Content-Type:" -d "$GITHUB_SHA" "$PUT_LAST_VALIDATED_COMMIT_HASH_URL" 27 | - name: Store last validated commit hash (as it's to be used in other job) 28 | uses: actions/upload-artifact@v2 29 | with: 30 | name: last-validated-commit-hash 31 | path: /home/runner/last-validated-commit-hash 32 | 33 | - name: Checkout repository 34 | uses: actions/checkout@v2 35 | 36 | - name: Retrieve ~/.npm from cache 37 | uses: actions/cache@v1 38 | with: 39 | path: ~/.npm 40 | key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('**package*.json') }} 41 | restore-keys: npm-v14-${{ runner.os }}-${{ github.ref }}- 42 | - name: Retrieve node_modules from cache 43 | id: cacheNodeModules 44 | uses: actions/cache@v1 45 | with: 46 | path: node_modules 47 | key: node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} 48 | restore-keys: node-modules-v14-${{ runner.os }}-${{ github.ref }}- 49 | - name: Retrieve src/node_modules from cache 50 | id: cacheSrcNodeModules 51 | uses: actions/cache@v1 52 | with: 53 | path: src/node_modules 54 | key: src/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('src/package*.json') }} 55 | restore-keys: src/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 56 | - name: Retrieve src/_express/node_modules from cache 57 | id: cacheSrcExpressNodeModules 58 | uses: actions/cache@v1 59 | with: 60 | path: src/_express/node_modules 61 | key: src/_express/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('src/_express/package*.json') }} 62 | restore-keys: src/_express/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 63 | - name: Retrieve src/_src/node_modules from cache 64 | id: cacheSrcSrcNodeModules 65 | uses: actions/cache@v1 66 | with: 67 | path: src/_src/node_modules 68 | key: src/_src/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('src/_src/package*.json') }} 69 | restore-keys: src/_src/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 70 | - name: Retrieve test/src/node_modules from cache 71 | id: cacheTestSrcNodeModules 72 | uses: actions/cache@v1 73 | with: 74 | path: test/src/node_modules 75 | key: test/src/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('test/src/package.json') }} 76 | restore-keys: test/src/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 77 | 78 | - name: Install Node.js and npm 79 | uses: actions/setup-node@v1 80 | with: 81 | node-version: 14.x 82 | 83 | - name: Install root dependencies 84 | if: steps.cacheNodeModules.outputs.cache-hit != 'true' 85 | run: npm update --save-dev --no-save 86 | - name: Install src dependencies 87 | if: steps.cacheSrcNodeModules.outputs.cache-hit != 'true' 88 | run: | 89 | cd src 90 | npm ci 91 | - name: Install src/_express dependencies 92 | if: steps.cacheSrcExpressNodeModules.outputs.cache-hit != 'true' 93 | run: | 94 | cd src/_express 95 | npm ci 96 | - name: Install src/_src dependencies 97 | if: steps.cacheSrcSrcNodeModules.outputs.cache-hit != 'true' 98 | run: | 99 | cd src/_src 100 | npm ci 101 | - name: Install test/src dependencies 102 | if: steps.cacheTestSrcNodeModules.outputs.cache-hit != 'true' 103 | run: | 104 | cd test/src 105 | npm update --no-save 106 | 107 | # Ensure no parallel runs 108 | # See: https://github.community/t/how-to-limit-concurrent-workflow-runs/16844/21 109 | - name: Turnstyle 110 | uses: softprops/turnstyle@v1 111 | env: 112 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 113 | 114 | - name: Publish "dev" version 115 | run: npm run publish:dev 116 | 117 | - name: Integration tests 118 | env: 119 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 120 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 121 | run: npm test 122 | 123 | tagIfNewVersion: 124 | name: Tag if new version 125 | runs-on: ubuntu-latest 126 | needs: validate 127 | steps: 128 | - name: Checkout repository 129 | uses: actions/checkout@v2 130 | with: 131 | # Ensure to have complete history of commits pushed with given push operation 132 | # It's loose and imperfect assumption that no more than 30 commits will be pushed at once 133 | fetch-depth: 30 134 | # Tag needs to be pushed with real user token 135 | # (hence we're not relying on actions secrets.GITHUB_TOKEN) 136 | # Otherwise pushed tag won't trigger the actions workflow 137 | token: ${{ secrets.USER_GITHUB_TOKEN }} 138 | 139 | - name: Resolve last validated commit hash (for `git diff` purposes) 140 | uses: actions/download-artifact@v2 141 | continue-on-error: true 142 | with: 143 | name: last-validated-commit-hash 144 | path: /home/runner 145 | 146 | - name: Tag if new version 147 | run: | 148 | LAST_VALIDATED_COMMIT_HASH=`cat /home/runner/last-validated-commit-hash` || : 149 | if [ -n "$LAST_VALIDATED_COMMIT_HASH" ]; 150 | then 151 | NEW_VERSION=`git diff -U0 $LAST_VALIDATED_COMMIT_HASH serverless.component.yml | grep 'version: ' | tail -n 1 | grep -oE "[0-9]+\.[0-9]+\.[0-9]+"` || : 152 | if [ -n "$NEW_VERSION" ]; 153 | then 154 | git tag v$NEW_VERSION 155 | git push --tags 156 | fi 157 | fi 158 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # Version tags only 2 | 3 | name: Publish 4 | 5 | on: 6 | push: 7 | tags: 8 | - v[0-9]+.[0-9]+.[0-9]+ 9 | 10 | env: 11 | FORCE_COLOR: 1 12 | 13 | jobs: 14 | publish: 15 | name: Publish 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v2 20 | 21 | - name: Retrieve node_modules from cache 22 | uses: actions/cache@v1 23 | with: 24 | path: node_modules 25 | key: node-modules-v14-${{ runner.os }}-refs/heads/master-${{ hashFiles('package.json') }} 26 | - name: Retrieve src/node_modules from cache 27 | uses: actions/cache@v1 28 | with: 29 | path: src/node_modules 30 | key: src/node-modules-v14-${{ runner.os }}-refs/heads/master-${{ hashFiles('src/package*.json') }} 31 | - name: Retrieve src/_express/node_modules from cache 32 | uses: actions/cache@v1 33 | with: 34 | path: src/_express/node_modules 35 | key: src/_express/node-modules-v14-${{ runner.os }}-refs/heads/master-${{ hashFiles('src/_express/package*.json') }} 36 | - name: Retrieve src/_src/node_modules from cache 37 | uses: actions/cache@v1 38 | with: 39 | path: src/_src/node_modules 40 | key: src/_src/node-modules-v14-${{ runner.os }}-refs/heads/master-${{ hashFiles('src/_src/package*.json') }} 41 | 42 | - name: Install Node.js and npm 43 | uses: actions/setup-node@v1 44 | with: 45 | node-version: 14.x 46 | 47 | - name: Publish new version 48 | env: 49 | SERVERLESS_ACCESS_KEY: ${{ secrets.SERVERLESS_ACCESS_KEY }} 50 | run: npm run publish 51 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | # PR's only 2 | 3 | name: Validate 4 | 5 | on: 6 | pull_request: 7 | branches: [master] 8 | 9 | env: 10 | FORCE_COLOR: 1 11 | 12 | jobs: 13 | lintAndFormatting: 14 | name: Lint & Formatting 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | with: 20 | # Workaround unexplained "no merge base" error 21 | # https://github.com/serverless/enterprise-plugin/pull/466/checks?check_run_id=954633250 22 | fetch-depth: 2 23 | 24 | - name: Retrieve last master commit (for `git diff` purposes) 25 | run: | 26 | git checkout -b pr 27 | git fetch --prune --depth=1 origin +refs/heads/master:refs/remotes/origin/master 28 | git checkout master 29 | git checkout pr 30 | 31 | - name: Retrieve ~/.npm from cache 32 | uses: actions/cache@v1 33 | with: 34 | path: ~/.npm 35 | key: npm-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('**package*.json') }} 36 | restore-keys: | 37 | npm-v14-${{ runner.os }}-${{ github.ref }}- 38 | npm-v14-${{ runner.os }}-refs/heads/master- 39 | - name: Retrieve node_modules from cache 40 | id: cacheNodeModules 41 | uses: actions/cache@v1 42 | with: 43 | path: node_modules 44 | key: node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('package.json') }} 45 | restore-keys: | 46 | node-modules-v14-${{ runner.os }}-${{ github.ref }}- 47 | node-modules-v14-${{ runner.os }}-refs/heads/master- 48 | - name: Retrieve src/node_modules from cache 49 | id: cacheSrcNodeModules 50 | uses: actions/cache@v1 51 | with: 52 | path: src/node_modules 53 | key: src/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('src/package*.json') }} 54 | restore-keys: | 55 | src/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 56 | src/node-modules-v14-${{ runner.os }}-refs/heads/master- 57 | - name: Retrieve src/_express/node_modules from cache 58 | id: cacheSrcExpressNodeModules 59 | uses: actions/cache@v1 60 | with: 61 | path: src/_express/node_modules 62 | key: src/_express/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('src/_express/package*.json') }} 63 | restore-keys: | 64 | src/_express/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 65 | src/_express/node-modules-v14-${{ runner.os }}-refs/heads/master- 66 | - name: Retrieve src/_src/node_modules from cache 67 | id: cacheSrcSrcNodeModules 68 | uses: actions/cache@v1 69 | with: 70 | path: src/_src/node_modules 71 | key: src/_src/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('src/_src/package*.json') }} 72 | restore-keys: | 73 | src/_src/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 74 | src/_src/node-modules-v14-${{ runner.os }}-refs/heads/master- 75 | - name: Retrieve test/src/node_modules from cache 76 | id: cacheTestSrcNodeModules 77 | uses: actions/cache@v1 78 | with: 79 | path: test/src/node_modules 80 | key: test/src/node-modules-v14-${{ runner.os }}-${{ github.ref }}-${{ hashFiles('test/src/package.json') }} 81 | restore-keys: | 82 | test/src/node-modules-v14-${{ runner.os }}-${{ github.ref }}- 83 | test/src/node-modules-v14-${{ runner.os }}-refs/heads/master- 84 | 85 | - name: Install Node.js and npm 86 | uses: actions/setup-node@v1 87 | with: 88 | node-version: 14.x 89 | 90 | - name: Install root dependencies 91 | if: steps.cacheNodeModules.outputs.cache-hit != 'true' 92 | run: npm update --save-dev --no-save 93 | - name: Install src dependencies 94 | if: steps.cacheSrcNodeModules.outputs.cache-hit != 'true' 95 | run: | 96 | cd src 97 | npm ci 98 | - name: Install src/_express dependencies 99 | if: steps.cacheSrcExpressNodeModules.outputs.cache-hit != 'true' 100 | run: | 101 | cd src/_express 102 | npm ci 103 | - name: Install src/_src dependencies 104 | if: steps.cacheSrcSrcNodeModules.outputs.cache-hit != 'true' 105 | run: | 106 | cd src/_src 107 | npm ci 108 | - name: Install test/src dependencies 109 | if: steps.cacheTestSrcNodeModules.outputs.cache-hit != 'true' 110 | run: | 111 | cd test/src 112 | npm update --no-save 113 | - name: Validate Formatting 114 | run: npm run prettier-check:updated 115 | - name: Validate Lint rules 116 | run: npm run lint:updated 117 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env* 2 | /.eslintcache 3 | /node_modules 4 | /package-lock.json 5 | /src/_express/node_modules 6 | /src/_src/node_modules 7 | /src/node_modules 8 | /test/src/node_modules 9 | /test/src/package-lock.json 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | dist 3 | node_modules -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting our team at **hello@serverless.com**. As an alternative 59 | feel free to reach out to any of us personally. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at [http://contributor-covenant.org/version/1/4][version] 73 | 74 | [homepage]: http://contributor-covenant.org 75 | [version]: http://contributor-covenant.org/version/1/4/ 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a [code of conduct](./CODE_OF_CONDUCT.md), please follow it in all your interactions with the project. 7 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Serverless, Inc. https://serverless.com 2 | 3 | Serverless Components may be freely distributed under the Apache 2.0 license. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2018 Serverless, Inc. 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | https://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Serverless Components](https://s3.amazonaws.com/public.assets.serverless.com/images/readme_serverless_express.gif)](http://serverless.com) 2 | 3 |
4 | 5 | **Serverless Express** ⎯⎯⎯ This [Serverless Framework Component](https://github.com/serverless/components) enables you to take existing Express.js apps and deploy them onto cheap, auto-scaling, serverless infrastructure on AWS (specifically AWS HTTP API and AWS Lambda), easily. It's packed with production-ready features, like custom domains, SSL certificates, canary deployments, and costs an average of **\$0.000003** per request. 6 | 7 |
8 | 9 | - [x] **Never Pay For Idle** - No HTTP requests, no cost. Averages ~\$0.000003 per request. 10 | - [x] **Zero Configuration** - All we need is your code, then just deploy (advanced config options are available). 11 | - [x] **Fast Deployments** - Deploy changes to the cloud in seconds. 12 | - [x] **Realtime Logging** - Rapidly develop on the cloud w/ real-time logs and errors in the CLI. 13 | - [x] **Canary Deployments** - Deploy your app gradually to a subset of your traffic overtime. 14 | - [x] **Custom Domain + SSL** - Auto-configure a custom domain w/ a free AWS ACM SSL certificate. 15 | - [x] **Team Collaboration** - Collaborate with your teamates with shared state and outputs. 16 | - [x] **Built-in Monitoring** - Monitor your express app right from the Serverless Dashboard. 17 | 18 |
19 | 20 | Check out the **[Serverless Fullstack Application](https://github.com/serverless-components/fullstack-app)** for a ready-to-use boilerplate and overall great example of how to use this Component. 21 | 22 |

23 | 24 |

25 | 26 | Get Started: 27 | 28 | 1. [**Install**](#install) 29 | 2. [**Initialize**](#initialize) 30 | 3. [**Deploy**](#deploy) 31 | 4. [**Configure**](#configure) 32 | 5. [**Dev Mode**](#dev-mode) 33 | 6. [**Monitor**](#monitor) 34 | 7. [**Remove**](#remove) 35 | 36 | Extra: 37 | 38 | - [**Architecture**](#architecture) 39 | - [**Guides**](#guides) 40 | 41 |   42 | 43 | ### Install 44 | 45 | To get started with this component, install the latest version of the Serverless Framework: 46 | 47 | ``` 48 | $ npm install -g serverless 49 | ``` 50 | 51 | After installation, make sure you connect your AWS account by setting a provider in the org setting page on the [Serverless Dashboard](https://app.serverless.com). 52 | 53 | ### Initialize 54 | 55 | The easiest way to start using the express component is by initializing the `express-starter` template. Just run this command: 56 | 57 | ``` 58 | $ serverless init express-starter 59 | $ cd express-starter 60 | ``` 61 | 62 | This will also run `npm install` for you. You should now have a directory that looks something like this: 63 | 64 | ``` 65 | |- app.js 66 | |- node_modules 67 | |- package.json 68 | |- serverless.yml 69 | ``` 70 | 71 | ### Deploy 72 | 73 | 74 | 75 | Once you have the directory set up, you're now ready to deploy. Just run `serverless deploy` from within the directory containing the `serverless.yml` file. Your first deployment might take a little while, but subsequent deployment would just take few seconds. After deployment is done, you should see your express app's URL. Visit that URL to see your new app live. 76 | 77 | **Note:** If you see an `internal server error`, it probably means you did not run `npm install` after `serverless create`. See above for more info. 78 | 79 | For more information on what's going on during deployment, you could specify the `serverless deploy --debug` flag, which would view deployment logs in realtime. 80 | 81 |
82 | 83 | ### Configure 84 | 85 | The Express component is a zero configuration component, meaning that it'll work out of the box with no configuration and sane defaults. With that said, there are still a lot of optional configuration that you can specify. 86 | 87 | Here's a complete reference of the `serverless.yml` file for the express component: 88 | 89 | ```yml 90 | component: express # (required) name of the component. In that case, it's express. You will want to pin this to a specific version in production via semantic versioning, like this: express@1.0.10. Run 'serverless registry express' to see available versions. 91 | name: express-api # (required) name of your express component instance. 92 | org: serverlessinc # (optional) serverless dashboard org. default is the first org you created during signup. 93 | app: myApp # (optional) serverless dashboard app. default is the same as the name property. 94 | stage: dev # (optional) serverless dashboard stage. default is dev. 95 | 96 | inputs: 97 | src: ./ # (optional) path to the source folder. default is a hello world app. 98 | memory: 512 # (optional) lambda memory size. 99 | timeout: 10 # (optional) lambda timeout. 100 | description: My Express App # (optional) lambda & api gateway description. 101 | env: # (optional) env vars. 102 | DEBUG: 'express:*' # this express specific env var will print express debug logs. 103 | roleName: my-custom-role-name # (optional) custom AWS IAM Role name for setting custom permissions. 104 | traffic: 0.2 # (optional) traffic percentage to apply to this deployment. 105 | layers: # (optional) list of lambda layer arns to attach to your lambda function. 106 | - arn:aws:first:layer 107 | - arn:aws:second:layer 108 | domain: api.serverless.com # (optional) if the domain was registered via AWS Route53 on the account you are deploying to, it will automatically be set-up with your Express app's API Gateway, as well as a free AWS ACM SSL Cert. 109 | vpc: # (optional) vpc configuration to apply on the express lambda function 110 | securityGroupIds: 111 | - abc 112 | - xyz 113 | subnetIds: 114 | - abc 115 | - xyz 116 | region: us-east-2 # (optional) aws region to deploy to. default is us-east-1. 117 | ``` 118 | 119 | Once you've chosen your configuration, run `serverless deploy` again (or simply just `serverless`) to deploy your changes. 120 | 121 | ### Dev Mode 122 | 123 | 124 | 125 | Now that you've got your basic express app up and running, it's time to develop that into a real world application. Instead of having to run `serverless deploy` everytime you make changes you wanna test, run `serverless dev`, which allows the CLI to watch for changes in your source directory as you develop, and deploy instantly on save. 126 | 127 | To enable dev mode, simply run `serverless dev` from within the directory containing the `serverless.yml` file. 128 | 129 | Dev mode also enables live streaming logs from your express app so that you can see the results of your code changes right away on the CLI as they happen. 130 | 131 | ### Monitor 132 | 133 | 134 | 135 | Anytime you need to know more about your running express instance, you can run `serverless info` to view the most critical info. This is especially helpful when you want to know the outputs of your instances so that you can reference them in another instance. You will also see a url where you'll be able to view more info about your instance on the Serverless Dashboard. 136 | 137 | It also shows you the status of your instance, when it was last deployed, and how many times it was deployed. To dig even deeper, you can pass the `--debug` flag to view the state of your component instance in case the deployment failed for any reason. 138 | 139 | ### Remove 140 | 141 | 142 | 143 | If you wanna tear down your entire express infrastructure that was created during deployment, just run `serverless remove` in the directory containing the `serverless.yml` file. The express component will then use all the data it needs from the built-in state storage system to delete only the relavent cloud resources that it created. 144 | 145 | Just like deployment, you could also specify a `--debug` flag for realtime logs from the express component running in the cloud. 146 | 147 | ## Architecture 148 | 149 | This is the AWS serverless infrastructure that is created by this Component: 150 | 151 | - [x] **AWS HTTP API** - The API Gateway which receives all requests and proxies them to AWS Lambda. 152 | - [x] **AWS Lambda** - A single AWS Lambda function runs your Express.js application. 153 | - [x] **AWS IAM** - An AWS IAM role is automatically created, if you do not provide a custom one. 154 | - [x] **AWS Route53** - If you enter a `domain` input and the domain already exists on your AWS account, a Route53 hosted zone will be created and integrated into your API Gateway. 155 | - [x] **AWS ACM SSL Certificate** - If you enter a `domain` input and the domain already exists on your AWS account, a free AWS ACM SSL certificate will be created. 156 | 157 | # Guides 158 | 159 | ### Setting Up A Custom Domain & SSL Certificate 160 | 161 | The Express Component can easily set up a custom domain and free SSL certificate for you. 162 | 163 | First, register your custom domain via Route53 on the AWS Acccount you are deploying to. 164 | 165 | Next, add the domain to the `domain` in `inputs` in `serverless.yml`, like this: 166 | 167 | ```yaml 168 | inputs: 169 | src: ./ 170 | domain: serverlessexpress.com 171 | ``` 172 | 173 | You can also use a subdomain: 174 | 175 | ```yaml 176 | inputs: 177 | src: ./ 178 | domain: express.component-demos.com 179 | ``` 180 | 181 | Run `serverless deploy`. 182 | 183 | Keep in mind, it will take AWS CloudFront and AWS Route53 and DNS up to 24 hours to propagate these changes and make your custom domain globally accessible. However, with recent AWS CloudFront speed increases, your domain should be accessible within ~20 minutes. 184 | 185 | #### Setting up domains registered outside of AWS 186 | 187 | If your domain is not on AWS Route53, you will have to set this up manually because the component does not have access to your registrar. Here are the general steps involved: 188 | 189 | 1. Create an AWS ACM certificate for your domain. Make sure you set the "Additional Names" field to `*.yourdomain.com` as well to include all subdomains as well. 190 | 2. After you create the certificate, it should be in a `PENDING_VALIDATION` status. Now you will need to validate your domain. We suggest you follow the DNS steps by adding the validation CNAME record you see on the AWS console to your domain via your registrar dashboard. 191 | 3. After you add the validation record, it might take a while, but eventually the certificate should change status to `ISSUED`. Usually it takes around 5 minutes. 192 | 4. Add your domain to the `serverless.yml` file as shown above and deploy. This step is important as it adds your domain to API Gateway. 193 | 5. Notice the regional url that is returned as an output. Copy this URL, get back to your registrar and add another CNAME record with your domain or subdomain name and a value of this regional url. This ensures that your domain points to that cloudfront URL. 194 | 6. After around 20 mins, your SSL certificate and domain should all be working and pointing to your URL. Keep in mind that if you change the `name`, `stage`, `app` or `org` properties in `serverless.yml`, this would result in a completely new instance with a new cloudfront url. This allows you to setup different domains for each stage or instance 195 | 196 | ### Canary Deployments 197 | 198 | At scale, when you want to push changes out to a small set of users, Serverless Express offers easy Canary Deployments out of the box! 199 | 200 | This enables you to push out a version of your app (containing code changes you deem risky) which is only served to a percentage of traffic that you specificy (0-99%). This allows you to test big changes with little risk. 201 | 202 | To perform a canary deployment, first update your code with the potentially risky change. 203 | 204 | Next, set a traffic weighting in your `serverless.yml` `inputs`: 205 | 206 | ```yaml 207 | inputs: 208 | src: ./ 209 | traffic: 0.5 # 50% 210 | ``` 211 | 212 | This tells Serverless Express to serve the new (potentially risky) code to 50% of the API requests, and the old (stable) code to the other 50% of requests. 213 | 214 | Run `serverless deploy`. After deployment is complete, 50% of your requests will be randomly handled by the new experimental code. 215 | 216 | You can slowly increment the percentage over time, just continue to re-deploy it. 217 | 218 | If things aren't working, revert your code to the old code, remove the `traffic` configuration option, and deploy. 219 | 220 | If things are working, keep the new code, remove the `traffic` configuration option, and deploy. 221 | 222 | 223 | ### How To Debug CORS Errors 224 | 225 | The Express Component uses AWS HTTP API, which can offer an API with CORS enabled or not enabled based on the headers you set in your code's responses. 226 | 227 | For CORS support you **do not** need to configure it within the HTTP API infrastructure. You have complete control the CORS behavior of your application via setting traditional CORS headers, like this: 228 | 229 | ```javascript 230 | 231 | /** 232 | * Configure Express.js Middleware 233 | */ 234 | 235 | // Enable CORS 236 | app.use(function (req, res, next) { 237 | res.header('Access-Control-Allow-Origin', '*') 238 | res.header('Access-Control-Allow-Methods', '*') 239 | res.header('Access-Control-Allow-Headers', '*') 240 | res.header('x-powered-by', 'serverless-express') 241 | next() 242 | }) 243 | 244 | ``` 245 | 246 | If you run into a CORS issue, ensure you are setting up your Express app to return the right headers, like above. 247 | 248 | THe biggest reason why CORS errors can happen is because users do not capture errors correctly, and then return a correct HTTP response (with the headers above) in that error response. It may look like a CORS error, but actually, your code is crashing and the automatic error response from HTTP API does not contain the CORS headers. 249 | -------------------------------------------------------------------------------- /assets/deploy-debug-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-components/express/17045b8351eeb2de30e13f2d2574cb4f0929732f/assets/deploy-debug-demo.gif -------------------------------------------------------------------------------- /assets/deploy-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-components/express/17045b8351eeb2de30e13f2d2574cb4f0929732f/assets/deploy-demo.gif -------------------------------------------------------------------------------- /assets/dev-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-components/express/17045b8351eeb2de30e13f2d2574cb4f0929732f/assets/dev-demo.gif -------------------------------------------------------------------------------- /assets/info-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-components/express/17045b8351eeb2de30e13f2d2574cb4f0929732f/assets/info-demo.gif -------------------------------------------------------------------------------- /assets/remove-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-components/express/17045b8351eeb2de30e13f2d2574cb4f0929732f/assets/remove-demo.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@serverless-components/express", 3 | "private": true, 4 | "author": "Serverless, Inc.", 5 | "license": "Apache", 6 | "scripts": { 7 | "lint": "eslint --ignore-path .gitignore .", 8 | "lint:updated": "pipe-git-updated --ext=js -- eslint --ignore-path .gitignore", 9 | "postinstall": "./scripts/install-nested-packages.js", 10 | "prettier-check": "prettier -c --ignore-path .gitignore \"**/*.{css,html,js,json,yaml,yml}\"", 11 | "prettier-check:updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=yaml --ext=yml -- prettier -c", 12 | "prettify": "prettier --write --ignore-path .gitignore \"**/*.{css,html,js,json,yaml,yml}\"", 13 | "prettify:updated": "pipe-git-updated --ext=css --ext=html --ext=js --ext=json --ext=yaml --ext=yml -- prettier --write", 14 | "publish": "components registry publish", 15 | "publish:dev": "components registry publish --dev", 16 | "test": "jest --bail 1 --testEnvironment node ./test/integration.test.js" 17 | }, 18 | "devDependencies": { 19 | "@serverless/components": "^2.31.6", 20 | "@serverless/eslint-config": "^2.1.1", 21 | "@serverless/platform-client": "^0.24.0", 22 | "aws-sdk": "^2.704.0", 23 | "axios": ">=0.21.1", 24 | "dotenv": "^8.2.0", 25 | "eslint": "^7.3.1", 26 | "eslint-plugin-import": "^2.21.2", 27 | "git-list-updated": "^1.2.1", 28 | "jest": "^25.5.4", 29 | "prettier": "^2.0.5" 30 | }, 31 | "eslintConfig": { 32 | "extends": "@serverless/eslint-config/node", 33 | "root": true, 34 | "rules": { 35 | "no-console": "off" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('@serverless/eslint-config/prettier.config'); 4 | -------------------------------------------------------------------------------- /scripts/install-nested-packages.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | process.on('unhandledRejection', (reason) => { 6 | throw reason; 7 | }); 8 | 9 | const path = require('path'); 10 | const childProcess = require('child_process'); 11 | 12 | const platformRoot = path.resolve(__dirname, '..'); 13 | 14 | const npmInstall = (packagePath) => 15 | new Promise((resolve, reject) => { 16 | console.log('---------------------------------------'); 17 | console.log(`Install \x1b[33m${packagePath}\x1b[39m ...\n`); 18 | const child = childProcess.spawn('npm', ['install'], { 19 | cwd: path.resolve(platformRoot, packagePath), 20 | stdio: 'inherit', 21 | }); 22 | child.on('error', reject); 23 | child.on('close', (code) => { 24 | if (code) { 25 | reject(new Error(`npm install failed at ${packagePath}`)); 26 | } else { 27 | resolve(); 28 | } 29 | }); 30 | }); 31 | 32 | const packagesPaths = ['src', 'src/_express', 'src/_src', 'test/src']; 33 | 34 | (async () => { 35 | // Running multiple "npm install" prcesses in parallel is not confirmed to be safe. 36 | for (const packagePath of packagesPaths) { 37 | await npmInstall(packagePath); 38 | } 39 | })(); 40 | -------------------------------------------------------------------------------- /serverless.component.yml: -------------------------------------------------------------------------------- 1 | name: express 2 | version: 3.0.1 3 | author: eahefnawy 4 | org: serverlessinc 5 | description: Deploys a serverless Express.js application onto AWS Lambda and AWS HTTP API. 6 | keywords: aws, serverless, express 7 | repo: https://github.com/serverless-components/express 8 | license: MIT 9 | main: ./src 10 | 11 | types: 12 | providers: 13 | - aws 14 | 15 | actions: 16 | # deploy 17 | deploy: 18 | description: Deploy your Express.js application to AWS Lambda, AWS HTTP API and more. 19 | inputs: 20 | src: 21 | type: src 22 | description: The folder containing the source code of your Express.js application. 23 | 24 | memory: 25 | type: number 26 | description: The memory size of your AWS Lambda function. 27 | default: 1024 28 | allow: 29 | - 128 30 | - 192 31 | - 256 32 | - 320 33 | - 384 34 | - 448 35 | - 512 36 | - 576 37 | - 704 38 | - 768 39 | - 832 40 | - 1024 41 | - 1280 42 | - 1536 43 | - 1792 44 | - 2048 45 | - 2240 46 | - 2688 47 | - 2944 48 | - 3008 49 | 50 | timeout: 51 | type: number 52 | description: The number of seconds until your AWS Lambda function times out. 53 | default: 5 54 | min: 1 55 | max: 900 56 | 57 | description: 58 | type: string 59 | description: A description of your application. 60 | 61 | region: 62 | type: string 63 | description: The AWS region you wish to deploy your application to. 64 | default: us-east-1 65 | allow: 66 | - us-east-1 67 | - us-east-2 68 | - us-west-1 69 | - us-west-2 70 | - af-south-1 71 | - ap-east-1 72 | - ap-south-1 73 | - ap-northeast-1 74 | - ap-northeast-2 75 | - ap-southeast-1 76 | - ap-southeast-2 77 | - ca-central-1 78 | - cn-north-1 79 | - cn-northwest-1 80 | - eu-central-1 81 | - eu-west-1 82 | - eu-west-2 83 | - eu-west-3 84 | - eu-south-1 85 | - eu-north-1 86 | - me-south-1 87 | - sa-east-1 88 | - us-gov-east-1 89 | - us-gov-west-1 90 | 91 | domain: 92 | type: string 93 | description: A custom domain that will be automatically set up for your Express.js API. 94 | 95 | roleName: 96 | type: string 97 | description: The name of an AWS IAM Role that contains custom permissions for your AWS Lambda function. 98 | 99 | env: 100 | type: env 101 | description: Environment variables to be passed into your AWS Lambda function runtime. 102 | 103 | layers: 104 | type: array 105 | items: 106 | - type: string 107 | description: An array of AWS Lambda Layer ARNs to attach to your AWS Lambda function. 108 | min: 0 109 | max: 5 110 | 111 | traffic: 112 | type: number 113 | description: A percentage of traffic expressed as a decimal between 0.0 and 1.0 (e.g. 0.4) to serve your latest deployment to 114 | min: 0 115 | max: 1 116 | 117 | vpc: 118 | type: object 119 | description: The VPC configuration for your AWS Lambda function 120 | keys: 121 | securityGroupIds: 122 | type: array 123 | items: 124 | - type: string 125 | subnetIds: 126 | type: array 127 | items: 128 | - type: string 129 | 130 | # remove 131 | 132 | remove: 133 | description: Removes your Express.js application from AWS Lambda, AWS HTTP API and more 134 | 135 | # metrics 136 | 137 | metrics: 138 | description: Fetch metrics from your Express.js API 139 | -------------------------------------------------------------------------------- /src/_express/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const serverlessHttp = require('serverless-http'); 4 | 5 | // remove AWS Lambda default handling of unhandled rejections 6 | // this makes sure dev mode cli catches unhandled rejections 7 | process.removeAllListeners('unhandledRejection'); 8 | 9 | let app = (_req, res) => { 10 | res.statusCode = 200; 11 | res.end(`Request received: ${_req.method} - ${_req.path}`); 12 | }; 13 | 14 | try { 15 | // eslint-disable-next-line import/no-unresolved 16 | app = require('..'); 17 | 18 | // check if the user forgot to export their express app 19 | if (typeof app === 'object' && Object.keys(app).length === 0) { 20 | app = (_req, res) => { 21 | const e = new Error( 22 | 'App not found. Please make sure the app is probably exported from the JS file.' 23 | ); 24 | console.error(e); 25 | res.statusCode = 404; 26 | res.end(e.stack); 27 | }; 28 | } 29 | } catch (e) { 30 | // return require error and log it for dev mode 31 | app = (_req, res) => { 32 | console.error(e); 33 | res.statusCode = 500; 34 | res.end(e.stack); 35 | }; 36 | } 37 | 38 | // default error handler logs errorss for dev mode 39 | if (typeof app.use === 'function') { 40 | // eslint-disable-next-line 41 | app.use((err, req, res, next) => { 42 | console.error(err.stack); 43 | throw err; 44 | }); 45 | } 46 | 47 | const handle = serverlessHttp(app); 48 | 49 | exports.handler = async (event, context) => { 50 | const res = await handle(event, context); 51 | 52 | return res; 53 | }; 54 | -------------------------------------------------------------------------------- /src/_express/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_express", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "license": "Apache", 8 | "dependencies": { 9 | "serverless-http": "^2.7.0" 10 | } 11 | }, 12 | "node_modules/@types/aws-lambda": { 13 | "version": "8.10.72", 14 | "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.72.tgz", 15 | "integrity": "sha512-jOrTwAhSiUtBIN/QsWNKlI4+4aDtpZ0sr2BRvKW6XQZdspgHUSHPcuzxbzCRiHUiDQ+0026u5TSE38VyIhNnfA==", 16 | "optional": true 17 | }, 18 | "node_modules/serverless-http": { 19 | "version": "2.7.0", 20 | "resolved": "https://registry.npmjs.org/serverless-http/-/serverless-http-2.7.0.tgz", 21 | "integrity": "sha512-iWq0z1X2Xkuvz6wL305uCux/SypbojHlYsB5bzmF5TqoLYsdvMNIoCsgtWjwqWoo3AR2cjw3zAmHN2+U6mF99Q==", 22 | "engines": { 23 | "node": ">=8.0" 24 | }, 25 | "optionalDependencies": { 26 | "@types/aws-lambda": "^8.10.56" 27 | } 28 | } 29 | }, 30 | "dependencies": { 31 | "@types/aws-lambda": { 32 | "version": "8.10.72", 33 | "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.72.tgz", 34 | "integrity": "sha512-jOrTwAhSiUtBIN/QsWNKlI4+4aDtpZ0sr2BRvKW6XQZdspgHUSHPcuzxbzCRiHUiDQ+0026u5TSE38VyIhNnfA==", 35 | "optional": true 36 | }, 37 | "serverless-http": { 38 | "version": "2.7.0", 39 | "resolved": "https://registry.npmjs.org/serverless-http/-/serverless-http-2.7.0.tgz", 40 | "integrity": "sha512-iWq0z1X2Xkuvz6wL305uCux/SypbojHlYsB5bzmF5TqoLYsdvMNIoCsgtWjwqWoo3AR2cjw3zAmHN2+U6mF99Q==", 41 | "requires": { 42 | "@types/aws-lambda": "^8.10.56" 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/_express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./handler.js", 3 | "author": "Serverless, Inc.", 4 | "license": "Apache", 5 | "dependencies": { 6 | "serverless-http": "^2.7.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/_src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const app = express(); 6 | 7 | // Routes 8 | app.get('/*', (req, res) => { 9 | res.send(`Request received: ${req.method} - ${req.path}`); 10 | }); 11 | 12 | // Error handler 13 | app.use((err, req, res) => { 14 | console.error(err); 15 | res.status(500).send('Internal Serverless Error'); 16 | }); 17 | 18 | module.exports = app; 19 | -------------------------------------------------------------------------------- /src/_src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "_src", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "license": "Apache", 8 | "dependencies": { 9 | "express": "^4.17.1" 10 | } 11 | }, 12 | "node_modules/accepts": { 13 | "version": "1.3.7", 14 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 15 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 16 | "dependencies": { 17 | "mime-types": "~2.1.24", 18 | "negotiator": "0.6.2" 19 | }, 20 | "engines": { 21 | "node": ">= 0.6" 22 | } 23 | }, 24 | "node_modules/array-flatten": { 25 | "version": "1.1.1", 26 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 27 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 28 | }, 29 | "node_modules/body-parser": { 30 | "version": "1.19.0", 31 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 32 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 33 | "dependencies": { 34 | "bytes": "3.1.0", 35 | "content-type": "~1.0.4", 36 | "debug": "2.6.9", 37 | "depd": "~1.1.2", 38 | "http-errors": "1.7.2", 39 | "iconv-lite": "0.4.24", 40 | "on-finished": "~2.3.0", 41 | "qs": "6.7.0", 42 | "raw-body": "2.4.0", 43 | "type-is": "~1.6.17" 44 | }, 45 | "engines": { 46 | "node": ">= 0.8" 47 | } 48 | }, 49 | "node_modules/bytes": { 50 | "version": "3.1.0", 51 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 52 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", 53 | "engines": { 54 | "node": ">= 0.8" 55 | } 56 | }, 57 | "node_modules/content-disposition": { 58 | "version": "0.5.3", 59 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 60 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 61 | "dependencies": { 62 | "safe-buffer": "5.1.2" 63 | }, 64 | "engines": { 65 | "node": ">= 0.6" 66 | } 67 | }, 68 | "node_modules/content-type": { 69 | "version": "1.0.4", 70 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 71 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 72 | "engines": { 73 | "node": ">= 0.6" 74 | } 75 | }, 76 | "node_modules/cookie": { 77 | "version": "0.4.0", 78 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 79 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", 80 | "engines": { 81 | "node": ">= 0.6" 82 | } 83 | }, 84 | "node_modules/cookie-signature": { 85 | "version": "1.0.6", 86 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 87 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 88 | }, 89 | "node_modules/debug": { 90 | "version": "2.6.9", 91 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 92 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 93 | "dependencies": { 94 | "ms": "2.0.0" 95 | } 96 | }, 97 | "node_modules/depd": { 98 | "version": "1.1.2", 99 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 100 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 101 | "engines": { 102 | "node": ">= 0.6" 103 | } 104 | }, 105 | "node_modules/destroy": { 106 | "version": "1.0.4", 107 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 108 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 109 | }, 110 | "node_modules/ee-first": { 111 | "version": "1.1.1", 112 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 113 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 114 | }, 115 | "node_modules/encodeurl": { 116 | "version": "1.0.2", 117 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 118 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 119 | "engines": { 120 | "node": ">= 0.8" 121 | } 122 | }, 123 | "node_modules/escape-html": { 124 | "version": "1.0.3", 125 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 126 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 127 | }, 128 | "node_modules/etag": { 129 | "version": "1.8.1", 130 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 131 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 132 | "engines": { 133 | "node": ">= 0.6" 134 | } 135 | }, 136 | "node_modules/express": { 137 | "version": "4.17.1", 138 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 139 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 140 | "dependencies": { 141 | "accepts": "~1.3.7", 142 | "array-flatten": "1.1.1", 143 | "body-parser": "1.19.0", 144 | "content-disposition": "0.5.3", 145 | "content-type": "~1.0.4", 146 | "cookie": "0.4.0", 147 | "cookie-signature": "1.0.6", 148 | "debug": "2.6.9", 149 | "depd": "~1.1.2", 150 | "encodeurl": "~1.0.2", 151 | "escape-html": "~1.0.3", 152 | "etag": "~1.8.1", 153 | "finalhandler": "~1.1.2", 154 | "fresh": "0.5.2", 155 | "merge-descriptors": "1.0.1", 156 | "methods": "~1.1.2", 157 | "on-finished": "~2.3.0", 158 | "parseurl": "~1.3.3", 159 | "path-to-regexp": "0.1.7", 160 | "proxy-addr": "~2.0.5", 161 | "qs": "6.7.0", 162 | "range-parser": "~1.2.1", 163 | "safe-buffer": "5.1.2", 164 | "send": "0.17.1", 165 | "serve-static": "1.14.1", 166 | "setprototypeof": "1.1.1", 167 | "statuses": "~1.5.0", 168 | "type-is": "~1.6.18", 169 | "utils-merge": "1.0.1", 170 | "vary": "~1.1.2" 171 | }, 172 | "engines": { 173 | "node": ">= 0.10.0" 174 | } 175 | }, 176 | "node_modules/finalhandler": { 177 | "version": "1.1.2", 178 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 179 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 180 | "dependencies": { 181 | "debug": "2.6.9", 182 | "encodeurl": "~1.0.2", 183 | "escape-html": "~1.0.3", 184 | "on-finished": "~2.3.0", 185 | "parseurl": "~1.3.3", 186 | "statuses": "~1.5.0", 187 | "unpipe": "~1.0.0" 188 | }, 189 | "engines": { 190 | "node": ">= 0.8" 191 | } 192 | }, 193 | "node_modules/forwarded": { 194 | "version": "0.1.2", 195 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 196 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", 197 | "engines": { 198 | "node": ">= 0.6" 199 | } 200 | }, 201 | "node_modules/fresh": { 202 | "version": "0.5.2", 203 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 204 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 205 | "engines": { 206 | "node": ">= 0.6" 207 | } 208 | }, 209 | "node_modules/http-errors": { 210 | "version": "1.7.2", 211 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 212 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 213 | "dependencies": { 214 | "depd": "~1.1.2", 215 | "inherits": "2.0.3", 216 | "setprototypeof": "1.1.1", 217 | "statuses": ">= 1.5.0 < 2", 218 | "toidentifier": "1.0.0" 219 | }, 220 | "engines": { 221 | "node": ">= 0.6" 222 | } 223 | }, 224 | "node_modules/iconv-lite": { 225 | "version": "0.4.24", 226 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 227 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 228 | "dependencies": { 229 | "safer-buffer": ">= 2.1.2 < 3" 230 | }, 231 | "engines": { 232 | "node": ">=0.10.0" 233 | } 234 | }, 235 | "node_modules/inherits": { 236 | "version": "2.0.3", 237 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 238 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 239 | }, 240 | "node_modules/ipaddr.js": { 241 | "version": "1.9.1", 242 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 243 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 244 | "engines": { 245 | "node": ">= 0.10" 246 | } 247 | }, 248 | "node_modules/media-typer": { 249 | "version": "0.3.0", 250 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 251 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 252 | "engines": { 253 | "node": ">= 0.6" 254 | } 255 | }, 256 | "node_modules/merge-descriptors": { 257 | "version": "1.0.1", 258 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 259 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 260 | }, 261 | "node_modules/methods": { 262 | "version": "1.1.2", 263 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 264 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 265 | "engines": { 266 | "node": ">= 0.6" 267 | } 268 | }, 269 | "node_modules/mime": { 270 | "version": "1.6.0", 271 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 272 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 273 | "bin": { 274 | "mime": "cli.js" 275 | }, 276 | "engines": { 277 | "node": ">=4" 278 | } 279 | }, 280 | "node_modules/mime-db": { 281 | "version": "1.46.0", 282 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", 283 | "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", 284 | "engines": { 285 | "node": ">= 0.6" 286 | } 287 | }, 288 | "node_modules/mime-types": { 289 | "version": "2.1.29", 290 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", 291 | "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", 292 | "dependencies": { 293 | "mime-db": "1.46.0" 294 | }, 295 | "engines": { 296 | "node": ">= 0.6" 297 | } 298 | }, 299 | "node_modules/ms": { 300 | "version": "2.0.0", 301 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 302 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 303 | }, 304 | "node_modules/negotiator": { 305 | "version": "0.6.2", 306 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 307 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", 308 | "engines": { 309 | "node": ">= 0.6" 310 | } 311 | }, 312 | "node_modules/on-finished": { 313 | "version": "2.3.0", 314 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 315 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 316 | "dependencies": { 317 | "ee-first": "1.1.1" 318 | }, 319 | "engines": { 320 | "node": ">= 0.8" 321 | } 322 | }, 323 | "node_modules/parseurl": { 324 | "version": "1.3.3", 325 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 326 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 327 | "engines": { 328 | "node": ">= 0.8" 329 | } 330 | }, 331 | "node_modules/path-to-regexp": { 332 | "version": "0.1.7", 333 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 334 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 335 | }, 336 | "node_modules/proxy-addr": { 337 | "version": "2.0.6", 338 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 339 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 340 | "dependencies": { 341 | "forwarded": "~0.1.2", 342 | "ipaddr.js": "1.9.1" 343 | }, 344 | "engines": { 345 | "node": ">= 0.10" 346 | } 347 | }, 348 | "node_modules/qs": { 349 | "version": "6.7.0", 350 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 351 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", 352 | "engines": { 353 | "node": ">=0.6" 354 | } 355 | }, 356 | "node_modules/range-parser": { 357 | "version": "1.2.1", 358 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 359 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 360 | "engines": { 361 | "node": ">= 0.6" 362 | } 363 | }, 364 | "node_modules/raw-body": { 365 | "version": "2.4.0", 366 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 367 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 368 | "dependencies": { 369 | "bytes": "3.1.0", 370 | "http-errors": "1.7.2", 371 | "iconv-lite": "0.4.24", 372 | "unpipe": "1.0.0" 373 | }, 374 | "engines": { 375 | "node": ">= 0.8" 376 | } 377 | }, 378 | "node_modules/safe-buffer": { 379 | "version": "5.1.2", 380 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 381 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 382 | }, 383 | "node_modules/safer-buffer": { 384 | "version": "2.1.2", 385 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 386 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 387 | }, 388 | "node_modules/send": { 389 | "version": "0.17.1", 390 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 391 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 392 | "dependencies": { 393 | "debug": "2.6.9", 394 | "depd": "~1.1.2", 395 | "destroy": "~1.0.4", 396 | "encodeurl": "~1.0.2", 397 | "escape-html": "~1.0.3", 398 | "etag": "~1.8.1", 399 | "fresh": "0.5.2", 400 | "http-errors": "~1.7.2", 401 | "mime": "1.6.0", 402 | "ms": "2.1.1", 403 | "on-finished": "~2.3.0", 404 | "range-parser": "~1.2.1", 405 | "statuses": "~1.5.0" 406 | }, 407 | "engines": { 408 | "node": ">= 0.8.0" 409 | } 410 | }, 411 | "node_modules/send/node_modules/ms": { 412 | "version": "2.1.1", 413 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 414 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 415 | }, 416 | "node_modules/serve-static": { 417 | "version": "1.14.1", 418 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 419 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 420 | "dependencies": { 421 | "encodeurl": "~1.0.2", 422 | "escape-html": "~1.0.3", 423 | "parseurl": "~1.3.3", 424 | "send": "0.17.1" 425 | }, 426 | "engines": { 427 | "node": ">= 0.8.0" 428 | } 429 | }, 430 | "node_modules/setprototypeof": { 431 | "version": "1.1.1", 432 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 433 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 434 | }, 435 | "node_modules/statuses": { 436 | "version": "1.5.0", 437 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 438 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 439 | "engines": { 440 | "node": ">= 0.6" 441 | } 442 | }, 443 | "node_modules/toidentifier": { 444 | "version": "1.0.0", 445 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 446 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", 447 | "engines": { 448 | "node": ">=0.6" 449 | } 450 | }, 451 | "node_modules/type-is": { 452 | "version": "1.6.18", 453 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 454 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 455 | "dependencies": { 456 | "media-typer": "0.3.0", 457 | "mime-types": "~2.1.24" 458 | }, 459 | "engines": { 460 | "node": ">= 0.6" 461 | } 462 | }, 463 | "node_modules/unpipe": { 464 | "version": "1.0.0", 465 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 466 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 467 | "engines": { 468 | "node": ">= 0.8" 469 | } 470 | }, 471 | "node_modules/utils-merge": { 472 | "version": "1.0.1", 473 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 474 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 475 | "engines": { 476 | "node": ">= 0.4.0" 477 | } 478 | }, 479 | "node_modules/vary": { 480 | "version": "1.1.2", 481 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 482 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 483 | "engines": { 484 | "node": ">= 0.8" 485 | } 486 | } 487 | }, 488 | "dependencies": { 489 | "accepts": { 490 | "version": "1.3.7", 491 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 492 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 493 | "requires": { 494 | "mime-types": "~2.1.24", 495 | "negotiator": "0.6.2" 496 | } 497 | }, 498 | "array-flatten": { 499 | "version": "1.1.1", 500 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 501 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 502 | }, 503 | "body-parser": { 504 | "version": "1.19.0", 505 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 506 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 507 | "requires": { 508 | "bytes": "3.1.0", 509 | "content-type": "~1.0.4", 510 | "debug": "2.6.9", 511 | "depd": "~1.1.2", 512 | "http-errors": "1.7.2", 513 | "iconv-lite": "0.4.24", 514 | "on-finished": "~2.3.0", 515 | "qs": "6.7.0", 516 | "raw-body": "2.4.0", 517 | "type-is": "~1.6.17" 518 | } 519 | }, 520 | "bytes": { 521 | "version": "3.1.0", 522 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 523 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 524 | }, 525 | "content-disposition": { 526 | "version": "0.5.3", 527 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 528 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 529 | "requires": { 530 | "safe-buffer": "5.1.2" 531 | } 532 | }, 533 | "content-type": { 534 | "version": "1.0.4", 535 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 536 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 537 | }, 538 | "cookie": { 539 | "version": "0.4.0", 540 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 541 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 542 | }, 543 | "cookie-signature": { 544 | "version": "1.0.6", 545 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 546 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 547 | }, 548 | "debug": { 549 | "version": "2.6.9", 550 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 551 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 552 | "requires": { 553 | "ms": "2.0.0" 554 | } 555 | }, 556 | "depd": { 557 | "version": "1.1.2", 558 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 559 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 560 | }, 561 | "destroy": { 562 | "version": "1.0.4", 563 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 564 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 565 | }, 566 | "ee-first": { 567 | "version": "1.1.1", 568 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 569 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 570 | }, 571 | "encodeurl": { 572 | "version": "1.0.2", 573 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 574 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 575 | }, 576 | "escape-html": { 577 | "version": "1.0.3", 578 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 579 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 580 | }, 581 | "etag": { 582 | "version": "1.8.1", 583 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 584 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 585 | }, 586 | "express": { 587 | "version": "4.17.1", 588 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 589 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 590 | "requires": { 591 | "accepts": "~1.3.7", 592 | "array-flatten": "1.1.1", 593 | "body-parser": "1.19.0", 594 | "content-disposition": "0.5.3", 595 | "content-type": "~1.0.4", 596 | "cookie": "0.4.0", 597 | "cookie-signature": "1.0.6", 598 | "debug": "2.6.9", 599 | "depd": "~1.1.2", 600 | "encodeurl": "~1.0.2", 601 | "escape-html": "~1.0.3", 602 | "etag": "~1.8.1", 603 | "finalhandler": "~1.1.2", 604 | "fresh": "0.5.2", 605 | "merge-descriptors": "1.0.1", 606 | "methods": "~1.1.2", 607 | "on-finished": "~2.3.0", 608 | "parseurl": "~1.3.3", 609 | "path-to-regexp": "0.1.7", 610 | "proxy-addr": "~2.0.5", 611 | "qs": "6.7.0", 612 | "range-parser": "~1.2.1", 613 | "safe-buffer": "5.1.2", 614 | "send": "0.17.1", 615 | "serve-static": "1.14.1", 616 | "setprototypeof": "1.1.1", 617 | "statuses": "~1.5.0", 618 | "type-is": "~1.6.18", 619 | "utils-merge": "1.0.1", 620 | "vary": "~1.1.2" 621 | } 622 | }, 623 | "finalhandler": { 624 | "version": "1.1.2", 625 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 626 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 627 | "requires": { 628 | "debug": "2.6.9", 629 | "encodeurl": "~1.0.2", 630 | "escape-html": "~1.0.3", 631 | "on-finished": "~2.3.0", 632 | "parseurl": "~1.3.3", 633 | "statuses": "~1.5.0", 634 | "unpipe": "~1.0.0" 635 | } 636 | }, 637 | "forwarded": { 638 | "version": "0.1.2", 639 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 640 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 641 | }, 642 | "fresh": { 643 | "version": "0.5.2", 644 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 645 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 646 | }, 647 | "http-errors": { 648 | "version": "1.7.2", 649 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 650 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 651 | "requires": { 652 | "depd": "~1.1.2", 653 | "inherits": "2.0.3", 654 | "setprototypeof": "1.1.1", 655 | "statuses": ">= 1.5.0 < 2", 656 | "toidentifier": "1.0.0" 657 | } 658 | }, 659 | "iconv-lite": { 660 | "version": "0.4.24", 661 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 662 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 663 | "requires": { 664 | "safer-buffer": ">= 2.1.2 < 3" 665 | } 666 | }, 667 | "inherits": { 668 | "version": "2.0.3", 669 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 670 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 671 | }, 672 | "ipaddr.js": { 673 | "version": "1.9.1", 674 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 675 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 676 | }, 677 | "media-typer": { 678 | "version": "0.3.0", 679 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 680 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 681 | }, 682 | "merge-descriptors": { 683 | "version": "1.0.1", 684 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 685 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 686 | }, 687 | "methods": { 688 | "version": "1.1.2", 689 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 690 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 691 | }, 692 | "mime": { 693 | "version": "1.6.0", 694 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 695 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 696 | }, 697 | "mime-db": { 698 | "version": "1.46.0", 699 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", 700 | "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==" 701 | }, 702 | "mime-types": { 703 | "version": "2.1.29", 704 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", 705 | "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", 706 | "requires": { 707 | "mime-db": "1.46.0" 708 | } 709 | }, 710 | "ms": { 711 | "version": "2.0.0", 712 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 713 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 714 | }, 715 | "negotiator": { 716 | "version": "0.6.2", 717 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 718 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 719 | }, 720 | "on-finished": { 721 | "version": "2.3.0", 722 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 723 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 724 | "requires": { 725 | "ee-first": "1.1.1" 726 | } 727 | }, 728 | "parseurl": { 729 | "version": "1.3.3", 730 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 731 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 732 | }, 733 | "path-to-regexp": { 734 | "version": "0.1.7", 735 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 736 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 737 | }, 738 | "proxy-addr": { 739 | "version": "2.0.6", 740 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 741 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 742 | "requires": { 743 | "forwarded": "~0.1.2", 744 | "ipaddr.js": "1.9.1" 745 | } 746 | }, 747 | "qs": { 748 | "version": "6.7.0", 749 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 750 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 751 | }, 752 | "range-parser": { 753 | "version": "1.2.1", 754 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 755 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 756 | }, 757 | "raw-body": { 758 | "version": "2.4.0", 759 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 760 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 761 | "requires": { 762 | "bytes": "3.1.0", 763 | "http-errors": "1.7.2", 764 | "iconv-lite": "0.4.24", 765 | "unpipe": "1.0.0" 766 | } 767 | }, 768 | "safe-buffer": { 769 | "version": "5.1.2", 770 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 771 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 772 | }, 773 | "safer-buffer": { 774 | "version": "2.1.2", 775 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 776 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 777 | }, 778 | "send": { 779 | "version": "0.17.1", 780 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 781 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 782 | "requires": { 783 | "debug": "2.6.9", 784 | "depd": "~1.1.2", 785 | "destroy": "~1.0.4", 786 | "encodeurl": "~1.0.2", 787 | "escape-html": "~1.0.3", 788 | "etag": "~1.8.1", 789 | "fresh": "0.5.2", 790 | "http-errors": "~1.7.2", 791 | "mime": "1.6.0", 792 | "ms": "2.1.1", 793 | "on-finished": "~2.3.0", 794 | "range-parser": "~1.2.1", 795 | "statuses": "~1.5.0" 796 | }, 797 | "dependencies": { 798 | "ms": { 799 | "version": "2.1.1", 800 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 801 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 802 | } 803 | } 804 | }, 805 | "serve-static": { 806 | "version": "1.14.1", 807 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 808 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 809 | "requires": { 810 | "encodeurl": "~1.0.2", 811 | "escape-html": "~1.0.3", 812 | "parseurl": "~1.3.3", 813 | "send": "0.17.1" 814 | } 815 | }, 816 | "setprototypeof": { 817 | "version": "1.1.1", 818 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 819 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 820 | }, 821 | "statuses": { 822 | "version": "1.5.0", 823 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 824 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 825 | }, 826 | "toidentifier": { 827 | "version": "1.0.0", 828 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 829 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 830 | }, 831 | "type-is": { 832 | "version": "1.6.18", 833 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 834 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 835 | "requires": { 836 | "media-typer": "0.3.0", 837 | "mime-types": "~2.1.24" 838 | } 839 | }, 840 | "unpipe": { 841 | "version": "1.0.0", 842 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 843 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 844 | }, 845 | "utils-merge": { 846 | "version": "1.0.1", 847 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 848 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 849 | }, 850 | "vary": { 851 | "version": "1.1.2", 852 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 853 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 854 | } 855 | } 856 | } 857 | -------------------------------------------------------------------------------- /src/_src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "app.js", 3 | "author": "Serverless, Inc.", 4 | "license": "Apache", 5 | "dependencies": { 6 | "express": "^4.17.1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "license": "Apache", 8 | "dependencies": { 9 | "@serverless/aws-sdk-extra": "^1.0.0", 10 | "fs-extra": "^8.1.0", 11 | "moment": "^2.27.0", 12 | "parse-domain": "^3.0.3" 13 | } 14 | }, 15 | "node_modules/@serverless/aws-sdk-extra": { 16 | "version": "1.2.7", 17 | "resolved": "https://registry.npmjs.org/@serverless/aws-sdk-extra/-/aws-sdk-extra-1.2.7.tgz", 18 | "integrity": "sha512-52NT+eM1dEMRb3inAeFhcRVBNcTE8/+mZ9OIuqsqXvGbXvjS2gaxaHzSgaFoqPw9LOUpeCHYDaSDhZ+aOqNwdQ==", 19 | "dependencies": { 20 | "adm-zip": "^0.4.14", 21 | "aws-sdk": "^2.614.0", 22 | "merge-deep": "^3.0.2", 23 | "moment": "^2.27.0", 24 | "ramda": "^0.26.1" 25 | } 26 | }, 27 | "node_modules/adm-zip": { 28 | "version": "0.4.16", 29 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", 30 | "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", 31 | "engines": { 32 | "node": ">=0.3.0" 33 | } 34 | }, 35 | "node_modules/arr-union": { 36 | "version": "3.1.0", 37 | "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", 38 | "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", 39 | "engines": { 40 | "node": ">=0.10.0" 41 | } 42 | }, 43 | "node_modules/aws-sdk": { 44 | "version": "2.874.0", 45 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.874.0.tgz", 46 | "integrity": "sha512-YF2LYIfIuywFTzojGwwUwAiq+pgSM3IWRF/uDoJa28xRQSfYD1uMeZLCqqMt+L0/yxe2UJDYKTVPhB9eEqOxEQ==", 47 | "dependencies": { 48 | "buffer": "4.9.2", 49 | "events": "1.1.1", 50 | "ieee754": "1.1.13", 51 | "jmespath": "0.15.0", 52 | "querystring": "0.2.0", 53 | "sax": "1.2.1", 54 | "url": "0.10.3", 55 | "uuid": "3.3.2", 56 | "xml2js": "0.4.19" 57 | }, 58 | "engines": { 59 | "node": ">= 0.8.0" 60 | } 61 | }, 62 | "node_modules/base64-js": { 63 | "version": "1.5.1", 64 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 65 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 66 | "funding": [ 67 | { 68 | "type": "github", 69 | "url": "https://github.com/sponsors/feross" 70 | }, 71 | { 72 | "type": "patreon", 73 | "url": "https://www.patreon.com/feross" 74 | }, 75 | { 76 | "type": "consulting", 77 | "url": "https://feross.org/support" 78 | } 79 | ] 80 | }, 81 | "node_modules/buffer": { 82 | "version": "4.9.2", 83 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 84 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 85 | "dependencies": { 86 | "base64-js": "^1.0.2", 87 | "ieee754": "^1.1.4", 88 | "isarray": "^1.0.0" 89 | } 90 | }, 91 | "node_modules/clone-deep": { 92 | "version": "0.2.4", 93 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", 94 | "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", 95 | "dependencies": { 96 | "for-own": "^0.1.3", 97 | "is-plain-object": "^2.0.1", 98 | "kind-of": "^3.0.2", 99 | "lazy-cache": "^1.0.3", 100 | "shallow-clone": "^0.1.2" 101 | }, 102 | "engines": { 103 | "node": ">=0.10.0" 104 | } 105 | }, 106 | "node_modules/events": { 107 | "version": "1.1.1", 108 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 109 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", 110 | "engines": { 111 | "node": ">=0.4.x" 112 | } 113 | }, 114 | "node_modules/for-in": { 115 | "version": "1.0.2", 116 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 117 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", 118 | "engines": { 119 | "node": ">=0.10.0" 120 | } 121 | }, 122 | "node_modules/for-own": { 123 | "version": "0.1.5", 124 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", 125 | "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", 126 | "dependencies": { 127 | "for-in": "^1.0.1" 128 | }, 129 | "engines": { 130 | "node": ">=0.10.0" 131 | } 132 | }, 133 | "node_modules/fs-extra": { 134 | "version": "8.1.0", 135 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", 136 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 137 | "dependencies": { 138 | "graceful-fs": "^4.2.0", 139 | "jsonfile": "^4.0.0", 140 | "universalify": "^0.1.0" 141 | }, 142 | "engines": { 143 | "node": ">=6 <7 || >=8" 144 | } 145 | }, 146 | "node_modules/graceful-fs": { 147 | "version": "4.2.6", 148 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", 149 | "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" 150 | }, 151 | "node_modules/ieee754": { 152 | "version": "1.1.13", 153 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 154 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 155 | }, 156 | "node_modules/ip-regex": { 157 | "version": "4.3.0", 158 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", 159 | "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", 160 | "engines": { 161 | "node": ">=8" 162 | } 163 | }, 164 | "node_modules/is-buffer": { 165 | "version": "1.1.6", 166 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 167 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 168 | }, 169 | "node_modules/is-extendable": { 170 | "version": "0.1.1", 171 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 172 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", 173 | "engines": { 174 | "node": ">=0.10.0" 175 | } 176 | }, 177 | "node_modules/is-ip": { 178 | "version": "3.1.0", 179 | "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", 180 | "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", 181 | "dependencies": { 182 | "ip-regex": "^4.0.0" 183 | }, 184 | "engines": { 185 | "node": ">=8" 186 | } 187 | }, 188 | "node_modules/is-plain-object": { 189 | "version": "2.0.4", 190 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 191 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 192 | "dependencies": { 193 | "isobject": "^3.0.1" 194 | }, 195 | "engines": { 196 | "node": ">=0.10.0" 197 | } 198 | }, 199 | "node_modules/isarray": { 200 | "version": "1.0.0", 201 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 202 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 203 | }, 204 | "node_modules/isobject": { 205 | "version": "3.0.1", 206 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 207 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", 208 | "engines": { 209 | "node": ">=0.10.0" 210 | } 211 | }, 212 | "node_modules/jmespath": { 213 | "version": "0.15.0", 214 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 215 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=", 216 | "engines": { 217 | "node": ">= 0.6.0" 218 | } 219 | }, 220 | "node_modules/jsonfile": { 221 | "version": "4.0.0", 222 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 223 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 224 | "optionalDependencies": { 225 | "graceful-fs": "^4.1.6" 226 | } 227 | }, 228 | "node_modules/kind-of": { 229 | "version": "3.2.2", 230 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 231 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 232 | "dependencies": { 233 | "is-buffer": "^1.1.5" 234 | }, 235 | "engines": { 236 | "node": ">=0.10.0" 237 | } 238 | }, 239 | "node_modules/lazy-cache": { 240 | "version": "1.0.4", 241 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 242 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", 243 | "engines": { 244 | "node": ">=0.10.0" 245 | } 246 | }, 247 | "node_modules/merge-deep": { 248 | "version": "3.0.3", 249 | "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", 250 | "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", 251 | "dependencies": { 252 | "arr-union": "^3.1.0", 253 | "clone-deep": "^0.2.4", 254 | "kind-of": "^3.0.2" 255 | }, 256 | "engines": { 257 | "node": ">=0.10.0" 258 | } 259 | }, 260 | "node_modules/mixin-object": { 261 | "version": "2.0.1", 262 | "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", 263 | "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", 264 | "dependencies": { 265 | "for-in": "^0.1.3", 266 | "is-extendable": "^0.1.1" 267 | }, 268 | "engines": { 269 | "node": ">=0.10.0" 270 | } 271 | }, 272 | "node_modules/mixin-object/node_modules/for-in": { 273 | "version": "0.1.8", 274 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", 275 | "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", 276 | "engines": { 277 | "node": ">=0.10.0" 278 | } 279 | }, 280 | "node_modules/moment": { 281 | "version": "2.29.1", 282 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 283 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", 284 | "engines": { 285 | "node": "*" 286 | } 287 | }, 288 | "node_modules/node-fetch": { 289 | "version": "2.6.1", 290 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 291 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", 292 | "engines": { 293 | "node": "4.x || >=6.0.0" 294 | } 295 | }, 296 | "node_modules/parse-domain": { 297 | "version": "3.0.3", 298 | "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-3.0.3.tgz", 299 | "integrity": "sha512-KOJR8kEymjWO5xHrt57LFJ4xtncwGfd/Z9+Twm6apKU9NIw3uSnwYTAoRUwC+MflGsn5h6MeyHltz6Fa6KT7cA==", 300 | "dependencies": { 301 | "is-ip": "^3.1.0", 302 | "node-fetch": "^2.6.0", 303 | "punycode": "^2.1.1" 304 | }, 305 | "bin": { 306 | "parse-domain-update": "bin/update.js" 307 | } 308 | }, 309 | "node_modules/punycode": { 310 | "version": "2.1.1", 311 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 312 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 313 | "engines": { 314 | "node": ">=6" 315 | } 316 | }, 317 | "node_modules/querystring": { 318 | "version": "0.2.0", 319 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 320 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", 321 | "engines": { 322 | "node": ">=0.4.x" 323 | } 324 | }, 325 | "node_modules/ramda": { 326 | "version": "0.26.1", 327 | "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", 328 | "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" 329 | }, 330 | "node_modules/sax": { 331 | "version": "1.2.1", 332 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 333 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 334 | }, 335 | "node_modules/shallow-clone": { 336 | "version": "0.1.2", 337 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", 338 | "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", 339 | "dependencies": { 340 | "is-extendable": "^0.1.1", 341 | "kind-of": "^2.0.1", 342 | "lazy-cache": "^0.2.3", 343 | "mixin-object": "^2.0.1" 344 | }, 345 | "engines": { 346 | "node": ">=0.10.0" 347 | } 348 | }, 349 | "node_modules/shallow-clone/node_modules/kind-of": { 350 | "version": "2.0.1", 351 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", 352 | "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", 353 | "dependencies": { 354 | "is-buffer": "^1.0.2" 355 | }, 356 | "engines": { 357 | "node": ">=0.10.0" 358 | } 359 | }, 360 | "node_modules/shallow-clone/node_modules/lazy-cache": { 361 | "version": "0.2.7", 362 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", 363 | "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", 364 | "engines": { 365 | "node": ">=0.10.0" 366 | } 367 | }, 368 | "node_modules/universalify": { 369 | "version": "0.1.2", 370 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 371 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", 372 | "engines": { 373 | "node": ">= 4.0.0" 374 | } 375 | }, 376 | "node_modules/url": { 377 | "version": "0.10.3", 378 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 379 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 380 | "dependencies": { 381 | "punycode": "1.3.2", 382 | "querystring": "0.2.0" 383 | } 384 | }, 385 | "node_modules/url/node_modules/punycode": { 386 | "version": "1.3.2", 387 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 388 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 389 | }, 390 | "node_modules/uuid": { 391 | "version": "3.3.2", 392 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 393 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", 394 | "bin": { 395 | "uuid": "bin/uuid" 396 | } 397 | }, 398 | "node_modules/xml2js": { 399 | "version": "0.4.19", 400 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 401 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 402 | "dependencies": { 403 | "sax": ">=0.6.0", 404 | "xmlbuilder": "~9.0.1" 405 | } 406 | }, 407 | "node_modules/xmlbuilder": { 408 | "version": "9.0.7", 409 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 410 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=", 411 | "engines": { 412 | "node": ">=4.0" 413 | } 414 | } 415 | }, 416 | "dependencies": { 417 | "@serverless/aws-sdk-extra": { 418 | "version": "1.2.7", 419 | "resolved": "https://registry.npmjs.org/@serverless/aws-sdk-extra/-/aws-sdk-extra-1.2.7.tgz", 420 | "integrity": "sha512-52NT+eM1dEMRb3inAeFhcRVBNcTE8/+mZ9OIuqsqXvGbXvjS2gaxaHzSgaFoqPw9LOUpeCHYDaSDhZ+aOqNwdQ==", 421 | "requires": { 422 | "adm-zip": "^0.4.14", 423 | "aws-sdk": "^2.614.0", 424 | "merge-deep": "^3.0.2", 425 | "moment": "^2.27.0", 426 | "ramda": "^0.26.1" 427 | } 428 | }, 429 | "adm-zip": { 430 | "version": "0.4.16", 431 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", 432 | "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==" 433 | }, 434 | "arr-union": { 435 | "version": "3.1.0", 436 | "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", 437 | "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" 438 | }, 439 | "aws-sdk": { 440 | "version": "2.874.0", 441 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.874.0.tgz", 442 | "integrity": "sha512-YF2LYIfIuywFTzojGwwUwAiq+pgSM3IWRF/uDoJa28xRQSfYD1uMeZLCqqMt+L0/yxe2UJDYKTVPhB9eEqOxEQ==", 443 | "requires": { 444 | "buffer": "4.9.2", 445 | "events": "1.1.1", 446 | "ieee754": "1.1.13", 447 | "jmespath": "0.15.0", 448 | "querystring": "0.2.0", 449 | "sax": "1.2.1", 450 | "url": "0.10.3", 451 | "uuid": "3.3.2", 452 | "xml2js": "0.4.19" 453 | } 454 | }, 455 | "base64-js": { 456 | "version": "1.5.1", 457 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 458 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" 459 | }, 460 | "buffer": { 461 | "version": "4.9.2", 462 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", 463 | "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", 464 | "requires": { 465 | "base64-js": "^1.0.2", 466 | "ieee754": "^1.1.4", 467 | "isarray": "^1.0.0" 468 | } 469 | }, 470 | "clone-deep": { 471 | "version": "0.2.4", 472 | "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", 473 | "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", 474 | "requires": { 475 | "for-own": "^0.1.3", 476 | "is-plain-object": "^2.0.1", 477 | "kind-of": "^3.0.2", 478 | "lazy-cache": "^1.0.3", 479 | "shallow-clone": "^0.1.2" 480 | } 481 | }, 482 | "events": { 483 | "version": "1.1.1", 484 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 485 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 486 | }, 487 | "for-in": { 488 | "version": "1.0.2", 489 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 490 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" 491 | }, 492 | "for-own": { 493 | "version": "0.1.5", 494 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", 495 | "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", 496 | "requires": { 497 | "for-in": "^1.0.1" 498 | } 499 | }, 500 | "fs-extra": { 501 | "version": "8.1.0", 502 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", 503 | "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", 504 | "requires": { 505 | "graceful-fs": "^4.2.0", 506 | "jsonfile": "^4.0.0", 507 | "universalify": "^0.1.0" 508 | } 509 | }, 510 | "graceful-fs": { 511 | "version": "4.2.6", 512 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", 513 | "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" 514 | }, 515 | "ieee754": { 516 | "version": "1.1.13", 517 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 518 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 519 | }, 520 | "ip-regex": { 521 | "version": "4.3.0", 522 | "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", 523 | "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" 524 | }, 525 | "is-buffer": { 526 | "version": "1.1.6", 527 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 528 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 529 | }, 530 | "is-extendable": { 531 | "version": "0.1.1", 532 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 533 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" 534 | }, 535 | "is-ip": { 536 | "version": "3.1.0", 537 | "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", 538 | "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", 539 | "requires": { 540 | "ip-regex": "^4.0.0" 541 | } 542 | }, 543 | "is-plain-object": { 544 | "version": "2.0.4", 545 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 546 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 547 | "requires": { 548 | "isobject": "^3.0.1" 549 | } 550 | }, 551 | "isarray": { 552 | "version": "1.0.0", 553 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 554 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 555 | }, 556 | "isobject": { 557 | "version": "3.0.1", 558 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 559 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" 560 | }, 561 | "jmespath": { 562 | "version": "0.15.0", 563 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 564 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 565 | }, 566 | "jsonfile": { 567 | "version": "4.0.0", 568 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", 569 | "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", 570 | "requires": { 571 | "graceful-fs": "^4.1.6" 572 | } 573 | }, 574 | "kind-of": { 575 | "version": "3.2.2", 576 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 577 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 578 | "requires": { 579 | "is-buffer": "^1.1.5" 580 | } 581 | }, 582 | "lazy-cache": { 583 | "version": "1.0.4", 584 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", 585 | "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" 586 | }, 587 | "merge-deep": { 588 | "version": "3.0.3", 589 | "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", 590 | "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", 591 | "requires": { 592 | "arr-union": "^3.1.0", 593 | "clone-deep": "^0.2.4", 594 | "kind-of": "^3.0.2" 595 | } 596 | }, 597 | "mixin-object": { 598 | "version": "2.0.1", 599 | "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", 600 | "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", 601 | "requires": { 602 | "for-in": "^0.1.3", 603 | "is-extendable": "^0.1.1" 604 | }, 605 | "dependencies": { 606 | "for-in": { 607 | "version": "0.1.8", 608 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", 609 | "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=" 610 | } 611 | } 612 | }, 613 | "moment": { 614 | "version": "2.29.1", 615 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", 616 | "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" 617 | }, 618 | "node-fetch": { 619 | "version": "2.6.1", 620 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 621 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 622 | }, 623 | "parse-domain": { 624 | "version": "3.0.3", 625 | "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-3.0.3.tgz", 626 | "integrity": "sha512-KOJR8kEymjWO5xHrt57LFJ4xtncwGfd/Z9+Twm6apKU9NIw3uSnwYTAoRUwC+MflGsn5h6MeyHltz6Fa6KT7cA==", 627 | "requires": { 628 | "is-ip": "^3.1.0", 629 | "node-fetch": "^2.6.0", 630 | "punycode": "^2.1.1" 631 | } 632 | }, 633 | "punycode": { 634 | "version": "2.1.1", 635 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 636 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 637 | }, 638 | "querystring": { 639 | "version": "0.2.0", 640 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 641 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 642 | }, 643 | "ramda": { 644 | "version": "0.26.1", 645 | "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", 646 | "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" 647 | }, 648 | "sax": { 649 | "version": "1.2.1", 650 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 651 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 652 | }, 653 | "shallow-clone": { 654 | "version": "0.1.2", 655 | "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", 656 | "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", 657 | "requires": { 658 | "is-extendable": "^0.1.1", 659 | "kind-of": "^2.0.1", 660 | "lazy-cache": "^0.2.3", 661 | "mixin-object": "^2.0.1" 662 | }, 663 | "dependencies": { 664 | "kind-of": { 665 | "version": "2.0.1", 666 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", 667 | "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", 668 | "requires": { 669 | "is-buffer": "^1.0.2" 670 | } 671 | }, 672 | "lazy-cache": { 673 | "version": "0.2.7", 674 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", 675 | "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=" 676 | } 677 | } 678 | }, 679 | "universalify": { 680 | "version": "0.1.2", 681 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", 682 | "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" 683 | }, 684 | "url": { 685 | "version": "0.10.3", 686 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 687 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 688 | "requires": { 689 | "punycode": "1.3.2", 690 | "querystring": "0.2.0" 691 | }, 692 | "dependencies": { 693 | "punycode": { 694 | "version": "1.3.2", 695 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 696 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 697 | } 698 | } 699 | }, 700 | "uuid": { 701 | "version": "3.3.2", 702 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 703 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 704 | }, 705 | "xml2js": { 706 | "version": "0.4.19", 707 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 708 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 709 | "requires": { 710 | "sax": ">=0.6.0", 711 | "xmlbuilder": "~9.0.1" 712 | } 713 | }, 714 | "xmlbuilder": { 715 | "version": "9.0.7", 716 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 717 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 718 | } 719 | } 720 | } 721 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "./serverless.js", 3 | "author": "Serverless, Inc.", 4 | "license": "Apache", 5 | "dependencies": { 6 | "@serverless/aws-sdk-extra": "^1.0.0", 7 | "fs-extra": "^8.1.0", 8 | "moment": "^2.27.0", 9 | "parse-domain": "^3.0.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/serverless.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line import/no-unresolved 4 | const { Component } = require('@serverless/core'); 5 | const { 6 | generateId, 7 | getClients, 8 | packageExpress, 9 | createOrUpdateFunctionRole, 10 | createOrUpdateMetaRole, 11 | createOrUpdateLambda, 12 | createOrUpdateAlias, 13 | createOrUpdateApi, 14 | createOrUpdateDomain, 15 | removeApi, 16 | removeAllRoles, 17 | removeLambda, 18 | removeDomain, 19 | getMetrics, 20 | retryIfRateExceeded, 21 | } = require('./utils'); 22 | 23 | class Express extends Component { 24 | /** 25 | * Deploy 26 | * @param {object} inputs 27 | */ 28 | async deploy(inputs) { 29 | const outputs = {}; 30 | 31 | console.log('Deploying Express App...'); 32 | 33 | inputs.domain = inputs.domain 34 | ? inputs.domain.replace('https://', '').replace('http://', '') 35 | : null; 36 | 37 | // for backward compatability 38 | this.state.domain = this.state.domain 39 | ? this.state.domain.replace('https://', '').replace('http://', '') 40 | : null; 41 | 42 | // Throw error on domain change 43 | if (inputs.domain && this.state.domain && this.state.domain !== inputs.domain) { 44 | throw new Error( 45 | `Changing the domain from ${this.state.domain} to ${inputs.domain} will remove your infrastructure. Please remove it manually, change the domain, then re-deploy.` 46 | ); 47 | } 48 | 49 | // Validate 50 | if (inputs.timeout && inputs.timeout > 30) { 51 | throw new Error('"timeout" can not be greater than 30 seconds.'); 52 | } 53 | 54 | // Set app name & region or use previously set name 55 | this.state.name = this.state.name || `${this.name}-${generateId()}`; 56 | this.state.region = inputs.region || 'us-east-1'; 57 | 58 | const clients = getClients(this.credentials.aws, inputs.region); 59 | 60 | await packageExpress(this, inputs, outputs); 61 | 62 | await Promise.all([ 63 | retryIfRateExceeded(() => createOrUpdateFunctionRole(this, inputs, clients)), 64 | retryIfRateExceeded(() => createOrUpdateMetaRole(this, inputs, clients, this.accountId)), 65 | ]); 66 | 67 | await retryIfRateExceeded(() => createOrUpdateLambda(this, inputs, clients)); 68 | await retryIfRateExceeded(() => createOrUpdateAlias(this, inputs, clients)); 69 | await retryIfRateExceeded(() => createOrUpdateApi(this, inputs, clients)); 70 | 71 | if (inputs.domain) { 72 | await retryIfRateExceeded(() => createOrUpdateDomain(this, inputs, clients)); 73 | } else if (this.state.domain) { 74 | delete this.state.domain; 75 | } 76 | 77 | // Set outputs 78 | 79 | outputs.apiGatewayUrl = this.state.apiGatewayUrl; 80 | 81 | if (inputs.domain) { 82 | // Ensure http info isn't replicated 83 | if (!inputs.domain.includes('http://') && !inputs.domain.includes('https://')) { 84 | outputs.url = `https://${inputs.domain}`; 85 | } else { 86 | outputs.url = inputs.domain; 87 | } 88 | // if domain is not in aws account, show the regional url 89 | // as it would be required by the external registrars 90 | if (this.state.apigatewayDomainName && !this.state.domainHostedZoneId) { 91 | outputs.regionalUrl = `https://${this.state.apigatewayDomainName}`; 92 | } 93 | } else { 94 | outputs.url = this.state.apiGatewayUrl; 95 | } 96 | 97 | return outputs; 98 | } 99 | 100 | /** 101 | * Remove 102 | */ 103 | async remove() { 104 | const clients = getClients(this.credentials.aws, this.state.region); 105 | 106 | await removeAllRoles(this, clients); 107 | await removeLambda(this, clients); 108 | await removeDomain(this, clients); 109 | await removeApi(this, clients); 110 | 111 | this.state = {}; 112 | return {}; 113 | } 114 | 115 | /** 116 | * Metrics 117 | */ 118 | async metrics(inputs = {}) { 119 | console.log('Fetching metrics...'); 120 | 121 | const result = await getMetrics( 122 | this.state.region, 123 | this.state.metaRoleArn, 124 | this.state.apiId, 125 | this.state.lambdaName, 126 | inputs.rangeStart, 127 | inputs.rangeEnd 128 | ); 129 | 130 | return result; 131 | } 132 | } 133 | 134 | module.exports = Express; 135 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const AWS = require('@serverless/aws-sdk-extra'); 5 | const https = require('https'); 6 | const { parseDomain } = require('parse-domain'); 7 | 8 | const agent = new https.Agent({ 9 | keepAlive: true, 10 | }); 11 | 12 | const { readFile, copySync } = require('fs-extra'); 13 | 14 | /* 15 | * Pauses execution for the provided miliseconds 16 | * 17 | * @param ${number} wait - number of miliseconds to wait 18 | */ 19 | const sleep = async (wait) => new Promise((resolve) => setTimeout(() => resolve(), wait)); 20 | 21 | /* 22 | * Generates a random id 23 | */ 24 | const generateId = () => Math.random().toString(36).substring(6); 25 | 26 | /** 27 | * Generate a default description for resources 28 | * @param {*} instance 29 | */ 30 | const getDefaultDescription = (instance) => { 31 | return `A resource of the Serverless Express Component for ${instance.org} - ${instance.stage} - ${instance.app} - ${instance.name}`; 32 | }; 33 | 34 | /** 35 | * The ARN of the Lambda IAM Policy used for the default IAM Role 36 | */ 37 | const getDefaultLambdaRolePolicyArn = () => { 38 | return 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'; 39 | }; 40 | 41 | /* 42 | * Initializes an AWS SDK and returns the relavent service clients 43 | * 44 | * @param ${object} credentials - aws credentials object 45 | * @param ${string} region - aws region 46 | */ 47 | const getClients = (credentials, region = 'us-east-1') => { 48 | AWS.config.update({ 49 | httpOptions: { 50 | agent, 51 | }, 52 | }); 53 | 54 | const sts = new AWS.STS({ credentials, region }); 55 | const iam = new AWS.IAM({ credentials, region }); 56 | const lambda = new AWS.Lambda({ credentials, region }); 57 | const apig = new AWS.ApiGatewayV2({ credentials, region }); 58 | const route53 = new AWS.Route53({ credentials, region }); 59 | const acm = new AWS.ACM({ 60 | credentials, 61 | region, 62 | }); 63 | const extras = new AWS.Extras({ credentials, region }); 64 | 65 | return { 66 | sts, 67 | iam, 68 | lambda, 69 | apig, 70 | route53, 71 | acm, 72 | extras, 73 | }; 74 | }; 75 | 76 | /* 77 | * Extracts the naked second level domain (ie. serverless.com) from 78 | * the provided domain or subdomain (ie. api.serverless.com) 79 | * 80 | * @param ${string} domain - the domain input that the user provided 81 | */ 82 | const getNakedDomain = (domain) => { 83 | const parsedDomain = parseDomain(domain); 84 | 85 | if (!parsedDomain.topLevelDomains) { 86 | throw new Error(`"${domain}" is not a valid domain.`); 87 | } 88 | 89 | const nakedDomain = `${parsedDomain.domain}.${parsedDomain.topLevelDomains.join('.')}`; 90 | return nakedDomain; 91 | }; 92 | 93 | /* 94 | * Packages express app and injects shims and sdk 95 | * 96 | * @param ${instance} instance - the component instance 97 | * @param ${object} config - the component config 98 | */ 99 | const packageExpress = async (instance, inputs) => { 100 | console.log('Packaging Express.js application...'); 101 | 102 | // unzip source zip file 103 | console.log(`Unzipping ${inputs.src || 'files'}...`); 104 | const sourceDirectory = await instance.unzip(inputs.src); 105 | console.log(`Files unzipped into ${sourceDirectory}...`); 106 | 107 | // add shim to the source directory 108 | console.log('Installing Express + AWS Lambda handler...'); 109 | copySync(path.join(__dirname, '_express'), path.join(sourceDirectory, '_express')); 110 | 111 | /** 112 | * DEPRECATED: This runs untrusted code and should not be used until we can find a way to do this more securely. 113 | */ 114 | // Attempt to infer data from the application 115 | // if (inputs.openApi) { 116 | // console.log('Attempting to collect API routes and convert to OpenAPI format, since openAPI is set to 'true'') 117 | // await infer(instance, inputs, outputs, sourceDirectory); 118 | // } 119 | 120 | // add sdk to the source directory, add original handler 121 | console.log('Installing Serverless Framework SDK...'); 122 | instance.state.handler = await instance.addSDK(sourceDirectory, '_express/handler.handler'); 123 | 124 | if (!inputs.src) { 125 | // add default express app 126 | console.log('Installing Default Express App...'); 127 | copySync(path.join(__dirname, '_src'), path.join(sourceDirectory)); 128 | } 129 | // zip the source directory with the shim and the sdk 130 | 131 | console.log('Zipping files...'); 132 | const zipPath = await instance.zip(sourceDirectory); 133 | console.log(`Files zipped into ${zipPath}...`); 134 | 135 | // save the zip path to state for lambda to use it 136 | instance.state.zipPath = zipPath; 137 | 138 | return zipPath; 139 | }; 140 | 141 | /* 142 | * DEPRECATED: This runs untrusted code and should not be used until we can find a way to do this more securely. 143 | * 144 | * Infer data from the Application by attempting to intiatlize it during deployment and extracting data. 145 | * 146 | * @param ${object} instance - the component instance 147 | * @param ${object} inputs - the component inputs 148 | */ 149 | // const infer = async (instance, inputs, outputs, sourceDirectory) => { 150 | // // Initialize application 151 | // let app; 152 | // try { 153 | // // Load app 154 | // app = require(path.join(sourceDirectory, './app.js')); 155 | // } catch (error) { 156 | // const msg = error.message; 157 | // error.message = `OpenAPI auto-generation failed due to the Express Component not being able to start your app. To fix this, you can turn this feature off by specifying 'inputs.openApi: false' or fix the following issue: ${msg}`; 158 | // throw error; 159 | // } 160 | 161 | // try { 162 | // await generateOpenAPI(instance, inputs, outputs, app); 163 | // } catch (error) { 164 | // const msg = error.message; 165 | // error.message = `OpenAPI auto-generation failed due to the Express Component not being able to start your app. To fix this, you can turn this feature off by specifying 'inputs.openApi: false' or fix the following issue: ${msg}`; 166 | // throw error; 167 | // } 168 | // }; 169 | 170 | /* 171 | * DEPRECATED: This runs untrusted code and should not be used until we can find a way to do this more securely. 172 | * 173 | * Generate an OpenAPI specification from the Application 174 | * 175 | * @param ${object} instance - the component instance 176 | * @param ${object} inputs - the component inputs 177 | */ 178 | // const generateOpenAPI = async (instance, inputs, outputs, app) => { 179 | // // Open API Version 3.0.3, found here: https://swagger.io/specification/ 180 | // // TODO: This is not complete, but the pieces that do exist are accurate. 181 | // const openApi = { 182 | // openapi: '3.0.3', 183 | // info: { 184 | // // title: null, 185 | // // description: null, 186 | // version: '0.0.1', 187 | // }, 188 | // paths: {}, 189 | // }; 190 | 191 | // // Parts of the OpenAPI spec that we may use these at a later date. 192 | // // For now, they are unincorporated. 193 | // // const oaServersObject = { 194 | // // url: null, 195 | // // description: null, 196 | // // variables: {}, 197 | // // }; 198 | // // const oaComponentsObject = { 199 | // // schemas: {}, 200 | // // responses: {}, 201 | // // parameters: {}, 202 | // // examples: {}, 203 | // // requestBodies: {}, 204 | // // headers: {}, 205 | // // securitySchemes: {}, 206 | // // links: {}, 207 | // // callbacks: {}, 208 | // // }; 209 | // // const oaPathItem = { 210 | // // description: null, 211 | // // summary: null, 212 | // // operationId: null, 213 | // // responses: {}, 214 | // // }; 215 | 216 | // if (app && app._router && app._router.stack && app._router.stack.length) { 217 | // app._router.stack.forEach((route) => { 218 | // // This array holds all middleware layers, which include routes and more 219 | // // First check if this 'layer' is an express route type, otherwise skip 220 | // if (!route.route) return; 221 | 222 | // // Define key data 223 | // const ePath = route.route.path; 224 | 225 | // if (['*', '/*'].indexOf(ePath) > -1) { 226 | // return; 227 | // } 228 | 229 | // // Save path 230 | // openApi.paths[ePath] = openApi.paths[ePath] || {}; 231 | 232 | // for (const method of Object.keys(route.route.methods)) { 233 | // // Save method 234 | // openApi.paths[ePath][method] = {}; 235 | // } 236 | // }); 237 | // } 238 | 239 | // // Save to outputs 240 | // outputs.api = openApi; 241 | // }; 242 | 243 | /* 244 | * Fetches a lambda function by ARN 245 | */ 246 | const getLambda = async (clients, lambdaName) => { 247 | try { 248 | const res = await clients.lambda 249 | .getFunctionConfiguration({ 250 | FunctionName: lambdaName, 251 | }) 252 | .promise(); 253 | return res.FunctionArn; 254 | } catch (e) { 255 | if (e.code === 'ResourceNotFoundException') { 256 | return null; 257 | } 258 | throw e; 259 | } 260 | }; 261 | 262 | const getVpcConfig = (vpcConfig) => { 263 | if (vpcConfig == null) { 264 | return { 265 | SecurityGroupIds: [], 266 | SubnetIds: [], 267 | }; 268 | } 269 | 270 | return { 271 | SecurityGroupIds: vpcConfig.securityGroupIds, 272 | SubnetIds: vpcConfig.subnetIds, 273 | }; 274 | }; 275 | 276 | /* 277 | * Creates a lambda function on aws 278 | * 279 | * @param ${instance} instance - the component instance 280 | * @param ${object} inputs - the component inputs 281 | * @param ${object} clients - the aws clients object 282 | */ 283 | const createLambda = async (instance, inputs, clients, retries = 0) => { 284 | // Track retries 285 | retries++; 286 | 287 | const vpcConfig = getVpcConfig(inputs.vpc); 288 | 289 | const params = { 290 | FunctionName: instance.state.lambdaName, 291 | Code: {}, 292 | Description: inputs.description || getDefaultDescription(instance), 293 | Handler: instance.state.handler, 294 | MemorySize: inputs.memory || 1536, 295 | Publish: true, 296 | Role: instance.state.userRoleArn || instance.state.defaultLambdaRoleArn, // Default to automatically created role 297 | Runtime: 'nodejs12.x', 298 | Timeout: inputs.timeout || 29, // Meet the APIG timeout limit, don't exceed it 299 | Environment: { 300 | Variables: inputs.env || {}, 301 | }, 302 | VpcConfig: vpcConfig, 303 | }; 304 | 305 | if (inputs.layers) { 306 | params.Layers = inputs.layers; 307 | } 308 | 309 | console.log('Creating AWS Lambda...'); 310 | params.Code.ZipFile = await readFile(instance.state.zipPath); 311 | 312 | try { 313 | const res = await clients.lambda.createFunction(params).promise(); 314 | console.log(`Lambda created with ARN ${res.FunctionArn}`); 315 | instance.state.lambdaArn = res.FunctionArn; 316 | instance.state.lambdaVersion = res.Version; 317 | } catch (e) { 318 | console.log(`Unable to create AWS Lambda due to: ${e.message}`); 319 | 320 | // Handle known errors 321 | 322 | if (e.message.includes('The role defined for the function cannot be assumed by Lambda')) { 323 | // This error can happen upon first creation. So sleeping is an acceptable solution. This code will retry multiple times. 324 | if (retries > 5) { 325 | console.log( 326 | 'Attempted to retry Lambda creation 5 times, but the invalid role error persists. Aborting...' 327 | ); 328 | 329 | // Throw different errors, depending on whether the user is using a custom role 330 | if (instance.state.userRoleArn) { 331 | throw new Error( 332 | 'Unable to create the AWS Lambda function which your Express.js app runs on. The reason is "the role defined for the function cannot be assumed by Lambda". This might be due to a missing or invalid "Trust Relationship" within the policy of the custom IAM Role you you are attempting to use. Try modifying that. If that doesn\'t work, this is an issue with AWS Lambda\'s APIs. We suggest trying to remove this instance by running "serverless remove" then redeploying to get around this.' 333 | ); 334 | } else { 335 | throw new Error( 336 | 'Unable to create the AWS Lambda function which your Express.js app runs on. The reason is "the role defined for the function cannot be assumed by Lambda". This is an issue with AWS Lambda\'s APIs. We suggest trying to remove this instance by running "serverless remove" then redeploying to get around this. This seems to be the only way users have gotten past this.' 337 | ); 338 | } 339 | } 340 | // Try again. We often to wait around 3 seconds after the role is created before it can be assumed 341 | await sleep(3000); 342 | return createLambda(instance, inputs, clients, retries); 343 | } 344 | 345 | if ( 346 | e.message.includes( 347 | 'Lambda was unable to configure access to your environment variables because the KMS key is invalid' 348 | ) 349 | ) { 350 | // This error can happen upon first creation. So sleeping is an acceptable solution. This code will retry multiple times. 351 | if (retries > 5) { 352 | console.log( 353 | 'Attempted to retry Lambda creation 5 times, but the KMS error persists Aborting...' 354 | ); 355 | throw new Error( 356 | 'Unable to create the AWS Lambda function which your Express.js app runs on. The reason is "Lambda was unable to configure access to your environment variables because the KMS key is invalid". This is a known issue with AWS Lambda\'s APIs, and there is nothing the Serverless Framework can do to help with it at this time. We suggest trying to remove this instance by running "serverless remove" then redeploying to attempt to get around this.' 357 | ); 358 | } 359 | // Retry. 360 | await sleep(3000); 361 | return createLambda(instance, inputs, clients, retries); 362 | } 363 | 364 | throw e; 365 | } 366 | return null; 367 | }; 368 | 369 | /* 370 | * Updates lambda code on aws according to the provided source 371 | * 372 | * @param ${instance} instance - the component instance 373 | * @param ${object} inputs - the component inputs 374 | * @param ${object} clients - the aws clients object 375 | */ 376 | const updateLambdaCode = async (instance, inputs, clients) => { 377 | const functionCodeParams = { 378 | FunctionName: instance.state.lambdaName, 379 | Publish: true, 380 | }; 381 | functionCodeParams.ZipFile = await readFile(instance.state.zipPath); 382 | try { 383 | const res = await clients.lambda.updateFunctionCode(functionCodeParams).promise(); 384 | instance.state.lambdaArn = res.FunctionArn; 385 | instance.state.lambdaVersion = res.Version; 386 | } catch (e) { 387 | if (e.code === 'ResourceConflictException') { 388 | await sleep(2000); 389 | return await updateLambdaCode(instance, inputs, clients); 390 | } 391 | throw e; 392 | } 393 | }; 394 | 395 | /* 396 | * Updates lambda code on aws according to the provided config 397 | * 398 | * @param ${instance} instance - the component instance 399 | * @param ${object} inputs - the component inputs 400 | * @param ${object} clients - the aws clients object 401 | */ 402 | const updateLambdaConfig = async (instance, inputs, clients) => { 403 | const vpcConfig = getVpcConfig(inputs.vpc); 404 | 405 | const functionConfigParams = { 406 | FunctionName: instance.state.lambdaName, 407 | Description: inputs.description || getDefaultDescription(instance), 408 | MemorySize: inputs.memory || 1536, 409 | Role: instance.state.userRoleArn || instance.state.defaultLambdaRoleArn, // Default to auto-create role 410 | Timeout: inputs.timeout || 29, // Meet APIG timeout limit, don't exceed it 411 | Handler: instance.state.handler, 412 | Runtime: 'nodejs12.x', 413 | Environment: { 414 | Variables: inputs.env || {}, 415 | }, 416 | VpcConfig: vpcConfig, 417 | }; 418 | 419 | if (inputs.layers) { 420 | functionConfigParams.Layers = inputs.layers; 421 | } 422 | 423 | try { 424 | const res = await clients.lambda.updateFunctionConfiguration(functionConfigParams).promise(); 425 | return res.FunctionArn; 426 | } catch (e) { 427 | if ( 428 | e.message.includes('The role defined for the function cannot be assumed by Lambda') || 429 | e.message.includes( 430 | 'Lambda was unable to configure access to your environment variables because the KMS key is invalid' 431 | ) 432 | ) { 433 | // we need to wait around 2 seconds after the role is created before it can be assumed 434 | await sleep(2000); 435 | return updateLambdaConfig(instance, inputs, clients); 436 | } 437 | throw e; 438 | } 439 | }; 440 | 441 | /** 442 | * Get the Hosted Zone ID of the custom domain in Route 53 443 | */ 444 | const getDomainHostedZoneId = async (instance, inputs, clients) => { 445 | const nakedDomain = getNakedDomain(inputs.domain); 446 | 447 | instance.state.nakedDomain = nakedDomain; 448 | 449 | console.log(`Getting Route53 Hosted Zone ID for domain: ${nakedDomain}`); 450 | 451 | const hostedZones = await clients.route53.listHostedZonesByName().promise(); 452 | 453 | let hostedZone = hostedZones.HostedZones.find( 454 | // Name has a period at the end, so we're using includes rather than equals 455 | (zone) => zone.Name.includes(nakedDomain) 456 | ); 457 | 458 | if (!hostedZone) { 459 | console.log( 460 | `Domain ${nakedDomain} was not found in your AWS account. Skipping DNS operations.` 461 | ); 462 | return null; 463 | } 464 | 465 | hostedZone = hostedZone.Id.replace('/hostedzone/', ''); // hosted zone id is always prefixed with this :( 466 | 467 | console.log(`Domain hosted zone id for ${nakedDomain} is ${hostedZone}`); 468 | 469 | return hostedZone; 470 | }; 471 | 472 | const getCertificateArnByDomain = async (clients, instance) => { 473 | const listRes = await clients.acm.listCertificates().promise(); 474 | const certificate = listRes.CertificateSummaryList.find( 475 | (cert) => cert.DomainName === instance.state.nakedDomain 476 | ); 477 | return certificate && certificate.CertificateArn ? certificate.CertificateArn : null; 478 | }; 479 | 480 | const getCertificateValidationRecord = (certificate, domain) => { 481 | if (!certificate.DomainValidationOptions) { 482 | return null; 483 | } 484 | const domainValidationOption = certificate.DomainValidationOptions.find( 485 | (option) => option.DomainName === domain 486 | ); 487 | 488 | return domainValidationOption.ResourceRecord; 489 | }; 490 | 491 | const describeCertificateByArn = async (clients, certificateArn, domain) => { 492 | const res = await clients.acm.describeCertificate({ CertificateArn: certificateArn }).promise(); 493 | const certificate = res && res.Certificate ? res.Certificate : null; 494 | 495 | if ( 496 | certificate.Status === 'PENDING_VALIDATION' && 497 | !getCertificateValidationRecord(certificate, domain) 498 | ) { 499 | await sleep(1000); 500 | return describeCertificateByArn(clients, certificateArn, domain); 501 | } 502 | 503 | return certificate; 504 | }; 505 | 506 | const findOrCreateCertificate = async (instance, clients) => { 507 | const wildcardSubDomain = `*.${instance.state.nakedDomain}`; 508 | 509 | const params = { 510 | DomainName: instance.state.nakedDomain, 511 | SubjectAlternativeNames: [instance.state.nakedDomain, wildcardSubDomain], 512 | ValidationMethod: 'DNS', 513 | }; 514 | 515 | console.log(`Checking if a certificate for the ${instance.state.nakedDomain} domain exists`); 516 | let certificateArn = await getCertificateArnByDomain(clients, instance); 517 | 518 | if (!certificateArn) { 519 | console.log( 520 | `Certificate for the ${instance.state.nakedDomain} domain does not exist. Creating...` 521 | ); 522 | certificateArn = (await clients.acm.requestCertificate(params).promise()).CertificateArn; 523 | } 524 | 525 | const certificate = await describeCertificateByArn( 526 | clients, 527 | certificateArn, 528 | instance.state.nakedDomain 529 | ); 530 | 531 | console.log( 532 | `Certificate for ${instance.state.nakedDomain} is in a '${certificate.Status}' status` 533 | ); 534 | 535 | if (certificate.Status === 'PENDING_VALIDATION') { 536 | const certificateValidationRecord = getCertificateValidationRecord( 537 | certificate, 538 | instance.state.nakedDomain 539 | ); 540 | // only validate if domain/hosted zone is found in this account 541 | if (instance.state.domainHostedZoneId) { 542 | console.log(`Validating the certificate for the ${instance.state.nakedDomain} domain.`); 543 | 544 | const recordParams = { 545 | HostedZoneId: instance.state.domainHostedZoneId, 546 | ChangeBatch: { 547 | Changes: [ 548 | { 549 | Action: 'UPSERT', 550 | ResourceRecordSet: { 551 | Name: certificateValidationRecord.Name, 552 | Type: certificateValidationRecord.Type, 553 | TTL: 300, 554 | ResourceRecords: [ 555 | { 556 | Value: certificateValidationRecord.Value, 557 | }, 558 | ], 559 | }, 560 | }, 561 | ], 562 | }, 563 | }; 564 | await clients.route53.changeResourceRecordSets(recordParams).promise(); 565 | console.log( 566 | 'Your certificate was created and is being validated. It may take a few mins to validate.' 567 | ); 568 | console.log( 569 | 'Please deploy again after few mins to use your newly validated certificate and activate your domain.' 570 | ); 571 | } else { 572 | // if domain is not in account, let the user validate manually 573 | console.log( 574 | `Certificate for the ${instance.state.nakedDomain} domain was created, but not validated. Please validate it manually.` 575 | ); 576 | console.log(`Certificate Validation Record Name: ${certificateValidationRecord.Name} `); 577 | console.log(`Certificate Validation Record Type: ${certificateValidationRecord.Type} `); 578 | console.log(`Certificate Validation Record Value: ${certificateValidationRecord.Value} `); 579 | } 580 | } else if (certificate.Status === 'ISSUED') { 581 | // if certificate status is ISSUED, mark it a as valid for CloudFront to use 582 | instance.state.certificateValid = true; 583 | } else if (certificate.Status === 'SUCCESS') { 584 | // nothing to do here. We just need to wait a min until the status changes to ISSUED 585 | } else if (certificate.Status === 'VALIDATION_TIMED_OUT') { 586 | // if 72 hours passed and the user did not validate the certificate 587 | // it will timeout and the user will need to recreate and validate the certificate manulaly 588 | console.log( 589 | 'Certificate validation timed out after 72 hours. Please recreate and validate the certifcate manually.' 590 | ); 591 | console.log('Your domain will not work until your certificate is created and validated.'); 592 | } else { 593 | // something else happened?! 594 | throw new Error( 595 | `Failed to validate ACM certificate. Unsupported ACM certificate status ${certificate.Status}` 596 | ); 597 | } 598 | 599 | return certificateArn; 600 | }; 601 | /** 602 | * Create a custom domain record in API Gateway 603 | * @param {*} instance 604 | * @param {*} inputs 605 | * @param {*} clients 606 | */ 607 | const createDomainInApig = async (instance, inputs, clients) => { 608 | console.log(`Domain ${inputs.domain} not found in API Gateway. Creating...`); 609 | 610 | let res; 611 | try { 612 | const params = { 613 | DomainName: inputs.domain, 614 | DomainNameConfigurations: [ 615 | { 616 | EndpointType: 'REGIONAL', // ApiGateway V2 does not support EDGE endpoints yet (Writte in April 9th 2020) 617 | SecurityPolicy: 'TLS_1_2', 618 | CertificateArn: instance.state.certificateArn, 619 | }, 620 | ], 621 | }; 622 | res = await clients.apig.createDomainName(params).promise(); 623 | } catch (e) { 624 | if (e.code === 'TooManyRequestsException') { 625 | console.log('API Gateway is throttling our API Requests *sigh*. Sleeping for 2 seconds...'); 626 | await sleep(2000); 627 | return createDomainInApig(instance, inputs, clients); 628 | } 629 | throw e; 630 | } 631 | return res; 632 | }; 633 | 634 | /** 635 | * Ensure a Route 53 Hosted Zone AliasTarget Record Set for the HTTP Custom Domain 636 | * @param {*} instance 637 | * @param {*} inputs 638 | * @param {*} clients 639 | */ 640 | const ensureRecordSetForApiGCustomDomain = async (instance, inputs, clients) => { 641 | console.log( 642 | `Ensuring the existence of a Route 53 Hosted Zone AliasTarget Record Set for HTTP API with a Hosted Zone ID: ${instance.state.apigatewayHostedZoneId} and DNS Name: ${instance.state.apigatewayDomainName}.` 643 | ); 644 | 645 | const changeParams = { 646 | HostedZoneId: instance.state.domainHostedZoneId, 647 | ChangeBatch: { 648 | Changes: [ 649 | { 650 | Action: 'UPSERT', 651 | ResourceRecordSet: { 652 | Name: inputs.domain, 653 | Type: 'A', 654 | AliasTarget: { 655 | HostedZoneId: instance.state.apigatewayHostedZoneId, 656 | DNSName: instance.state.apigatewayDomainName, 657 | EvaluateTargetHealth: false, 658 | }, 659 | }, 660 | }, 661 | ], 662 | }, 663 | }; 664 | 665 | try { 666 | await clients.route53.changeResourceRecordSets(changeParams).promise(); 667 | } catch (error) { 668 | console.error(error); 669 | } 670 | }; 671 | 672 | /** 673 | * Find or create a custom domain on API Gateway 674 | */ 675 | const findOrCreateCustomDomain = async (instance, inputs, clients) => { 676 | const result = {}; 677 | // Find or create custom domain on API Gateway 678 | try { 679 | console.log(`Verifying Custom Domain exists on API Gateway: ${inputs.domain}...`); 680 | const params = { DomainName: inputs.domain }; 681 | const domain = await clients.apig.getDomainName(params).promise(); 682 | result.apigatewayHostedZoneId = domain.DomainNameConfigurations[0].HostedZoneId; 683 | result.apigatewayDomainName = domain.DomainNameConfigurations[0].ApiGatewayDomainName; 684 | return result; 685 | } catch (error) { 686 | if (error.code === 'NotFoundException') { 687 | console.log(`Custom Domain not found in API Gateway: ${inputs.domain}. Creating it...`); 688 | const res = await createDomainInApig(instance, inputs, clients); 689 | result.apigatewayHostedZoneId = res.DomainNameConfigurations[0].HostedZoneId; 690 | result.apigatewayDomainName = res.DomainNameConfigurations[0].ApiGatewayDomainName; 691 | console.log( 692 | `Domain ${instance.state.domain} successfully created. If this is your first deploy, please note that you will have to wait typical DNS propagation times for your domain name to be accessible. This is often only 10-20 minutes, but on occassion can take ~4 hours.` 693 | ); 694 | return result; 695 | } 696 | throw error; 697 | } 698 | }; 699 | 700 | /** 701 | * Ensure API Gateway API is mapped to the custom API Gateway Domain 702 | */ 703 | const findOrCreateApiMapping = async (instance, inputs, clients) => { 704 | console.log( 705 | `Verifying API Gateway custom domain ${inputs.domain} is mapped to API ID: ${instance.state.apiId}` 706 | ); 707 | 708 | let apiMapping; 709 | const paramsGet = { 710 | DomainName: instance.state.domain, 711 | }; 712 | const apiMappings = await clients.apig.getApiMappings(paramsGet).promise(); 713 | apiMappings.Items.forEach((am) => { 714 | if (am.ApiId === instance.state.apiId) { 715 | apiMapping = am; 716 | } 717 | }); 718 | 719 | if (apiMapping) { 720 | console.log(`API Mapping found with API Mapping ID: ${apiMapping.ApiMappingId}`); 721 | return apiMapping.ApiMappingId; 722 | } 723 | 724 | try { 725 | console.log('API Mapping to API Custom Domain not found. Creating one...'); 726 | const createApiMappingParams = { 727 | DomainName: inputs.domain, 728 | ApiId: instance.state.apiId, 729 | Stage: '$default', 730 | }; 731 | const resMapping = await clients.apig.createApiMapping(createApiMappingParams).promise(); 732 | console.log(`API Mapping successfully created with ID: ${resMapping.ApiMappingId}`); 733 | return resMapping.ApiMappingId; 734 | } catch (e) { 735 | if (e.code === 'TooManyRequestsException') { 736 | console.log('AWS API Gateway is throttling our API requests. Sleeping for 2 seconds...'); 737 | await sleep(2000); 738 | return findOrCreateApiMapping(instance, inputs, clients); 739 | } 740 | if (e.code === 'ConflictException') { 741 | throw new Error(`The domain ${inputs.domain} is already in use by another API`); 742 | } 743 | throw e; 744 | } 745 | }; 746 | 747 | /** 748 | * 749 | * 750 | * Create Or Update Logic 751 | * 752 | * 753 | */ 754 | 755 | /* 756 | * Ensure the provided or default IAM Role exists 757 | * 758 | * @param ${instance} instance - the component instance 759 | * @param ${object} inputs - the component inputs 760 | * @param ${object} clients - the aws clients object 761 | */ 762 | const createOrUpdateFunctionRole = async (instance, inputs, clients) => { 763 | // Verify existing role, either provided or the previously created default role... 764 | if (inputs.roleName) { 765 | console.log( 766 | `Verifying the provided IAM Role with the name: ${inputs.roleName} in the inputs exists...` 767 | ); 768 | 769 | const userRole = await clients.extras.getRole({ roleName: inputs.roleName }); 770 | const userRoleArn = userRole && userRole.Role && userRole.Role.Arn ? userRole.Role.Arn : null; // Don't save user provided role to state, always reference it as an input, in case it changes 771 | 772 | // If user role exists, save it to state so it can be used for the create/update lambda logic later 773 | if (userRoleArn) { 774 | console.log(`The provided IAM Role with the name: ${inputs.roleName} in the inputs exists.`); 775 | instance.state.userRoleArn = userRoleArn; 776 | 777 | // Save AWS Account ID by fetching the role ID 778 | // TODO: This may not work with cross-account roles. 779 | instance.state.awsAccountId = instance.state.userRoleArn.split(':')[4]; 780 | } else { 781 | throw new Error( 782 | `The provided IAM Role with the name: ${inputs.roleName} could not be found.` 783 | ); 784 | } 785 | } else { 786 | // Create a default role with basic Lambda permissions 787 | 788 | const defaultLambdaRoleName = `${instance.state.name}-lambda-role`; 789 | console.log( 790 | `IAM Role not found. Creating or updating a default role with the name: ${defaultLambdaRoleName}` 791 | ); 792 | 793 | const result = await clients.extras.deployRole({ 794 | roleName: defaultLambdaRoleName, 795 | service: ['lambda.amazonaws.com'], 796 | policy: getDefaultLambdaRolePolicyArn(), 797 | }); 798 | 799 | instance.state.defaultLambdaRoleName = defaultLambdaRoleName; 800 | instance.state.defaultLambdaRoleArn = result.roleArn; 801 | instance.state.awsAccountId = instance.state.defaultLambdaRoleArn.split(':')[4]; 802 | 803 | console.log( 804 | `Default Lambda IAM Role created or updated with ARN ${instance.state.defaultLambdaRoleArn}` 805 | ); 806 | } 807 | }; 808 | 809 | /* 810 | * Ensure the Meta IAM Role exists 811 | */ 812 | const createOrUpdateMetaRole = async (instance, inputs, clients, serverlessAccountId) => { 813 | // Create or update Meta Role for monitoring and more, if option is enabled. It's enabled by default. 814 | if (inputs.monitoring || typeof inputs.monitoring === 'undefined') { 815 | console.log('Creating or updating the meta IAM Role...'); 816 | 817 | const roleName = `${instance.state.name}-meta-role`; 818 | 819 | const assumeRolePolicyDocument = { 820 | Version: '2012-10-17', 821 | Statement: { 822 | Effect: 'Allow', 823 | Principal: { 824 | AWS: `arn:aws:iam::${serverlessAccountId}:root`, // Serverless's Components account 825 | }, 826 | Action: 'sts:AssumeRole', 827 | }, 828 | }; 829 | 830 | // Create a policy that only can access APIGateway and Lambda metrics, logs from CloudWatch... 831 | const policy = { 832 | Version: '2012-10-17', 833 | Statement: [ 834 | { 835 | Effect: 'Allow', 836 | Resource: '*', 837 | Action: [ 838 | 'cloudwatch:Describe*', 839 | 'cloudwatch:Get*', 840 | 'cloudwatch:List*', 841 | 'logs:Get*', 842 | 'logs:List*', 843 | 'logs:Describe*', 844 | 'logs:TestMetricFilter', 845 | 'logs:FilterLogEvents', 846 | ], 847 | // TODO: Finish this. Haven't been able to get this to work. Perhaps there is a missing service (Cloudfront?) 848 | // Condition: { 849 | // StringEquals: { 850 | // 'cloudwatch:namespace': [ 851 | // 'AWS/ApiGateway', 852 | // 'AWS/Lambda' 853 | // ] 854 | // } 855 | // } 856 | }, 857 | ], 858 | }; 859 | 860 | const roleDescription = `The Meta Role for the Serverless Framework App: ${instance.name} Stage: ${instance.stage}`; 861 | 862 | const result = await clients.extras.deployRole({ 863 | roleName, 864 | roleDescription, 865 | policy, 866 | assumeRolePolicyDocument, 867 | }); 868 | 869 | instance.state.metaRoleName = roleName; 870 | instance.state.metaRoleArn = result.roleArn; 871 | 872 | console.log(`Meta IAM Role created or updated with ARN ${instance.state.metaRoleArn}`); 873 | } 874 | }; 875 | 876 | /* 877 | * Ensures the AWS Lambda function exists 878 | * 879 | * @param ${instance} instance - the component instance 880 | * @param ${object} inputs - the component inputs 881 | * @param ${object} clients - the aws clients object 882 | */ 883 | const createOrUpdateLambda = async (instance, inputs, clients) => { 884 | // Verify existing lambda 885 | if (instance.state.lambdaArn && instance.state.lambdaName) { 886 | console.log( 887 | `Verifying the AWS Lambda with the ARN: ${instance.state.lambdaArn} and Name: ${instance.state.lambdaName} found in state exists...` 888 | ); 889 | instance.state.lambdaArn = await getLambda(clients, instance.state.lambdaName); 890 | } 891 | 892 | if (!instance.state.lambdaArn) { 893 | instance.state.lambdaName = `${instance.state.name}-function`; // WARNING: DO NOT ADJUST THIS, OR EVERYONE WILL DEPLOY NEW FUNCTIONS, RATHER THAN UPDATE THEIR OLD ONES. ADJUST THIS ONLY WHEN WE'RE READY TO DO A BREAKING CHANGE. 894 | console.log( 895 | `No AWS Lambda function found. Creating one with the name: ${instance.state.lambdaName}` 896 | ); 897 | return createLambda(instance, inputs, clients); 898 | } 899 | 900 | console.log("AWS Lambda function found. Updating it's configuration and code..."); 901 | await updateLambdaConfig(instance, inputs, clients); 902 | await updateLambdaCode(instance, inputs, clients); 903 | console.log(`AWS Lambda version "${instance.state.lambdaVersion}" published`); 904 | console.log(`AWS Lambda function updated with ARN: ${instance.state.lambdaArn}`); 905 | return null; 906 | }; 907 | 908 | /* 909 | * Adds permission to API Gateway to invoke the latest lambda version/alias 910 | * 911 | * @param ${instance} instance - the component instance 912 | * @param ${object} inputs - the component inputs 913 | * @param ${object} clients - the aws clients object 914 | */ 915 | const addPermission = async (instance, inputs, clients) => { 916 | const lambdaArn = instance.state.aliasArn; 917 | const apigArn = `arn:aws:execute-api:${instance.state.region}:${instance.state.awsAccountId}:${instance.state.apiId}/*/*`; 918 | console.log(`Add permission to Lambda enabling API Gateway with this ARN to call it: ${apigArn}`); 919 | const paramsPermission = { 920 | Action: 'lambda:InvokeFunction', 921 | FunctionName: lambdaArn, 922 | Principal: 'apigateway.amazonaws.com', 923 | SourceArn: apigArn, 924 | StatementId: `API-${instance.state.apiId}-${inputs.alias}`, 925 | }; 926 | try { 927 | await clients.lambda.addPermission(paramsPermission).promise(); 928 | console.log('Permission successfully added to AWS Lambda for API Gateway'); 929 | } catch (e) { 930 | if (!e.message.includes('already exists')) { 931 | throw e; 932 | } 933 | } 934 | }; 935 | 936 | /* 937 | * Creates an API on aws if it doesn't already exists 938 | * 939 | * @param ${instance} instance - the component instance 940 | * @param ${object} inputs - the component inputs 941 | * @param ${object} clients - the aws clients object 942 | */ 943 | const createOrUpdateApi = async (instance, inputs, clients) => { 944 | let apiId; 945 | if (instance.state.apiId) { 946 | console.log(`Checking for existing API with ID: ${instance.state.apiId}`); 947 | const paramsGet = { ApiId: instance.state.apiId }; 948 | try { 949 | const res = await clients.apig.getApi(paramsGet).promise(); 950 | apiId = res.ApiId; 951 | } catch (error) { 952 | if (error.code !== 'NotFoundException') { 953 | throw error; 954 | } 955 | } 956 | } 957 | 958 | // use the alias if defined for traffic control, or the latest lambda arn 959 | const lambdaArn = instance.state.aliasArn; 960 | 961 | if (apiId) { 962 | console.log(`API found. Updating API with ID: ${instance.state.apiId}...`); 963 | 964 | // Ensure this is on state 965 | instance.state.apiId = apiId; 966 | instance.state.apiGatewayUrl = `https://${instance.state.apiId}.execute-api.${instance.state.region}.amazonaws.com`; 967 | 968 | const updateApiParams = { 969 | ApiId: apiId, 970 | Description: inputs.description || getDefaultDescription(instance), 971 | Target: `arn:aws:apigateway:${instance.state.region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations`, 972 | }; 973 | 974 | await clients.apig.updateApi(updateApiParams).promise(); 975 | 976 | // update permissions for the new lambda version 977 | await addPermission(instance, inputs, clients); 978 | 979 | console.log(`API with ID "${instance.state.apiId}" Updated.`); 980 | return; 981 | } 982 | 983 | instance.state.apiName = `${instance.name}-api`; // WARNING: DO NOT ADJUST THIS, OR EVERYONE WILL DEPLOY A NEW API, RATHER THAN UPDATE THEIR OLD ONE. ADJUST THIS ONLY WHEN WE'RE READY TO DO A BREAKING CHANGE. 984 | console.log(`API not found. Creating API with name: ${instance.state.apiName}...`); 985 | const createApiParams = { 986 | Name: instance.state.apiName, 987 | ProtocolType: 'HTTP', 988 | // CredentialsArn: inputs.roleName || instance.state.defaultLambdaRoleArn, 989 | Description: inputs.description || getDefaultDescription(instance), 990 | Target: `arn:aws:apigateway:${instance.state.region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations`, 991 | }; 992 | 993 | const res = await clients.apig.createApi(createApiParams).promise(); 994 | instance.state.apiId = res.ApiId; 995 | 996 | console.log(`API ${instance.state.apiName} created with ID ${instance.state.apiId}`); 997 | 998 | instance.state.apiGatewayUrl = `https://${instance.state.apiId}.execute-api.${instance.state.region}.amazonaws.com`; 999 | 1000 | // Give newly created API permission to call Lambda 1001 | await addPermission(instance, inputs, clients); 1002 | }; 1003 | 1004 | /* 1005 | * Creates an API on aws if it doesn't already exists 1006 | * 1007 | * @param ${instance} instance - the component instance 1008 | * @param ${object} inputs - the component inputs 1009 | * @param ${object} clients - the aws clients object 1010 | */ 1011 | const createOrUpdateDomain = async (instance, inputs, clients) => { 1012 | instance.state.domain = inputs.domain; 1013 | 1014 | instance.state.domainHostedZoneId = await getDomainHostedZoneId(instance, inputs, clients); 1015 | 1016 | instance.state.certificateArn = await findOrCreateCertificate(instance, clients); 1017 | 1018 | // if certificate is not valid, then we cannot create the domain name 1019 | // the user has to manually validate the certificate 1020 | if (!instance.state.certificateValid) { 1021 | delete instance.state.domain; 1022 | return; 1023 | } 1024 | 1025 | const domain = await findOrCreateCustomDomain(instance, inputs, clients); 1026 | instance.state.apigatewayHostedZoneId = domain.apigatewayHostedZoneId; 1027 | instance.state.apigatewayDomainName = domain.apigatewayDomainName; 1028 | 1029 | const mappingId = await findOrCreateApiMapping(instance, inputs, clients); 1030 | instance.state.apiMappingId = mappingId; 1031 | 1032 | if (instance.state.domainHostedZoneId) { 1033 | await ensureRecordSetForApiGCustomDomain(instance, inputs, clients); 1034 | } 1035 | }; 1036 | 1037 | /** 1038 | * 1039 | * 1040 | * Removal Logic 1041 | * 1042 | * 1043 | */ 1044 | 1045 | /* 1046 | * Removes the Function & Meta Roles from aws according to the provided config 1047 | * 1048 | * @param ${object} clients - an object containing aws sdk clients 1049 | * @param ${object} config - the component config 1050 | */ 1051 | const removeAllRoles = async (instance, clients) => { 1052 | // Delete Function Role 1053 | if (instance.state.defaultLambdaRoleName) { 1054 | console.log('Deleting the default Function Role...'); 1055 | await clients.extras.removeRole({ 1056 | roleName: instance.state.defaultLambdaRoleName, 1057 | }); 1058 | } 1059 | 1060 | // Delete Meta Role 1061 | if (instance.state.metaRoleName) { 1062 | console.log('Deleting the Meta Role...'); 1063 | await clients.extras.removeRole({ 1064 | roleName: instance.state.metaRoleName, 1065 | }); 1066 | } 1067 | }; 1068 | 1069 | /* 1070 | * Removes a lambda function from aws according to the provided config 1071 | * 1072 | * @param ${object} clients - an object containing aws sdk clients 1073 | * @param ${object} config - the component config 1074 | */ 1075 | const removeLambda = async (instance, clients) => { 1076 | if (!instance.state.lambdaName) { 1077 | return; 1078 | } 1079 | 1080 | console.log(`Removing lambda with arn ${instance.state.lambdaArn}`); 1081 | 1082 | try { 1083 | const params = { FunctionName: instance.state.lambdaName }; 1084 | await clients.lambda.deleteFunction(params).promise(); 1085 | } catch (error) { 1086 | if (error.code !== 'ResourceNotFoundException') { 1087 | throw error; 1088 | } 1089 | } 1090 | }; 1091 | 1092 | /** 1093 | * Remove Mapping between API Gateway Custom Domain & HTTP API. This has to be removed before API Gateway Custom Domain can be deleted. 1094 | */ 1095 | const removeApiMapping = async (instance, clients) => { 1096 | if (!instance.state.apiMappingId || !instance.state.domain) { 1097 | return; 1098 | } 1099 | 1100 | console.log( 1101 | `Removing API Mapping with ID ${instance.state.apiMappingId} and domain ${instance.state.domain}` 1102 | ); 1103 | 1104 | const params = { 1105 | ApiMappingId: instance.state.apiMappingId, 1106 | DomainName: instance.state.domain, 1107 | }; 1108 | 1109 | await clients.apig.deleteApiMapping(params).promise(); 1110 | }; 1111 | 1112 | /* 1113 | * Removes an API from aws according to the provided config 1114 | * 1115 | * @param ${object} clients - an object containing aws sdk clients 1116 | * @param ${object} config - the component config 1117 | */ 1118 | const removeApi = async (instance, clients) => { 1119 | if (!instance.state.apiId) { 1120 | return; 1121 | } 1122 | 1123 | console.log(`Removing API with ID ${instance.state.apiId}`); 1124 | 1125 | try { 1126 | await clients.apig.deleteApi({ ApiId: instance.state.apiId }).promise(); 1127 | } catch (e) { 1128 | console.log(e); 1129 | } 1130 | }; 1131 | 1132 | /** 1133 | * Remove API Gateway Domain 1134 | */ 1135 | const removeDomainFromApig = async (instance, clients) => { 1136 | if (!instance.state.domain) { 1137 | return; 1138 | } 1139 | 1140 | console.log(`Removing domain ${instance.state.domain} from API Gateway`); 1141 | 1142 | const params = { 1143 | DomainName: instance.state.domain, 1144 | }; 1145 | 1146 | await clients.apig.deleteDomainName(params).promise(); 1147 | }; 1148 | 1149 | /** 1150 | * Remove API Gateway Domain DNS Records 1151 | */ 1152 | const removeDnsRecordsForApigDomain = async (instance, clients) => { 1153 | if ( 1154 | !instance.state.domain || 1155 | !instance.state.domainHostedZoneId || 1156 | !instance.state.apigatewayDomainName 1157 | ) { 1158 | return; 1159 | } 1160 | 1161 | console.log(`Removing DNS records for domain ${instance.state.domain}`); 1162 | 1163 | const dnsRecord = { 1164 | HostedZoneId: instance.state.domainHostedZoneId, 1165 | ChangeBatch: { 1166 | Changes: [ 1167 | { 1168 | Action: 'DELETE', 1169 | ResourceRecordSet: { 1170 | Name: instance.state.domain, 1171 | Type: 'A', 1172 | AliasTarget: { 1173 | HostedZoneId: instance.state.apigatewayHostedZoneId, 1174 | DNSName: instance.state.apigatewayDomainName, 1175 | EvaluateTargetHealth: false, 1176 | }, 1177 | }, 1178 | }, 1179 | ], 1180 | }, 1181 | }; 1182 | 1183 | await clients.route53.changeResourceRecordSets(dnsRecord).promise(); 1184 | }; 1185 | 1186 | /** 1187 | * Remove a custom domain 1188 | */ 1189 | const removeDomain = async (instance, clients) => { 1190 | await removeApiMapping(instance, clients); 1191 | await removeDomainFromApig(instance, clients); 1192 | await removeDnsRecordsForApigDomain(instance, clients); 1193 | }; 1194 | 1195 | /** 1196 | * 1197 | * 1198 | * Metrics Logic 1199 | * 1200 | * 1201 | */ 1202 | 1203 | /** 1204 | * Get metrics from cloudwatch 1205 | * @param {*} clients 1206 | * @param {*} rangeStart 1207 | * @param {*} rangeEnd 1208 | */ 1209 | const getMetrics = async (region, metaRoleArn, apiId, functionName, rangeStart, rangeEnd) => { 1210 | /** 1211 | * Create AWS STS Token via the meta role that is deployed with the Express Component 1212 | */ 1213 | 1214 | // Assume Role 1215 | const assumeParams = {}; 1216 | assumeParams.RoleSessionName = `session${Date.now()}`; 1217 | assumeParams.RoleArn = metaRoleArn; 1218 | assumeParams.DurationSeconds = 900; 1219 | 1220 | const sts = new AWS.STS({ region }); 1221 | const resAssume = await sts.assumeRole(assumeParams).promise(); 1222 | 1223 | const roleCreds = {}; 1224 | roleCreds.accessKeyId = resAssume.Credentials.AccessKeyId; 1225 | roleCreds.secretAccessKey = resAssume.Credentials.SecretAccessKey; 1226 | roleCreds.sessionToken = resAssume.Credentials.SessionToken; 1227 | 1228 | const resources = [ 1229 | { 1230 | type: 'aws_http_api', 1231 | apiId, 1232 | }, 1233 | { 1234 | type: 'aws_lambda', 1235 | functionName, 1236 | }, 1237 | ]; 1238 | 1239 | /** 1240 | * Instantiate a new Extras instance w/ the temporary credentials 1241 | */ 1242 | 1243 | const extras = new AWS.Extras({ 1244 | credentials: roleCreds, 1245 | region, 1246 | }); 1247 | 1248 | return await extras.getMetrics({ 1249 | rangeStart, 1250 | rangeEnd, 1251 | resources, 1252 | }); 1253 | }; 1254 | 1255 | /* 1256 | * Fetches the lambda alias that the user specified 1257 | * 1258 | * @param ${instance} instance - the component instance 1259 | * @param ${object} inputs - the component inputs 1260 | * @param ${object} clients - the aws clients object 1261 | */ 1262 | const getAlias = async (instance, inputs, clients) => { 1263 | try { 1264 | const getAliasParams = { 1265 | FunctionName: instance.state.lambdaName, 1266 | Name: inputs.alias, 1267 | }; 1268 | 1269 | const getAliasRes = await clients.lambda.getAlias(getAliasParams).promise(); 1270 | return getAliasRes.AliasArn; 1271 | } catch (e) { 1272 | if (e.code === 'ResourceNotFoundException') { 1273 | return null; 1274 | } 1275 | throw e; 1276 | } 1277 | }; 1278 | 1279 | /* 1280 | * Fetches the function version of the specified alias 1281 | * 1282 | * @param ${instance} instance - the component instance 1283 | * @param ${object} inputs - the component inputs 1284 | * @param ${object} clients - the aws clients object 1285 | */ 1286 | const getAliasFunctionVersion = async (instance, inputs, clients) => { 1287 | try { 1288 | const getAliasParams = { 1289 | FunctionName: instance.state.lambdaName, 1290 | Name: 'default', 1291 | }; 1292 | 1293 | const getAliasRes = await clients.lambda.getAlias(getAliasParams).promise(); 1294 | return getAliasRes.FunctionVersion; 1295 | } catch (e) { 1296 | if (e.code === 'ResourceNotFoundException') { 1297 | throw new Error('The specified traffic destination does not exist'); 1298 | } 1299 | throw e; 1300 | } 1301 | }; 1302 | 1303 | /* 1304 | * Gets a clean AWS Routing Config object based on the traffic config 1305 | * 1306 | * @param ${instance} instance - the component instance 1307 | * @param ${object} inputs - the component inputs 1308 | * @param ${object} clients - the aws clients object 1309 | */ 1310 | const getRoutingConfig = async (instance, inputs, clients) => { 1311 | // return null if user did not define any canary deployments settings 1312 | if (inputs.alias !== 'feature') { 1313 | return null; 1314 | } 1315 | 1316 | const additionalVersion = await getAliasFunctionVersion(instance, inputs, clients); 1317 | 1318 | const routingConfig = { 1319 | AdditionalVersionWeights: {}, 1320 | }; 1321 | 1322 | // if the user specified 0.2 traffic for this feature codebase/deployment 1323 | // that means redirect 0.8 to the default alias 1324 | routingConfig.AdditionalVersionWeights[additionalVersion] = 1 - inputs.traffic; 1325 | 1326 | return routingConfig; 1327 | }; 1328 | 1329 | /* 1330 | * Creates or updates default or feature alias 1331 | * 1332 | * @param ${instance} instance - the component instance 1333 | * @param ${object} inputs - the component inputs 1334 | * @param ${object} clients - the aws clients object 1335 | */ 1336 | const createOrUpdateAlias = async (instance, inputs, clients) => { 1337 | inputs.alias = 'default'; 1338 | 1339 | if (inputs.traffic && Number(instance.state.lambdaVersion) > 1) { 1340 | inputs.alias = 'feature'; 1341 | } 1342 | 1343 | console.log(`Verifying alias "${inputs.alias}"...`); 1344 | instance.state.aliasArn = await getAlias(instance, inputs, clients); 1345 | 1346 | const aliasParams = { 1347 | FunctionName: instance.state.lambdaName, 1348 | Name: inputs.alias, 1349 | FunctionVersion: instance.state.lambdaVersion, 1350 | }; 1351 | 1352 | const userDefinedRoutingConfig = await getRoutingConfig(instance, inputs, clients); 1353 | 1354 | if (userDefinedRoutingConfig) { 1355 | aliasParams.RoutingConfig = userDefinedRoutingConfig; 1356 | console.log( 1357 | `Shifting ${String(inputs.traffic * 100)}% of the traffic to the "${inputs.alias}" alias...` 1358 | ); 1359 | } 1360 | 1361 | if (instance.state.aliasArn) { 1362 | console.log(`Alias "${inputs.alias}" found. Updating...`); 1363 | instance.state.aliasArn = (await clients.lambda.updateAlias(aliasParams).promise()).AliasArn; 1364 | console.log(`Alias "${inputs.alias}" updated.`); 1365 | } else { 1366 | console.log(`Alias "${inputs.alias}" not found. Creating...`); 1367 | instance.state.aliasArn = (await clients.lambda.createAlias(aliasParams).promise()).AliasArn; 1368 | console.log(`Alias "${inputs.alias}" created.`); 1369 | } 1370 | 1371 | return instance.state.aliasArn; 1372 | }; 1373 | 1374 | const retryIfRateExceeded = async (awsOperation, attempts = 10) => { 1375 | // after 10 attempts, just exist! 1376 | if (attempts === 0) { 1377 | throw new Error('Throttled by AWS despite 10 retry attempts.'); 1378 | } 1379 | try { 1380 | return await awsOperation(); 1381 | } catch (e) { 1382 | if (e.message.includes('Rate exceeded')) { 1383 | // sleep for a certain period according to 1384 | // the number of retry attempts performed so far 1385 | // the more attempts, the more sleep. 1386 | await sleep(Math.floor(10000 / attempts)); 1387 | 1388 | return retryIfRateExceeded(awsOperation, --attempts); 1389 | } 1390 | throw e; 1391 | } 1392 | }; 1393 | 1394 | module.exports = { 1395 | generateId, 1396 | sleep, 1397 | getClients, 1398 | packageExpress, 1399 | createOrUpdateFunctionRole, 1400 | createOrUpdateMetaRole, 1401 | createOrUpdateLambda, 1402 | createOrUpdateApi, 1403 | createOrUpdateDomain, 1404 | createOrUpdateAlias, 1405 | removeApi, 1406 | removeAllRoles, 1407 | removeLambda, 1408 | removeDomain, 1409 | getMetrics, 1410 | retryIfRateExceeded, 1411 | }; 1412 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-tsc/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | jest/* 3 | coverage 4 | node_modules 5 | build/ 6 | .env* 7 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-tsc/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/body-parser": { 8 | "version": "1.19.0", 9 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", 10 | "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", 11 | "dev": true, 12 | "requires": { 13 | "@types/connect": "*", 14 | "@types/node": "*" 15 | } 16 | }, 17 | "@types/connect": { 18 | "version": "3.4.33", 19 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", 20 | "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", 21 | "dev": true, 22 | "requires": { 23 | "@types/node": "*" 24 | } 25 | }, 26 | "@types/express": { 27 | "version": "4.17.8", 28 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz", 29 | "integrity": "sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ==", 30 | "dev": true, 31 | "requires": { 32 | "@types/body-parser": "*", 33 | "@types/express-serve-static-core": "*", 34 | "@types/qs": "*", 35 | "@types/serve-static": "*" 36 | } 37 | }, 38 | "@types/express-serve-static-core": { 39 | "version": "4.17.12", 40 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.12.tgz", 41 | "integrity": "sha512-EaEdY+Dty1jEU7U6J4CUWwxL+hyEGMkO5jan5gplfegUgCUsIUWqXxqw47uGjimeT4Qgkz/XUfwoau08+fgvKA==", 42 | "dev": true, 43 | "requires": { 44 | "@types/node": "*", 45 | "@types/qs": "*", 46 | "@types/range-parser": "*" 47 | } 48 | }, 49 | "@types/mime": { 50 | "version": "2.0.3", 51 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", 52 | "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", 53 | "dev": true 54 | }, 55 | "@types/node": { 56 | "version": "14.11.1", 57 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.1.tgz", 58 | "integrity": "sha512-oTQgnd0hblfLsJ6BvJzzSL+Inogp3lq9fGgqRkMB/ziKMgEUaFl801OncOzUmalfzt14N0oPHMK47ipl+wbTIw==", 59 | "dev": true 60 | }, 61 | "@types/qs": { 62 | "version": "6.9.5", 63 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", 64 | "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", 65 | "dev": true 66 | }, 67 | "@types/range-parser": { 68 | "version": "1.2.3", 69 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", 70 | "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", 71 | "dev": true 72 | }, 73 | "@types/serve-static": { 74 | "version": "1.13.5", 75 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz", 76 | "integrity": "sha512-6M64P58N+OXjU432WoLLBQxbA0LRGBCRm7aAGQJ+SMC1IMl0dgRVi9EFfoDcS2a7Xogygk/eGN94CfwU9UF7UQ==", 77 | "dev": true, 78 | "requires": { 79 | "@types/express-serve-static-core": "*", 80 | "@types/mime": "*" 81 | } 82 | }, 83 | "accepts": { 84 | "version": "1.3.7", 85 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 86 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 87 | "requires": { 88 | "mime-types": "~2.1.24", 89 | "negotiator": "0.6.2" 90 | } 91 | }, 92 | "array-flatten": { 93 | "version": "1.1.1", 94 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 95 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 96 | }, 97 | "body-parser": { 98 | "version": "1.19.0", 99 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 100 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 101 | "requires": { 102 | "bytes": "3.1.0", 103 | "content-type": "~1.0.4", 104 | "debug": "2.6.9", 105 | "depd": "~1.1.2", 106 | "http-errors": "1.7.2", 107 | "iconv-lite": "0.4.24", 108 | "on-finished": "~2.3.0", 109 | "qs": "6.7.0", 110 | "raw-body": "2.4.0", 111 | "type-is": "~1.6.17" 112 | } 113 | }, 114 | "bytes": { 115 | "version": "3.1.0", 116 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 117 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 118 | }, 119 | "content-disposition": { 120 | "version": "0.5.3", 121 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 122 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 123 | "requires": { 124 | "safe-buffer": "5.1.2" 125 | } 126 | }, 127 | "content-type": { 128 | "version": "1.0.4", 129 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 130 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 131 | }, 132 | "cookie": { 133 | "version": "0.4.0", 134 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 135 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 136 | }, 137 | "cookie-signature": { 138 | "version": "1.0.6", 139 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 140 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 141 | }, 142 | "debug": { 143 | "version": "2.6.9", 144 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 145 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 146 | "requires": { 147 | "ms": "2.0.0" 148 | } 149 | }, 150 | "depd": { 151 | "version": "1.1.2", 152 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 153 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 154 | }, 155 | "destroy": { 156 | "version": "1.0.4", 157 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 158 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 159 | }, 160 | "ee-first": { 161 | "version": "1.1.1", 162 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 163 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 164 | }, 165 | "encodeurl": { 166 | "version": "1.0.2", 167 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 168 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 169 | }, 170 | "escape-html": { 171 | "version": "1.0.3", 172 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 173 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 174 | }, 175 | "etag": { 176 | "version": "1.8.1", 177 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 178 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 179 | }, 180 | "express": { 181 | "version": "4.17.1", 182 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 183 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 184 | "requires": { 185 | "accepts": "~1.3.7", 186 | "array-flatten": "1.1.1", 187 | "body-parser": "1.19.0", 188 | "content-disposition": "0.5.3", 189 | "content-type": "~1.0.4", 190 | "cookie": "0.4.0", 191 | "cookie-signature": "1.0.6", 192 | "debug": "2.6.9", 193 | "depd": "~1.1.2", 194 | "encodeurl": "~1.0.2", 195 | "escape-html": "~1.0.3", 196 | "etag": "~1.8.1", 197 | "finalhandler": "~1.1.2", 198 | "fresh": "0.5.2", 199 | "merge-descriptors": "1.0.1", 200 | "methods": "~1.1.2", 201 | "on-finished": "~2.3.0", 202 | "parseurl": "~1.3.3", 203 | "path-to-regexp": "0.1.7", 204 | "proxy-addr": "~2.0.5", 205 | "qs": "6.7.0", 206 | "range-parser": "~1.2.1", 207 | "safe-buffer": "5.1.2", 208 | "send": "0.17.1", 209 | "serve-static": "1.14.1", 210 | "setprototypeof": "1.1.1", 211 | "statuses": "~1.5.0", 212 | "type-is": "~1.6.18", 213 | "utils-merge": "1.0.1", 214 | "vary": "~1.1.2" 215 | } 216 | }, 217 | "finalhandler": { 218 | "version": "1.1.2", 219 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 220 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 221 | "requires": { 222 | "debug": "2.6.9", 223 | "encodeurl": "~1.0.2", 224 | "escape-html": "~1.0.3", 225 | "on-finished": "~2.3.0", 226 | "parseurl": "~1.3.3", 227 | "statuses": "~1.5.0", 228 | "unpipe": "~1.0.0" 229 | } 230 | }, 231 | "forwarded": { 232 | "version": "0.1.2", 233 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 234 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 235 | }, 236 | "fresh": { 237 | "version": "0.5.2", 238 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 239 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 240 | }, 241 | "http-errors": { 242 | "version": "1.7.2", 243 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 244 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 245 | "requires": { 246 | "depd": "~1.1.2", 247 | "inherits": "2.0.3", 248 | "setprototypeof": "1.1.1", 249 | "statuses": ">= 1.5.0 < 2", 250 | "toidentifier": "1.0.0" 251 | } 252 | }, 253 | "iconv-lite": { 254 | "version": "0.4.24", 255 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 256 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 257 | "requires": { 258 | "safer-buffer": ">= 2.1.2 < 3" 259 | } 260 | }, 261 | "inherits": { 262 | "version": "2.0.3", 263 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 264 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 265 | }, 266 | "ipaddr.js": { 267 | "version": "1.9.1", 268 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 269 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 270 | }, 271 | "media-typer": { 272 | "version": "0.3.0", 273 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 274 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 275 | }, 276 | "merge-descriptors": { 277 | "version": "1.0.1", 278 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 279 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 280 | }, 281 | "methods": { 282 | "version": "1.1.2", 283 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 284 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 285 | }, 286 | "mime": { 287 | "version": "1.6.0", 288 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 289 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 290 | }, 291 | "mime-db": { 292 | "version": "1.44.0", 293 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 294 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 295 | }, 296 | "mime-types": { 297 | "version": "2.1.27", 298 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 299 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 300 | "requires": { 301 | "mime-db": "1.44.0" 302 | } 303 | }, 304 | "ms": { 305 | "version": "2.0.0", 306 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 307 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 308 | }, 309 | "negotiator": { 310 | "version": "0.6.2", 311 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 312 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 313 | }, 314 | "on-finished": { 315 | "version": "2.3.0", 316 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 317 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 318 | "requires": { 319 | "ee-first": "1.1.1" 320 | } 321 | }, 322 | "parseurl": { 323 | "version": "1.3.3", 324 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 325 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 326 | }, 327 | "path-to-regexp": { 328 | "version": "0.1.7", 329 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 330 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 331 | }, 332 | "proxy-addr": { 333 | "version": "2.0.6", 334 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 335 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 336 | "requires": { 337 | "forwarded": "~0.1.2", 338 | "ipaddr.js": "1.9.1" 339 | } 340 | }, 341 | "qs": { 342 | "version": "6.7.0", 343 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 344 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 345 | }, 346 | "range-parser": { 347 | "version": "1.2.1", 348 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 349 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 350 | }, 351 | "raw-body": { 352 | "version": "2.4.0", 353 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 354 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 355 | "requires": { 356 | "bytes": "3.1.0", 357 | "http-errors": "1.7.2", 358 | "iconv-lite": "0.4.24", 359 | "unpipe": "1.0.0" 360 | } 361 | }, 362 | "safe-buffer": { 363 | "version": "5.1.2", 364 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 365 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 366 | }, 367 | "safer-buffer": { 368 | "version": "2.1.2", 369 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 370 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 371 | }, 372 | "send": { 373 | "version": "0.17.1", 374 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 375 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 376 | "requires": { 377 | "debug": "2.6.9", 378 | "depd": "~1.1.2", 379 | "destroy": "~1.0.4", 380 | "encodeurl": "~1.0.2", 381 | "escape-html": "~1.0.3", 382 | "etag": "~1.8.1", 383 | "fresh": "0.5.2", 384 | "http-errors": "~1.7.2", 385 | "mime": "1.6.0", 386 | "ms": "2.1.1", 387 | "on-finished": "~2.3.0", 388 | "range-parser": "~1.2.1", 389 | "statuses": "~1.5.0" 390 | }, 391 | "dependencies": { 392 | "ms": { 393 | "version": "2.1.1", 394 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 395 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 396 | } 397 | } 398 | }, 399 | "serve-static": { 400 | "version": "1.14.1", 401 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 402 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 403 | "requires": { 404 | "encodeurl": "~1.0.2", 405 | "escape-html": "~1.0.3", 406 | "parseurl": "~1.3.3", 407 | "send": "0.17.1" 408 | } 409 | }, 410 | "setprototypeof": { 411 | "version": "1.1.1", 412 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 413 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 414 | }, 415 | "statuses": { 416 | "version": "1.5.0", 417 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 418 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 419 | }, 420 | "toidentifier": { 421 | "version": "1.0.0", 422 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 423 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 424 | }, 425 | "type-is": { 426 | "version": "1.6.18", 427 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 428 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 429 | "requires": { 430 | "media-typer": "0.3.0", 431 | "mime-types": "~2.1.24" 432 | } 433 | }, 434 | "typescript": { 435 | "version": "4.0.3", 436 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", 437 | "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", 438 | "dev": true 439 | }, 440 | "unpipe": { 441 | "version": "1.0.0", 442 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 443 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 444 | }, 445 | "utils-merge": { 446 | "version": "1.0.1", 447 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 448 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 449 | }, 450 | "vary": { 451 | "version": "1.1.2", 452 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 453 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 454 | } 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-tsc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-starter-typescript-tsc", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "dependencies": { 7 | "express": "^4.17.1" 8 | }, 9 | "devDependencies": { 10 | "@types/express": "^4.17.8", 11 | "typescript": "^4.0.3" 12 | }, 13 | "scripts": { 14 | "build": "tsc && npm run build-dependencies", 15 | "build-dependencies": "cp package*.json build/ && npm i --only=prod --prefix=build", 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "author": "", 19 | "license": "ISC" 20 | } 21 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-tsc/serverless.yml: -------------------------------------------------------------------------------- 1 | app: express-starter 2 | component: express 3 | name: express-starter-typescript-tsc 4 | 5 | inputs: 6 | src: 7 | src: ./ 8 | hook: npm run build 9 | dist: build 10 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-tsc/src/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | 3 | const app = express(); 4 | 5 | // Routes 6 | app.get('/*', (req, res) => { 7 | res.send(`Request received: ${req.method} - ${req.path}`); 8 | }); 9 | 10 | // Error handler 11 | app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { 12 | console.error(err); 13 | res.status(500).send('An internal server error occurred'); 14 | }); 15 | 16 | module.exports = app; 17 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-tsc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build/", 4 | "noImplicitAny": true, 5 | "module": "commonjs", 6 | "target": "es5", 7 | "jsx": "react", 8 | "allowJs": true 9 | }, 10 | "include": ["./src"] 11 | } 12 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-webpack/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | jest/* 3 | coverage 4 | node_modules 5 | build/ 6 | .env* 7 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-starter-typescript-webpack", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "dependencies": { 7 | "express": "^4.17.1" 8 | }, 9 | "devDependencies": { 10 | "@types/express": "^4.17.8", 11 | "ts-loader": "^8.0.4", 12 | "typescript": "^4.0.3", 13 | "webpack": "^5.0.0-beta.32", 14 | "webpack-cli": "^3.3.12" 15 | }, 16 | "scripts": { 17 | "build": "webpack && npm i --only=prod --prefix=build/", 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "author": "", 21 | "license": "ISC" 22 | } 23 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-webpack/serverless.yml: -------------------------------------------------------------------------------- 1 | app: express-starter 2 | component: express 3 | name: express-starter-typescript-webpack 4 | 5 | inputs: 6 | src: 7 | src: ./ 8 | hook: npm run build 9 | dist: build 10 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-webpack/src/app.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | 3 | const app = express(); 4 | 5 | // Routes 6 | app.get('/*', (req, res) => { 7 | res.send(`Request received: ${req.method} - ${req.path}`); 8 | }); 9 | 10 | // Error handler 11 | app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { 12 | console.error(err); 13 | res.status(500).send('An internal server error occurred'); 14 | }); 15 | 16 | module.exports = app; 17 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-webpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build/", 4 | "noImplicitAny": true, 5 | "module": "commonjs", 6 | "target": "es5", 7 | "jsx": "react", 8 | "allowJs": true 9 | }, 10 | "include": ["./src"] 11 | } 12 | -------------------------------------------------------------------------------- /templates/express-starter-typescript-webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | entry: './src/app.ts', 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.tsx?$/, 12 | use: 'ts-loader', 13 | exclude: /node_modules/, 14 | }, 15 | ], 16 | }, 17 | target: 'node', 18 | resolve: { 19 | extensions: ['.tsx', '.ts', '.js'], 20 | }, 21 | output: { 22 | filename: 'app.js', 23 | libraryTarget: 'commonjs-module', 24 | path: path.resolve(__dirname, 'build'), 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /templates/express-starter/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | jest/* 3 | coverage 4 | node_modules 5 | build/ 6 | .env* 7 | -------------------------------------------------------------------------------- /templates/express-starter/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint-disable-next-line import/no-unresolved 4 | const express = require('express'); 5 | 6 | const app = express(); 7 | 8 | // Routes 9 | app.get('/*', (req, res) => { 10 | res.send(`Request received: ${req.method} - ${req.path}`); 11 | }); 12 | 13 | // Error handler 14 | app.use((err, req, res) => { 15 | console.error(err); 16 | res.status(500).send('Internal Serverless Error'); 17 | }); 18 | 19 | module.exports = app; 20 | -------------------------------------------------------------------------------- /templates/express-starter/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "body-parser": { 22 | "version": "1.19.0", 23 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 24 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 25 | "requires": { 26 | "bytes": "3.1.0", 27 | "content-type": "~1.0.4", 28 | "debug": "2.6.9", 29 | "depd": "~1.1.2", 30 | "http-errors": "1.7.2", 31 | "iconv-lite": "0.4.24", 32 | "on-finished": "~2.3.0", 33 | "qs": "6.7.0", 34 | "raw-body": "2.4.0", 35 | "type-is": "~1.6.17" 36 | } 37 | }, 38 | "bytes": { 39 | "version": "3.1.0", 40 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 41 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 42 | }, 43 | "content-disposition": { 44 | "version": "0.5.3", 45 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 46 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 47 | "requires": { 48 | "safe-buffer": "5.1.2" 49 | } 50 | }, 51 | "content-type": { 52 | "version": "1.0.4", 53 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 54 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 55 | }, 56 | "cookie": { 57 | "version": "0.4.0", 58 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 59 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 60 | }, 61 | "cookie-signature": { 62 | "version": "1.0.6", 63 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 64 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 65 | }, 66 | "debug": { 67 | "version": "2.6.9", 68 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 69 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 70 | "requires": { 71 | "ms": "2.0.0" 72 | } 73 | }, 74 | "depd": { 75 | "version": "1.1.2", 76 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 77 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 78 | }, 79 | "destroy": { 80 | "version": "1.0.4", 81 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 82 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 83 | }, 84 | "ee-first": { 85 | "version": "1.1.1", 86 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 87 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 88 | }, 89 | "encodeurl": { 90 | "version": "1.0.2", 91 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 92 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 93 | }, 94 | "escape-html": { 95 | "version": "1.0.3", 96 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 97 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 98 | }, 99 | "etag": { 100 | "version": "1.8.1", 101 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 102 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 103 | }, 104 | "express": { 105 | "version": "4.17.1", 106 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 107 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 108 | "requires": { 109 | "accepts": "~1.3.7", 110 | "array-flatten": "1.1.1", 111 | "body-parser": "1.19.0", 112 | "content-disposition": "0.5.3", 113 | "content-type": "~1.0.4", 114 | "cookie": "0.4.0", 115 | "cookie-signature": "1.0.6", 116 | "debug": "2.6.9", 117 | "depd": "~1.1.2", 118 | "encodeurl": "~1.0.2", 119 | "escape-html": "~1.0.3", 120 | "etag": "~1.8.1", 121 | "finalhandler": "~1.1.2", 122 | "fresh": "0.5.2", 123 | "merge-descriptors": "1.0.1", 124 | "methods": "~1.1.2", 125 | "on-finished": "~2.3.0", 126 | "parseurl": "~1.3.3", 127 | "path-to-regexp": "0.1.7", 128 | "proxy-addr": "~2.0.5", 129 | "qs": "6.7.0", 130 | "range-parser": "~1.2.1", 131 | "safe-buffer": "5.1.2", 132 | "send": "0.17.1", 133 | "serve-static": "1.14.1", 134 | "setprototypeof": "1.1.1", 135 | "statuses": "~1.5.0", 136 | "type-is": "~1.6.18", 137 | "utils-merge": "1.0.1", 138 | "vary": "~1.1.2" 139 | } 140 | }, 141 | "finalhandler": { 142 | "version": "1.1.2", 143 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 144 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 145 | "requires": { 146 | "debug": "2.6.9", 147 | "encodeurl": "~1.0.2", 148 | "escape-html": "~1.0.3", 149 | "on-finished": "~2.3.0", 150 | "parseurl": "~1.3.3", 151 | "statuses": "~1.5.0", 152 | "unpipe": "~1.0.0" 153 | } 154 | }, 155 | "forwarded": { 156 | "version": "0.1.2", 157 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 158 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 159 | }, 160 | "fresh": { 161 | "version": "0.5.2", 162 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 163 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 164 | }, 165 | "http-errors": { 166 | "version": "1.7.2", 167 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 168 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 169 | "requires": { 170 | "depd": "~1.1.2", 171 | "inherits": "2.0.3", 172 | "setprototypeof": "1.1.1", 173 | "statuses": ">= 1.5.0 < 2", 174 | "toidentifier": "1.0.0" 175 | } 176 | }, 177 | "iconv-lite": { 178 | "version": "0.4.24", 179 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 180 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 181 | "requires": { 182 | "safer-buffer": ">= 2.1.2 < 3" 183 | } 184 | }, 185 | "inherits": { 186 | "version": "2.0.3", 187 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 188 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 189 | }, 190 | "ipaddr.js": { 191 | "version": "1.9.1", 192 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 193 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 194 | }, 195 | "media-typer": { 196 | "version": "0.3.0", 197 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 198 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 199 | }, 200 | "merge-descriptors": { 201 | "version": "1.0.1", 202 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 203 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 204 | }, 205 | "methods": { 206 | "version": "1.1.2", 207 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 208 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 209 | }, 210 | "mime": { 211 | "version": "1.6.0", 212 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 213 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 214 | }, 215 | "mime-db": { 216 | "version": "1.44.0", 217 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 218 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 219 | }, 220 | "mime-types": { 221 | "version": "2.1.27", 222 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 223 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 224 | "requires": { 225 | "mime-db": "1.44.0" 226 | } 227 | }, 228 | "ms": { 229 | "version": "2.0.0", 230 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 231 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 232 | }, 233 | "negotiator": { 234 | "version": "0.6.2", 235 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 236 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 237 | }, 238 | "on-finished": { 239 | "version": "2.3.0", 240 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 241 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 242 | "requires": { 243 | "ee-first": "1.1.1" 244 | } 245 | }, 246 | "parseurl": { 247 | "version": "1.3.3", 248 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 249 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 250 | }, 251 | "path-to-regexp": { 252 | "version": "0.1.7", 253 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 254 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 255 | }, 256 | "proxy-addr": { 257 | "version": "2.0.6", 258 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 259 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 260 | "requires": { 261 | "forwarded": "~0.1.2", 262 | "ipaddr.js": "1.9.1" 263 | } 264 | }, 265 | "qs": { 266 | "version": "6.7.0", 267 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 268 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 269 | }, 270 | "range-parser": { 271 | "version": "1.2.1", 272 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 273 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 274 | }, 275 | "raw-body": { 276 | "version": "2.4.0", 277 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 278 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 279 | "requires": { 280 | "bytes": "3.1.0", 281 | "http-errors": "1.7.2", 282 | "iconv-lite": "0.4.24", 283 | "unpipe": "1.0.0" 284 | } 285 | }, 286 | "safe-buffer": { 287 | "version": "5.1.2", 288 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 289 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 290 | }, 291 | "safer-buffer": { 292 | "version": "2.1.2", 293 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 294 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 295 | }, 296 | "send": { 297 | "version": "0.17.1", 298 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 299 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 300 | "requires": { 301 | "debug": "2.6.9", 302 | "depd": "~1.1.2", 303 | "destroy": "~1.0.4", 304 | "encodeurl": "~1.0.2", 305 | "escape-html": "~1.0.3", 306 | "etag": "~1.8.1", 307 | "fresh": "0.5.2", 308 | "http-errors": "~1.7.2", 309 | "mime": "1.6.0", 310 | "ms": "2.1.1", 311 | "on-finished": "~2.3.0", 312 | "range-parser": "~1.2.1", 313 | "statuses": "~1.5.0" 314 | }, 315 | "dependencies": { 316 | "ms": { 317 | "version": "2.1.1", 318 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 319 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 320 | } 321 | } 322 | }, 323 | "serve-static": { 324 | "version": "1.14.1", 325 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 326 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 327 | "requires": { 328 | "encodeurl": "~1.0.2", 329 | "escape-html": "~1.0.3", 330 | "parseurl": "~1.3.3", 331 | "send": "0.17.1" 332 | } 333 | }, 334 | "setprototypeof": { 335 | "version": "1.1.1", 336 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 337 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 338 | }, 339 | "statuses": { 340 | "version": "1.5.0", 341 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 342 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 343 | }, 344 | "toidentifier": { 345 | "version": "1.0.0", 346 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 347 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 348 | }, 349 | "type-is": { 350 | "version": "1.6.18", 351 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 352 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 353 | "requires": { 354 | "media-typer": "0.3.0", 355 | "mime-types": "~2.1.24" 356 | } 357 | }, 358 | "unpipe": { 359 | "version": "1.0.0", 360 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 361 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 362 | }, 363 | "utils-merge": { 364 | "version": "1.0.1", 365 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 366 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 367 | }, 368 | "vary": { 369 | "version": "1.1.2", 370 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 371 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /templates/express-starter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-starter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "dependencies": { 7 | "express": "^4.17.1" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /templates/express-starter/serverless.template.yml: -------------------------------------------------------------------------------- 1 | name: express-starter 2 | org: serverlessinc 3 | description: Deploys a serverless express api on top of AWS Lambda 4 | keywords: aws, serverless, faas, lambda, express 5 | repo: https://github.com/serverless-components/express 6 | license: MIT 7 | -------------------------------------------------------------------------------- /templates/express-starter/serverless.yml: -------------------------------------------------------------------------------- 1 | component: express 2 | name: express-starter 3 | org: serverlessinc 4 | 5 | inputs: 6 | src: ./ 7 | -------------------------------------------------------------------------------- /test/integration.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const axios = require('axios'); 5 | const { sleep, getCredentials, getServerlessSdk, getLambda } = require('./utils'); 6 | 7 | // set enough timeout for deployment to finish 8 | jest.setTimeout(100000); 9 | 10 | // the yaml file we're testing against 11 | const instanceYaml = { 12 | org: 'serverlessinc', 13 | app: 'component-tests', 14 | component: 'express@dev', 15 | name: 'express-integration-tests', 16 | stage: 'dev', 17 | inputs: {}, // should deploy with zero inputs 18 | }; 19 | 20 | // we need to keep the initial instance state after first deployment 21 | // to validate removal later 22 | let firstInstanceState; 23 | 24 | // get aws credentials from env 25 | const credentials = getCredentials(); 26 | 27 | // get serverless access key from env and construct sdk 28 | const sdk = getServerlessSdk(instanceYaml.org); 29 | 30 | // clean up the instance after tests 31 | afterAll(async () => { 32 | await sdk.remove(instanceYaml, credentials); 33 | }); 34 | 35 | it('should successfully deploy express app', async () => { 36 | const instance = await sdk.deploy(instanceYaml, credentials); 37 | 38 | // store the inital state for removal validation later on 39 | firstInstanceState = instance.state; 40 | 41 | expect(instance.outputs.url).toBeDefined(); 42 | }); 43 | 44 | it('should successfully update basic configuration', async () => { 45 | instanceYaml.inputs.memory = 3008; 46 | instanceYaml.inputs.timeout = 30; 47 | instanceYaml.inputs.env = { DEBUG: 'express:*' }; 48 | 49 | const instance = await sdk.deploy(instanceYaml, credentials); 50 | 51 | const lambda = await getLambda(credentials, instance.state.lambdaName); 52 | 53 | expect(lambda.MemorySize).toEqual(instanceYaml.inputs.memory); 54 | expect(lambda.Timeout).toEqual(instanceYaml.inputs.timeout); 55 | expect(lambda.Environment.Variables.DEBUG).toEqual(instanceYaml.inputs.env.DEBUG); 56 | }); 57 | 58 | it('should successfully update source code', async () => { 59 | // first deployment we did not specify source 60 | // we're now specifying our own source 61 | instanceYaml.inputs.src = path.resolve(__dirname, 'src'); 62 | 63 | const instance = await sdk.deploy(instanceYaml, credentials); 64 | 65 | const response = await axios.get(instance.outputs.url); 66 | 67 | // make sure it's the response we're expecting from the source we provided 68 | expect(response.data).toEqual('hello world'); 69 | }); 70 | 71 | it('should attach cookies correctly', async () => { 72 | instanceYaml.inputs.src = path.resolve(__dirname, 'src'); 73 | 74 | const instance = await sdk.deploy(instanceYaml, credentials); 75 | 76 | const response1 = await axios.get(`${instance.outputs.url}/cookie`, { 77 | headers: { 78 | cookie: 'cookie1=yum', 79 | }, 80 | }); 81 | expect(response1.data).toEqual('cookie1=yum'); 82 | 83 | const response2 = await axios.get(`${instance.outputs.url}/cookie`, { 84 | headers: { 85 | cookie: 'cookie1=yum; cookie2=hot', 86 | }, 87 | }); 88 | expect(response2.data).toEqual('cookie1=yum; cookie2=hot'); 89 | 90 | const response3 = await axios.get(`${instance.outputs.url}/cookie`); 91 | expect(response3.data).toEqual('undefined'); 92 | }); 93 | 94 | it('should enable traffic shifting', async () => { 95 | // change source code and apply it to a small subset of traffic 96 | instanceYaml.inputs.traffic = 0.2; 97 | delete instanceYaml.inputs.src; 98 | 99 | const instance = await sdk.deploy(instanceYaml, credentials); 100 | 101 | // make 10 requests and compare the experimental vs stable code responses 102 | let stableCodeResponses = 0; 103 | let experimentalCodeResponses = 0; 104 | const get = async () => { 105 | if (stableCodeResponses + experimentalCodeResponses > 10 && experimentalCodeResponses > 1) { 106 | return null; 107 | } 108 | 109 | const response = await axios.get(instance.outputs.url); 110 | 111 | if (response.data === 'hello world') { 112 | stableCodeResponses++; 113 | return get(); 114 | } 115 | experimentalCodeResponses++; 116 | 117 | return get(); 118 | }; 119 | 120 | await get(); 121 | 122 | expect(stableCodeResponses).toBeGreaterThan(experimentalCodeResponses); 123 | }); 124 | 125 | it('should disable traffic shifting', async () => { 126 | delete instanceYaml.inputs.traffic; 127 | 128 | const instance = await sdk.deploy(instanceYaml, credentials); 129 | 130 | // give aws some time... 131 | await sleep(10000); 132 | 133 | // make 10 requests and make sure old responses never appeared 134 | let requests = 0; 135 | const get = async () => { 136 | // after 10 requests, exit 137 | if (requests > 10) { 138 | return null; 139 | } 140 | 141 | // make a request 142 | const response = await axios.get(instance.outputs.url); 143 | 144 | // increment the number of requests made 145 | requests++; 146 | 147 | // this is an outdated response. We shouldn't receive it after disabling traffic shifting 148 | if (response.data === 'hello world') { 149 | throw new Error('Failed to disable traffic shifting. Outdated response received'); 150 | } 151 | 152 | return get(); 153 | }; 154 | 155 | // make 10 requests and make sure they're all responding with the latest code changes 156 | await get(); 157 | }); 158 | 159 | it('should successfully remove express app', async () => { 160 | await sdk.remove(instanceYaml, credentials); 161 | 162 | // make sure lambda was actually removed 163 | let lambda; 164 | try { 165 | lambda = await getLambda(credentials, firstInstanceState.lambdaName); 166 | } catch (e) { 167 | if (e.code !== 'ResourceNotFoundException') { 168 | throw e; 169 | } 170 | } 171 | 172 | expect(lambda).toBeUndefined(); 173 | }); 174 | -------------------------------------------------------------------------------- /test/src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | 5 | const app = express(); 6 | 7 | app.get('/cookie', (req, res) => { 8 | res.send(`${req.headers.cookie}`); 9 | }); 10 | 11 | app.get('/*', (req, res) => { 12 | res.send('hello world'); 13 | }); 14 | 15 | module.exports = app; 16 | -------------------------------------------------------------------------------- /test/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "app.js", 3 | "license": "ISC", 4 | "dependencies": { 5 | "express": "^4.17.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const AWS = require('aws-sdk'); 5 | const { ServerlessSDK } = require('@serverless/platform-client'); 6 | const dotenv = require('dotenv').config({ path: path.resolve(__dirname, '.env') }).parsed || {}; 7 | 8 | /* 9 | * Pauses execution for an X period of time 10 | */ 11 | const sleep = async (wait) => new Promise((resolve) => setTimeout(() => resolve(), wait)); 12 | 13 | /* 14 | * Generate random id 15 | */ 16 | const generateId = () => Math.random().toString(36).substring(6); 17 | 18 | /* 19 | * Fetches AWS credentials from the current environment 20 | * either from env vars, or .env file in the /tests directory 21 | */ 22 | const getCredentials = () => { 23 | const credentials = { 24 | aws: { 25 | accessKeyId: process.env.AWS_ACCESS_KEY_ID || dotenv.AWS_ACCESS_KEY_ID, 26 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || dotenv.AWS_SECRET_ACCESS_KEY, 27 | }, 28 | }; 29 | 30 | if (!credentials.aws.accessKeyId || !credentials.aws.accessKeyId) { 31 | throw new Error('Unable to run tests. AWS credentials not found in the envionrment'); 32 | } 33 | 34 | return credentials; 35 | }; 36 | 37 | /* 38 | * Initializes and returns an instance of the serverless sdk 39 | * @param ${string} orgName - the serverless org name. Must correspond to the access key in the env 40 | */ 41 | const getServerlessSdk = (orgName) => { 42 | const sdk = new ServerlessSDK({ 43 | accessKey: process.env.SERVERLESS_ACCESS_KEY || dotenv.SERVERLESS_ACCESS_KEY, 44 | context: { 45 | orgName, 46 | }, 47 | }); 48 | return sdk; 49 | }; 50 | 51 | /* 52 | * Fetches a lambda function from aws for validation 53 | * @param ${object} credentials - the cross provider credentials object 54 | * @param ${string} lambdaName - the name of the lambda function 55 | */ 56 | const getLambda = async (credentials, lambdaName) => { 57 | const config = { 58 | credentials: credentials.aws, 59 | region: 'us-east-1', 60 | }; 61 | const lambda = new AWS.Lambda(config); 62 | 63 | return lambda 64 | .getFunctionConfiguration({ 65 | FunctionName: lambdaName, 66 | }) 67 | .promise(); 68 | }; 69 | 70 | module.exports = { sleep, generateId, getCredentials, getServerlessSdk, getLambda }; 71 | --------------------------------------------------------------------------------