├── .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 | [](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 |
--------------------------------------------------------------------------------