├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── issue_template.md └── workflows │ ├── auto-approve-deploys.yml │ ├── build.yml │ ├── pr-auto-merge-dependabot.yml │ └── pr-auto-merge-labeler.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .nycrc.yaml ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── clean.sh ├── examples ├── basic │ ├── .npmrc │ ├── README.md │ ├── data-files │ │ ├── glyphicons-halflings-regular.woff2 │ │ ├── index.html │ │ ├── jpg.jpg │ │ ├── png.png │ │ └── subdir │ │ │ └── png.png │ ├── handler.js │ ├── package.json │ └── serverless.yml └── serverless-offline │ ├── .npmrc │ ├── README.md │ ├── data-files │ ├── glyphicons-halflings-regular.woff2 │ ├── index.html │ ├── jpg.jpg │ ├── png.png │ └── subdir │ │ └── png.png │ ├── handler.js │ ├── package.json │ └── serverless.yml ├── greenkeeper.json ├── package-lock.json ├── package.json ├── plugins └── BinaryMediaTypes.js ├── pre-commit.sh ├── release.config.js ├── scripts ├── approve-dependabot-deploys │ ├── .gitignore │ ├── README.md │ ├── approve-dependabot-deploys.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── aws-oidc-role-cloudformation-template.yaml └── aws-oidc-role-provision.sh ├── src ├── StaticFileHandler.js ├── plugins │ └── BinaryMediaTypes.js └── test │ ├── BinaryMediaTypes.js │ ├── StaticFileHandler.js │ ├── data │ └── testfiles │ │ ├── README.md │ │ ├── blah.bin │ │ ├── custom-error.html │ │ ├── fonts │ │ └── glyphicons-halflings-regular.woff2 │ │ ├── index.html │ │ ├── jpg.jpg │ │ ├── png.png │ │ ├── unknown-mime-type.unknowntype │ │ └── vendor │ │ ├── output.css.map │ │ └── output.js.map │ └── e2e.js └── test-files ├── basic-project ├── .gitignore ├── .npmrc ├── README.md ├── data-files │ ├── index.html │ └── png.png ├── handler.js ├── package.json └── serverless.yml ├── scripts ├── test-http.sh └── test-local-e2e.sh └── webpack-project ├── .babelrc ├── .gitignore ├── README.md ├── data-files ├── index.html └── png.png ├── event.json ├── handler.js ├── package-lock.json ├── package.json ├── serverless.yml └── webpack.config.js /.github/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 contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at serverless-aws-static-file-handler@willeke.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to this project. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | ## How Can I Contribute? 8 | 9 | Feel free to check [issues page](https://github.com/activescott/serverless-aws-static-file-handler/issues) to find an enhancement to implement or bug to fix. You could also **[Improve the documentation](https://github.com/activescott/serverless-aws-static-file-handler/edit/master/README.md)**, **Report a Bug**, or **Suggest an Enhancement**. 10 | 11 | ### Contribute Code 12 | 13 | - Fork this repository and [submit a pull request](https://help.github.com/articles/creating-a-pull-request/). 14 | - Write tests 15 | - Ensure the linter passes with `yarn lint` or `npm run lint` 16 | - Ensure the tests pass at the CI Server by [following the status](https://help.github.com/articles/about-statuses/) of your pull request. 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What did you implement: 2 | 3 | Closes #XXXXX 4 | 5 | 8 | 9 | ## How did you implement it: 10 | 11 | 14 | 15 | ## How can we verify it: 16 | 17 | 27 | 28 | ## Todos: 29 | 30 | - [ ] Write tests for any changed code. See tests at https://github.com/activescott/serverless-aws-static-file-handler/tree/main/src/test for some examples. 31 | - [ ] All tests pass at the CI Server by [following the status](https://help.github.com/articles/about-statuses/) of your pull request. 32 | 33 | If you need a hand with anything at all tag [activescott](https://github.com/activescott/) in a comment and ask! 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # docs v2 https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates 4 | - package-ecosystem: "npm" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | # Check for npm updates on Sundays 9 | day: "saturday" 10 | allow: 11 | - dependency-type: "production" 12 | commit-message: 13 | # for production deps, prefix commit messages with "fix" (trigger a patch release) 14 | prefix: "fix" 15 | # for development deps, prefix commit messages with "chore" (do NOT trigger an npm release) 16 | prefix-development: "chore" 17 | include: "scope" 18 | 19 | - package-ecosystem: "npm" 20 | directory: "/test-files/basic-project" 21 | schedule: 22 | interval: "monthly" 23 | # Check for npm updates on Sundays 24 | day: "saturday" 25 | commit-message: 26 | # since these are tests/samples, we'll not require a release of the package 27 | prefix: "chore" 28 | allow: 29 | # Allow only direct updates for all packages 30 | - dependency-type: "direct" 31 | 32 | - package-ecosystem: "npm" 33 | directory: "/test-files/webpack-project" 34 | schedule: 35 | interval: "monthly" 36 | # Check for npm updates on Sundays 37 | day: "saturday" 38 | commit-message: 39 | # since these are tests/samples, we'll not require a release of the package 40 | prefix: "chore" 41 | allow: 42 | # Allow only direct updates for all packages 43 | - dependency-type: "direct" 44 | 45 | - package-ecosystem: "npm" 46 | directory: "/examples/basic" 47 | schedule: 48 | interval: "monthly" 49 | # Check for npm updates on Sundays 50 | day: "saturday" 51 | commit-message: 52 | # since these are tests/samples, we'll not require a release of the package 53 | prefix: "chore" 54 | allow: 55 | # Allow only direct updates for all packages 56 | - dependency-type: "direct" 57 | 58 | - package-ecosystem: "npm" 59 | directory: "/examples/serverless-offline" 60 | schedule: 61 | interval: "monthly" 62 | # Check for npm updates on Sundays 63 | day: "saturday" 64 | commit-message: 65 | # since these are tests/samples, we'll not require a release of the package 66 | prefix: "chore" 67 | allow: 68 | # Allow only direct updates for all packages 69 | - dependency-type: "direct" 70 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior: 2 | 3 | ### Actual behavior: 4 | 5 | ### Steps to reproduce the problem: 6 | 7 | ### Environment: 8 | 9 | Please include version of serverless-aws-static-file-handler, version of serverless, version of node, and operating system name and version 10 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve-deploys.yml: -------------------------------------------------------------------------------- 1 | name: Deployment Auto-Approver 2 | # using triggers for every deployment and allowed manually 3 | # docs on these triggers: 4 | on: 5 | # allows manually triggering (see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) 6 | workflow_dispatch: 7 | schedule: 8 | # 08:30 UTC daily (see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule) 9 | - cron: "30 08 * * *" 10 | # the only problem with the 'deployment' trigger is that dependabot PRs are considered from forked repos and the token doesn't have permission to approve (see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#deployment) 11 | # deployment: 12 | 13 | jobs: 14 | auto_approve: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Auto Approve Deploys 18 | uses: activescott/automate-environment-deployment-approval@main 19 | with: 20 | github_token: ${{ secrets.GH_TOKEN_FOR_AUTO_APPROVING_DEPLOYS }} 21 | environment_allow_list: | 22 | aws 23 | # e.g. "dependabot[bot]" 24 | actor_allow_list: | 25 | dependabot[bot] 26 | activescott 27 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | # We want pushes to main, beta, and next to trigger a publish to npm for the corresponding npm dist-tag. 4 | # NOTE: semantic-release detects pull requests and won't deploy on them so we don't have to deal with that complexity here. 5 | # Any pull request should run all tests. 6 | # NOTE: e2e tests use shared resource (AWS) and if they run concurrently they step on one another and cause errors. 7 | # This can happen for beta & next branches if a PR targeting main also exists for them. There is some logic in e2e_tests job to mitigate this. 8 | on: 9 | push: 10 | branches: [main, beta, next] 11 | pull_request: 12 | branches: [main] 13 | 14 | # Allow one concurrent build for the same branch 15 | concurrency: 16 | group: current-branch-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | unit_tests: 21 | runs-on: ubuntu-20.04 22 | strategy: 23 | matrix: 24 | node: [20, 22] 25 | 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Use Node.js ${{ matrix.node }} 29 | uses: actions/setup-node@v2 30 | with: 31 | node-version: ${{ matrix.node }} 32 | cache: "npm" 33 | 34 | - name: install dependencies 35 | run: | 36 | npm i 37 | 38 | - name: test 39 | env: 40 | CI_NODE_VERSION: ${{ matrix.node }} 41 | run: | 42 | npm run test 43 | 44 | - name: publish coverage 45 | uses: coverallsapp/github-action@master 46 | continue-on-error: true 47 | with: 48 | github-token: ${{ secrets.GITHUB_TOKEN }} 49 | flag-name: nodejs-${{ matrix.node }} 50 | parallel: true 51 | 52 | update_code_coverage: 53 | needs: unit_tests 54 | runs-on: ubuntu-20.04 55 | steps: 56 | - name: Coveralls Finished 57 | uses: coverallsapp/github-action@master 58 | continue-on-error: true 59 | with: 60 | github-token: ${{ secrets.GITHUB_TOKEN }} 61 | parallel-finished: true 62 | 63 | e2e_tests: 64 | # Allow only one concurrent deployment for the target branch: 65 | ## no reason to run the long-running AWS e2e tests until we are sure this one will be viable 66 | ## NOTE: we could separate e2e_tests job into a e2e_tests_local and e2e_tests_remote since the local ones are quite a lot faster 67 | concurrency: 68 | group: target-branch-${{ github.base_ref }} 69 | 70 | permissions: 71 | # NOTE: If you specify the access for any of these scopes, all of those that are not specified are set to none. 72 | # for AWS OIDC Token access per https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#updating-your-github-actions-workflow 73 | id-token: write 74 | contents: read 75 | deployments: read 76 | needs: unit_tests 77 | runs-on: ubuntu-20.04 78 | environment: aws 79 | env: 80 | # e2e tests use a shared resource in AWS. Since we trigger on both a push to beta/next and a PR with destination branch of main, a PR against beta/next will cause them to run concurrently and causes errors. To prevent this contention we use a serverless stage. 81 | # For more information on serverless stages: 82 | # - https://www.serverless.com/framework/docs/providers/aws/cli-reference/deploy/ 83 | # - https://www.serverless.com/framework/docs/providers/aws/guide/deploying#tips 84 | # github.run_id: A unique number for each run within a repository. This number does not change if you re-run the workflow run. https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#github-context 85 | # github format: https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#format 86 | SERVERLESS_STAGE: ${{ github.event_name == 'pull_request' && format('pr{0}', github.run_id) || format('push{0}', github.run_id) }} 87 | steps: 88 | - uses: actions/checkout@v3 89 | 90 | - name: Use Node.js 91 | uses: actions/setup-node@v1 92 | with: 93 | # NOTE: in v13.0, serverless-offline removed support for node v14: https://github.com/dherault/serverless-offline/releases/tag/v13.0.0 94 | node-version: 20 95 | 96 | - name: build plugin locally 97 | run: | 98 | cd "$GITHUB_WORKSPACE" 99 | npm i 100 | npm pack 101 | 102 | - name: run local end-to-end test in serverless-offline 103 | run: | 104 | "$GITHUB_WORKSPACE/test-files/scripts/test-local-e2e.sh" 105 | 106 | - name: configure aws credentials 107 | # Configures AWS credential and region environment variables for use in other GitHub Actions. 108 | # The environment variables will be detected by both the AWS SDKs and the AWS CLI to determine the credentials and region to use for AWS API calls. 109 | # More on this action at https://github.com/aws-actions/configure-aws-credentials 110 | uses: aws-actions/configure-aws-credentials@v1 111 | with: 112 | role-to-assume: arn:aws:iam::166901232151:role/serverless-aws-static-file-handler-at-github 113 | aws-region: us-west-2 114 | 115 | - name: prepare to run remote end-to-end test 116 | run: | 117 | cd "$GITHUB_WORKSPACE/examples/basic" 118 | npm i 119 | # update to use the local plugin built above 120 | npm install --save file://../../serverless-aws-static-file-handler-0.0.0.tgz 121 | 122 | - name: run remote end-to-end test 123 | run: | 124 | cd "$GITHUB_WORKSPACE/examples/basic" 125 | echo "Deploying serverless stage $SERVERLESS_STAGE" 126 | ./node_modules/.bin/serverless deploy --stage $SERVERLESS_STAGE 127 | 128 | # get the APIG endpoint URL: 129 | APIG_URL=$(./node_modules/.bin/serverless info --stage $SERVERLESS_STAGE | sed -nr "s#^.*(https://.+/$SERVERLESS_STAGE)\$#\1#p") 130 | echo "Discovered APIG_URL: $APIG_URL" 131 | 132 | # CURL to some known good endpoints expecting 200: 133 | TEST_HTTP_EXEC=$GITHUB_WORKSPACE/test-files/scripts/test-http.sh 134 | ROOT_URL=$APIG_URL 135 | 136 | # 200; these all should succeed 137 | $TEST_HTTP_EXEC $ROOT_URL/binary/png.png 138 | $TEST_HTTP_EXEC $ROOT_URL/binary/jpg.jpg 139 | $TEST_HTTP_EXEC $ROOT_URL/binary/glyphicons-halflings-regular.woff2 140 | $TEST_HTTP_EXEC $ROOT_URL/binary/subdir/png.png 141 | 142 | # 403 143 | $TEST_HTTP_EXEC "$ROOT_URL/ff404.png" 403 144 | $TEST_HTTP_EXEC "$ROOT_URL/jpeg404.jpg" 403 145 | $TEST_HTTP_EXEC "$ROOT_URL/subdir404/ff.png" 403 146 | $TEST_HTTP_EXEC "$ROOT_URL/subdir/ff404.png" 403 147 | 148 | # 404 149 | $TEST_HTTP_EXEC "$ROOT_URL/binary/404-glyphicons-halflings-regular.woff2" 404 150 | $TEST_HTTP_EXEC "$ROOT_URL/binary/subdir/404-png.png" 404 151 | 152 | - name: cleanup remote end-to-end test (destroy serverless stack) 153 | # Run this step even if the prior one failed (to clean up) 154 | if: ${{ always() }} 155 | run: | 156 | cd "$GITHUB_WORKSPACE/examples/basic" 157 | echo "Destroying serverless stage $SERVERLESS_STAGE" 158 | ./node_modules/.bin/serverless remove --stage $SERVERLESS_STAGE 159 | 160 | publish_package: 161 | if: ${{ github.event_name != 'pull_request' }} 162 | needs: [e2e_tests, unit_tests] 163 | runs-on: ubuntu-20.04 164 | environment: npm 165 | steps: 166 | - uses: actions/checkout@v3 167 | 168 | - name: Use Node.js 169 | uses: actions/setup-node@v1 170 | with: 171 | node-version: 20 172 | 173 | #- name: debug publish_package 174 | # uses: actions/bin/debug@master 175 | 176 | - name: publish to npm 177 | env: 178 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 179 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 180 | run: | 181 | npm install 182 | npx semantic-release@17 183 | -------------------------------------------------------------------------------- /.github/workflows/pr-auto-merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: Auto-Merge 2 | on: 3 | pull_request_target: 4 | types: [labeled] 5 | 6 | jobs: 7 | enable-auto-merge: 8 | runs-on: ubuntu-latest 9 | 10 | # Specifically check that dependabot (or another trusted party) created this pull-request, and that it has been labelled correctly. 11 | if: github.event.pull_request.user.login == 'dependabot[bot]' && contains(github.event.pull_request.labels.*.name, 'dependencies') 12 | steps: 13 | - uses: alexwilson/enable-github-automerge-action@f4f9509cc5024102ac8d52d1c1d2d8e194afbbb3 14 | with: 15 | github-token: "${{ secrets.GITHUB_TOKEN }}" 16 | -------------------------------------------------------------------------------- /.github/workflows/pr-auto-merge-labeler.yml: -------------------------------------------------------------------------------- 1 | name: Label PR with Auto-Merge Status 2 | on: 3 | pull_request_target: 4 | types: [auto_merge_enabled, auto_merge_disabled] 5 | 6 | jobs: 7 | label: 8 | uses: activescott/github-actions-workflows/.github/workflows/pr-auto-merge-labeler.yml@pr-auto-merge-labeler-v1 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | examples/basic/.serverless 61 | 62 | .vscode/ 63 | .idea/ 64 | 65 | /junk.js 66 | /todo.md 67 | 68 | .serverless/ 69 | 70 | .DS_Store -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm test 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/test/ 2 | .prettierignore 3 | yarn.lock 4 | .nycrc.yaml 5 | -------------------------------------------------------------------------------- /.nycrc.yaml: -------------------------------------------------------------------------------- 1 | reporter: 2 | - lcov 3 | - html 4 | check-coverage: true 5 | branches: 90 6 | lines: 90 7 | functions: 90 8 | statements: 90 9 | all: true 10 | exclude: 11 | - src/test/ 12 | - coverage/ 13 | - examples/ 14 | - release.config.js 15 | - test-files/ 16 | - scripts/ 17 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | ./.vscode/ 2 | ./examples/basic/.serverless/ 3 | .nyc_output/ 4 | coverage/ 5 | node_modules/ 6 | .serverless/ 7 | .webpack/ 8 | scripts/approve-dependabot-deploys/responses/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 scott willeke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/serverless-aws-static-file-handler.svg)](https://www.npmjs.com/package/serverless-aws-static-file-handler) 2 | [![npm](https://img.shields.io/npm/dt/serverless-aws-static-file-handler.svg?logo=npm)](https://www.npmjs.com/package/serverless-aws-static-file-handler) 3 | [![Build Status](https://github.com/activescott/serverless-aws-static-file-handler/workflows/build/badge.svg)](https://github.com/activescott/serverless-aws-static-file-handler/actions) 4 | [![Coverage Status](https://coveralls.io/repos/github/activescott/serverless-aws-static-file-handler/badge.svg)](https://coveralls.io/github/activescott/serverless-aws-static-file-handler) 5 | [![License](https://img.shields.io/github/license/activescott/serverless-aws-static-file-handler.svg)](https://github.com/activescott/serverless-aws-static-file-handler/blob/master/LICENSE) 6 | [![GitHub stars](https://img.shields.io/github/stars/activescott/serverless-aws-static-file-handler.svg?style=social)](https://github.com/activescott/serverless-aws-static-file-handler) 7 | 8 | # serverless-aws-static-file-handler 9 | 10 | Host the front-end of your web applications on [Serverless framework](https://github.com/serverless/serverless) on AWS Lambda right alongside the API. 11 | 12 | It is a fast and easy way to get started and makes it trivial to deploy your web applications. If you need better response time in the future and get concerned about AWS costs of using Lambda to static content, you put CloudFront in front of your Serverless endpoints service static content. 13 | 14 | 15 | 16 | - [Usage / Quick Start](#usage--quick-start) 17 | - [Prerequisites / Usage Requirements](#prerequisites--usage-requirements) 18 | - [Install](#install) 19 | - [Features](#features) 20 | - [Contributing 🤝](#contributing-🤝) 21 | - [Show your support](#show-your-support) 22 | - [Release Process (Deploying to NPM)](#release-process-deploying-to-npm) 23 | - [License 📝](#license-📝) 24 | 25 | 26 | 27 | ## Usage / Quick Start 28 | 29 | Import & initialize: 30 | 31 | const StaticFileHandler = require('serverless-aws-static-file-handler') 32 | 33 | # configure where to serve files from: 34 | const clientFilesPath = path.join(__dirname, "./data-files/") 35 | const fileHandler = new StaticFileHandler(clientFilesPath) 36 | 37 | Define a handler in your code as follows: 38 | 39 | module.exports.html = async (event, context) => { 40 | event.path = "index.html" // forcing a specific page for this handler, ignore requested path. This would serve ./data-files/index.html 41 | return fileHandler.get(event, context) 42 | } 43 | 44 | In your `serverless.yml` file, reference the handler function from above to provide routes to your static files: 45 | 46 | functions: 47 | html: 48 | handler: handler.html 49 | events: 50 | - http: 51 | path: / 52 | method: get 53 | 54 | # Note Binary files work too! See configuration information below 55 | png: 56 | handler: handler.png 57 | events: 58 | - http: 59 | path: png 60 | method: get 61 | 62 | # The following example uses a path placeholder to serve all files directly in the /binary/ directory: 63 | binary: 64 | handler: handler.binary 65 | events: 66 | - http: 67 | path: /binary/{pathvar+} 68 | method: get 69 | 70 | To serve binary content make sure that you setup the plugin in your serverless.yml like so: 71 | 72 | plugins: 73 | - serverless-aws-static-file-handler/plugins/BinaryMediaTypes 74 | 75 | custom: 76 | apiGateway: 77 | binaryMediaTypes: 78 | - "image/png" 79 | - "image/jpeg" 80 | 81 | Some additional real-world examples are demonstrated in the [examples/basic/](examples/basic) directory as well as a [serverless-offline](https://github.com/dherault/serverless-offline)-specific example in [examples/serverless-offline/](examples/serverless-offline). 82 | 83 | ## Prerequisites / Usage Requirements 84 | 85 | Requires Node.js latest, LTS, or v10 ([tested](https://github.com/activescott/serverless-aws-static-file-handler/actions)). 86 | 87 | Requires Serverless Framework v1.x. 88 | If you are new to the Serverless Framework, check out the [Serverless Framework Getting Started Guide](https://serverless.com/framework/docs/getting-started/). 89 | 90 | ## Install 91 | 92 | Install with yarn (`yarn add serverless-aws-static-file-handler`) or npm (`npm install serverless-aws-static-file-handler --save-prod`) 93 | 94 | ## Features 95 | 96 | - Simple to get started 97 | - Works with **text files** such as HTML or **binary** files such as images or fonts 98 | 99 | ## Contributing 🤝 100 | 101 | This is a community project. We invite your participation through issues and pull requests! You can peruse the [contributing guidelines](.github/CONTRIBUTING.md). 102 | 103 | ## Show your support 104 | 105 | Give a ⭐️ if this project helped you! 106 | 107 | ## Release Process (Deploying to NPM) 108 | 109 | We use [semantic-release](https://github.com/semantic-release/semantic-release) to consistently release [semver](https://semver.org/)-compatible versions. This project deploys to multiple [npm distribution tags](https://docs.npmjs.com/cli/dist-tag). Each of the below branches correspond to the following npm distribution tags: 110 | 111 | | branch | npm distribution tag | 112 | | ------ | -------------------- | 113 | | main | latest | 114 | | beta | beta | 115 | 116 | To trigger a release use a Conventional Commit following [Angular Commit Message Conventions](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) on one of the above branches. 117 | 118 | ## License 📝 119 | 120 | Copyright © 2017 [scott@willeke.com](https://github.com/activescott). 121 | 122 | This project is [MIT](https://github.com/activescott/serverless-http-invoker/blob/master/LICENSE) licensed. 123 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | THISDIR=$(cd $(dirname "$0"); pwd) #this script's directory 3 | THISSCRIPT=$(basename $0) 4 | 5 | git clean -f -d -x "$THISDIR" 6 | -------------------------------------------------------------------------------- /examples/basic/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | To see this example take the following steps at the shell in the root of this example's folder: 2 | 3 | 1. Run `npm install` to install dependencies 4 | 2. Run `npm run deploy` to deploy to AWS via Serverless Framework 5 | 3. Open the root endpoint displayed by Serverless Framework after the successful deployment (something like https://d3cafbad.execute-api.us-east-1.amazonaws.com/dev/) 6 | -------------------------------------------------------------------------------- /examples/basic/data-files/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/basic/data-files/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /examples/basic/data-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 20 |
21 | These should all load and either be a valid image or a font: 22 |
23 | 24 |
25 | These each have a path that does not have a route (http event) 26 | setup in serverless.yml. They should all be returning 27 | 403 OR 28 | 404: Not Found (API Gateway in prod reports 29 | them as 403, but serverless-offline reports them as 404): 30 |
31 | 32 |
33 | These each have a valid route (http event) setup in 34 | serverless.yml, but the file doesn't exist on disk. They should all be 35 | returning 404: Not Found: 36 |
37 | 38 | 158 | 159 | -------------------------------------------------------------------------------- /examples/basic/data-files/jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/basic/data-files/jpg.jpg -------------------------------------------------------------------------------- /examples/basic/data-files/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/basic/data-files/png.png -------------------------------------------------------------------------------- /examples/basic/data-files/subdir/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/basic/data-files/subdir/png.png -------------------------------------------------------------------------------- /examples/basic/handler.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | const path = require("path") 3 | const StaticFileHandler = require("serverless-aws-static-file-handler") 4 | const clientFilesPath = path.join(__dirname, "./data-files/") 5 | const fileHandler = new StaticFileHandler(clientFilesPath) 6 | 7 | module.exports.root = async (event, context) => { 8 | event.path = "index.html" // forcing a specific page for this handler; ignore requested path 9 | return fileHandler.get(event, context) 10 | } 11 | 12 | module.exports.v2_root = async (event, context) => { 13 | event.rawPath = "index.html" // forcing a specific page for this handler; ignore requested path 14 | return fileHandler.get(event, context) 15 | } 16 | 17 | module.exports.binary = async (event, context) => { 18 | if (!event.path.startsWith("/binary/")) { 19 | throw new Error(`[404] Invalid filepath for this resource`) 20 | } 21 | return fileHandler.get(event, context) 22 | } 23 | 24 | module.exports.v2_binary = async (event, context) => { 25 | if (!event.rawPath.startsWith("/v2/binary/")) { 26 | throw new Error(`[404] Invalid filepath for this resource`) 27 | } 28 | return fileHandler.get(event, context) 29 | } 30 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-file-handler-demo-proxy", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "deploy": "serverless deploy --aws-profile serverless", 9 | "destroy": "serverless remove --aws-profile serverless", 10 | "lint": "prettier --write \"./**/*.{js,md,yml,json,html}\"", 11 | "reset": "rm -rfd ./node_modules/ && npm i && npm run deploy", 12 | "//dev-link": "Useful for debugging but don't commit it in this way; use `npm run dev-unlink` before committing. NOTE: Not using `npm link` or `npm add ../..` because that creates a filesystem symlink and serverless.com freaks out when attempting a deploy with a symlinked dependency.", 13 | "dev-link": "pushd . ; cd ../.. ; npm pack ; popd ; npm add ../../serverless-aws-static-file-handler-0.0.0.tgz", 14 | "dev-unlink": "npm rm serverless-aws-static-file-handler ; npm add serverless-aws-static-file-handler@>=3.0.2-beta.1" 15 | }, 16 | "devDependencies": { 17 | "serverless": "^3.37.0" 18 | }, 19 | "dependencies": { 20 | "serverless-aws-static-file-handler": ">=3.0.2-beta.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/basic/serverless.yml: -------------------------------------------------------------------------------- 1 | service: static-file-handler-demo 2 | 3 | plugins: 4 | - serverless-aws-static-file-handler/plugins/BinaryMediaTypes 5 | 6 | custom: 7 | apiGateway: 8 | binaryMediaTypes: 9 | # You can use the wildcard character (*) to cover multiple media types per https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html 10 | # NOTE: Using */* has a side effect as noted at https://github.com/activescott/serverless-aws-static-file-handler 11 | # IANA descrete type wildcards from: https://www.iana.org/assignments/media-types/media-types.xhtml 12 | - application/* 13 | - audio/* 14 | - font/* 15 | - image/* 16 | - video/* 17 | 18 | provider: 19 | name: aws 20 | runtime: nodejs20.x 21 | lambdaHashingVersion: 20201221 22 | 23 | functions: 24 | html: 25 | handler: handler.root 26 | events: 27 | - http: 28 | path: / 29 | method: get 30 | 31 | binary: 32 | handler: handler.binary 33 | events: 34 | - http: 35 | path: /binary/{pathvar+} 36 | method: get 37 | 38 | # API Gateway V2 Payload or HTTP API are below. As described at https://www.serverless.com/framework/docs/providers/aws/events/http-api 39 | v2_html: 40 | handler: handler.v2_root 41 | events: 42 | - httpApi: 43 | path: /v2 44 | method: get 45 | 46 | v2_binary: 47 | handler: handler.v2_binary 48 | events: 49 | - httpApi: 50 | path: /v2/binary/{pathvar+} 51 | method: get 52 | -------------------------------------------------------------------------------- /examples/serverless-offline/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /examples/serverless-offline/README.md: -------------------------------------------------------------------------------- 1 | To see this example take the following steps at the shell in the root of this example's folder: 2 | 3 | 1. Run `npm install` to install dependencies 4 | 2. Run `./node_modules/.bin/serverless offline start` to start serverless-offline running locally. 5 | 3. Open the root endpoint displayed by serverless-offline it successfully starts (usually http://localhost:3000) 6 | -------------------------------------------------------------------------------- /examples/serverless-offline/data-files/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/serverless-offline/data-files/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /examples/serverless-offline/data-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 20 |
21 | These should all load and either be a valid image or a font: 22 |
23 | 24 |
25 | These each have a path that does not have a route (http event) 26 | setup in serverless.yml. They should all be returning 27 | 403 OR 28 | 404: Not Found (API Gateway in prod reports 29 | them as 403, but serverless-offline reports them as 404): 30 |
31 | 32 |
33 | These each have a valid route (http event) setup in 34 | serverless.yml, but the file doesn't exist on disk. They should all be 35 | returning 404: Not Found: 36 |
37 | 38 | 160 | 161 | -------------------------------------------------------------------------------- /examples/serverless-offline/data-files/jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/serverless-offline/data-files/jpg.jpg -------------------------------------------------------------------------------- /examples/serverless-offline/data-files/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/serverless-offline/data-files/png.png -------------------------------------------------------------------------------- /examples/serverless-offline/data-files/subdir/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/examples/serverless-offline/data-files/subdir/png.png -------------------------------------------------------------------------------- /examples/serverless-offline/handler.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | const path = require("path") 3 | const StaticFileHandler = require("serverless-aws-static-file-handler") 4 | const clientFilesPath = path.join(__dirname, "./data-files/") 5 | const fileHandler = new StaticFileHandler(clientFilesPath) 6 | 7 | module.exports.root = async (event, context) => { 8 | event.path = "index.html" // forcing a specific page for this handler; ignore requested path 9 | return fileHandler.get(event, context) 10 | } 11 | 12 | module.exports.binary = async (event, context) => { 13 | if (!event.path.startsWith("/binary/")) { 14 | throw new Error(`[404] Invalid filepath for this resource: ${fname}`) 15 | } 16 | return fileHandler.get(event, context) 17 | } 18 | -------------------------------------------------------------------------------- /examples/serverless-offline/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-file-handler-demo-offline", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "deploy": "serverless deploy --aws-profile serverless", 9 | "destroy": "serverless remove --aws-profile serverless", 10 | "reset": "rm -rfd ./node_modules/ && npm i && npm run deploy", 11 | "offline": "serverless offline", 12 | "dev-link": "npm link ../../", 13 | "dev-unlink": "npm i" 14 | }, 15 | "devDependencies": { 16 | "serverless": "^3.37.0", 17 | "serverless-offline": "^13.3.0" 18 | }, 19 | "dependencies": { 20 | "serverless-aws-static-file-handler": ">=3.0.2-beta.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/serverless-offline/serverless.yml: -------------------------------------------------------------------------------- 1 | service: static-file-handler-demo-offline 2 | 3 | plugins: 4 | - serverless-offline 5 | - serverless-aws-static-file-handler/plugins/BinaryMediaTypes 6 | 7 | custom: 8 | apiGateway: 9 | binaryMediaTypes: 10 | # You can use the wildcard character (*) to cover multiple media types per https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html 11 | # NOTE: Using */* has a side effect as noted at https://github.com/activescott/serverless-aws-static-file-handler 12 | # IANA descrete type wildcards from: https://www.iana.org/assignments/media-types/media-types.xhtml 13 | - application/* 14 | - audio/* 15 | - font/* 16 | - image/* 17 | - video/* 18 | 19 | provider: 20 | name: aws 21 | runtime: nodejs20.x 22 | lambdaHashingVersion: 20201221 23 | 24 | functions: 25 | html: 26 | handler: handler.root 27 | events: 28 | - http: 29 | path: / 30 | method: get 31 | 32 | binary: 33 | handler: handler.binary 34 | events: 35 | - http: 36 | path: /binary/{pathvar+} 37 | method: get 38 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "examples/basic/package.json", 6 | "examples/serverless-offline/package.json", 7 | "package.json" 8 | ] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-aws-static-file-handler", 3 | "description": "Easily serve static files with the Serverless Framework on AWS Lambda.", 4 | "homepage": "https://github.com/activescott/serverless-aws-static-file-handler#readme", 5 | "version": "0.0.0", 6 | "main": "src/StaticFileHandler.js", 7 | "author": { 8 | "name": "Scott Willeke", 9 | "email": "scott@willeke.com", 10 | "url": "https://scott.willeke.com/" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/activescott/serverless-aws-static-file-handler.git" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/activescott/serverless-aws-static-file-handler/issues" 18 | }, 19 | "keywords": [ 20 | "aws-lambda", 21 | "binary", 22 | "nodejs", 23 | "serverless", 24 | "serverless-architectures", 25 | "serverless-framework", 26 | "serverless-functions" 27 | ], 28 | "dependencies": { 29 | "mime-types": "^3.0.1", 30 | "mustache": "^4.0.0" 31 | }, 32 | "devDependencies": { 33 | "chai": "^4.1.2", 34 | "chai-as-promised": "^7.1.1", 35 | "husky": "^7.0.0", 36 | "js-yaml": "^4.0.0", 37 | "mocha": "^11.1.0", 38 | "nyc": "^15.0.0", 39 | "prettier": "^2.0.0", 40 | "serverless": "^3.37.0", 41 | "sinon": "^11.1.2" 42 | }, 43 | "engines": { 44 | "node": ">=20" 45 | }, 46 | "files": [ 47 | "src/error.html", 48 | "src/plugins/BinaryMediaTypes.js", 49 | "src/StaticFileHandler.js", 50 | "plugins/BinaryMediaTypes.js" 51 | ], 52 | "scripts": { 53 | "test": "nyc ./node_modules/.bin/mocha './src/test/*.js'", 54 | "pretest": "npm run lint && npm version --allow-same-version --no-git-tag-version 0.0.0 && npm pack", 55 | "lint": "[ \"$CI_NODE_VERSION\" = \"8\" ] || prettier -l \"**/*.{js,md,yml,json,html}\"", 56 | "lint-fix": "prettier --write \"**/*.{js,md,yml,yaml,json,html}\"", 57 | "prepare": "husky install" 58 | }, 59 | "license": "MIT" 60 | } 61 | -------------------------------------------------------------------------------- /plugins/BinaryMediaTypes.js: -------------------------------------------------------------------------------- 1 | const BinaryMediaTypes = require("../src/plugins/BinaryMediaTypes") 2 | 3 | module.exports = BinaryMediaTypes 4 | -------------------------------------------------------------------------------- /pre-commit.sh: -------------------------------------------------------------------------------- 1 | # RUN `ln -sf ../../pre-commit.sh .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit` to set this up as the git pre-commit hook 2 | 3 | die () { 4 | echo "" 5 | echo "git pre-commit hook FAILED! See above for details." 6 | echo "" 7 | popd 8 | exit $@ 9 | } 10 | 11 | pushd . 12 | cd src 13 | npm run lint || die $? 14 | npm run test || die $? 15 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://github.com/semantic-release/semantic-release/blob/beta/docs/usage/configuration.md 3 | branches: [ 4 | "+([0-9])?(.{+([0-9]),x}).x", 5 | "main", 6 | "next", 7 | "next-major", 8 | { name: "beta", prerelease: true }, 9 | { name: "alpha", prerelease: true }, 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /scripts/approve-dependabot-deploys/.gitignore: -------------------------------------------------------------------------------- 1 | responses/ -------------------------------------------------------------------------------- /scripts/approve-dependabot-deploys/README.md: -------------------------------------------------------------------------------- 1 | # approve-dependabot-deploys 2 | 3 | A script to automatically approve deployment requests from dependabot to the aws environment. 4 | -------------------------------------------------------------------------------- /scripts/approve-dependabot-deploys/approve-dependabot-deploys.ts: -------------------------------------------------------------------------------- 1 | // https://docs.github.com/en/rest/actions/workflow-runs#list-workflow-runs-for-a-repository 2 | // https://docs.github.com/en/rest/actions/workflow-runs#review-pending-deployments-for-a-workflow-run 3 | // https://docs.github.com/en/rest/deployments/statuses 4 | // https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#status 5 | // https://github.com/octokit/core.js 6 | 7 | import { writeFile, mkdir} from "node:fs/promises" 8 | import { Octokit } from "octokit" 9 | import { Endpoints } from "@octokit/types" 10 | import * as dotenv from "dotenv" 11 | dotenv.config() 12 | 13 | if (!process.env.GITHUB_TOKEN) { 14 | throw new Error("You must have the `GITHUB_TOKEN` environment variable specified with a github personal access token.") 15 | } 16 | 17 | const baseOptions = { 18 | owner: "activescott", 19 | repo: "serverless-aws-static-file-handler", 20 | } 21 | 22 | const octokit = new Octokit({ 23 | auth: process.env.GITHUB_TOKEN, 24 | }) 25 | 26 | async function dumpResponse(method: "GET" | "POST" | "PUT", pathAfterRepo: string, response: Promise): Promise { 27 | pathAfterRepo = pathAfterRepo.startsWith("/") ? pathAfterRepo : "/" + pathAfterRepo 28 | pathAfterRepo = pathAfterRepo.replace(/\//g, "-") 29 | await mkdir("./responses", { recursive: true }) 30 | await writeFile( 31 | `./responses/${method}-repos-${baseOptions.owner}-${baseOptions.repo}${pathAfterRepo}.json`, 32 | JSON.stringify(await response, null, " ") 33 | ) 34 | } 35 | 36 | async function getWaitingWorkflowRuns(): Promise { 37 | const runs = octokit.request("GET /repos/{owner}/{repo}/actions/runs", { 38 | ...baseOptions, 39 | status: "waiting", 40 | }) 41 | await dumpResponse("GET", `actions/runs`, runs) 42 | return runs 43 | } 44 | 45 | async function getPendingDeploymentsForRun(run_id: number) { 46 | const deploys = octokit.request( 47 | "GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments", 48 | { 49 | ...baseOptions, 50 | run_id: run_id, 51 | } 52 | ) 53 | await dumpResponse("GET", `actions/runs/${run_id}/pending_deployments`, deploys) 54 | return deploys 55 | } 56 | 57 | async function approveDeployment(run, environment) { 58 | console.log( 59 | `Approving deployment to ${environment.name} triggered by ${run.actor.login} for run ${run.display_title}...` 60 | ) 61 | await octokit.request( 62 | "POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments", 63 | { 64 | ...baseOptions, 65 | run_id: run.id, 66 | environment_ids: [environment.id], 67 | state: "approved", 68 | comment: "approved by approve-dependabot-deploys script", 69 | } 70 | ) 71 | console.log( 72 | `SUCCESS approving deployment to ${environment.name} for run ${run.display_title}.` 73 | ) 74 | } 75 | 76 | const waitingRunsResponse = await getWaitingWorkflowRuns() 77 | const runs = waitingRunsResponse.data.workflow_runs 78 | 79 | const deploysToApprove = runs 80 | .map(async (run) => { 81 | // ID for login "dependabot[bot]" 82 | const DEPENDABOT_USER_ID = 49699333 83 | const ACTIVESCOTT_USER_ID = 213716 84 | const approvableActors = [DEPENDABOT_USER_ID, ACTIVESCOTT_USER_ID] 85 | if (!approvableActors.includes(run.actor.id)) { 86 | console.log(`Run ${run.id} is pending approval, but from an unknown actor: ${run.actor.login} (${run.actor.id})`) 87 | return null 88 | } 89 | console.log(`A run created by ${run.actor.login} is awaiting deployment: ${run.display_title}. Confirming that it is an expected environment and this user has permission to approve...`) 90 | const deploys = await getPendingDeploymentsForRun(run.id) 91 | const approvable = deploys.data.filter((deploy) => { 92 | const approvableEnvironments = ["aws"] 93 | if (!approvableEnvironments.includes(deploy.environment.name)) { 94 | console.log(`Environment '${deploy.environment.name}' not approvable for run ${run.display_title}.`) 95 | return false 96 | } 97 | if (!deploy.current_user_can_approve) { 98 | console.log(`The current user does not have permission to approve deployment for environment '${deploy.environment.name}'.`) 99 | return false 100 | } 101 | return true 102 | }) 103 | if (approvable.length > 0) { 104 | const deploy = approvable[0] 105 | // console.log(`Found pending deploy for run ${run.id}:`, deploy) 106 | return { 107 | environment: deploy.environment, 108 | run, 109 | } 110 | } else { 111 | return null 112 | } 113 | }) 114 | .filter((deploy) => deploy !== null) 115 | 116 | console.log(`Found ${deploysToApprove.length} deploys to approve.`) 117 | 118 | for await (const deploy of deploysToApprove) { 119 | await approveDeployment(deploy.run, deploy.environment) 120 | } 121 | -------------------------------------------------------------------------------- /scripts/approve-dependabot-deploys/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approve-dependabot-deploys", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "approve-dependabot-deploys", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^16.0.3", 13 | "octokit": "^3.1.2", 14 | "ts-node": "^10.9.1" 15 | } 16 | }, 17 | "node_modules/@cspotcode/source-map-support": { 18 | "version": "0.8.1", 19 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 20 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 21 | "dependencies": { 22 | "@jridgewell/trace-mapping": "0.3.9" 23 | }, 24 | "engines": { 25 | "node": ">=12" 26 | } 27 | }, 28 | "node_modules/@jridgewell/resolve-uri": { 29 | "version": "3.1.0", 30 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 31 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", 32 | "engines": { 33 | "node": ">=6.0.0" 34 | } 35 | }, 36 | "node_modules/@jridgewell/sourcemap-codec": { 37 | "version": "1.4.14", 38 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 39 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" 40 | }, 41 | "node_modules/@jridgewell/trace-mapping": { 42 | "version": "0.3.9", 43 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 44 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 45 | "dependencies": { 46 | "@jridgewell/resolve-uri": "^3.0.3", 47 | "@jridgewell/sourcemap-codec": "^1.4.10" 48 | } 49 | }, 50 | "node_modules/@octokit/app": { 51 | "version": "14.0.2", 52 | "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.0.2.tgz", 53 | "integrity": "sha512-NCSCktSx+XmjuSUVn2dLfqQ9WIYePGP95SDJs4I9cn/0ZkeXcPkaoCLl64Us3dRKL2ozC7hArwze5Eu+/qt1tg==", 54 | "dependencies": { 55 | "@octokit/auth-app": "^6.0.0", 56 | "@octokit/auth-unauthenticated": "^5.0.0", 57 | "@octokit/core": "^5.0.0", 58 | "@octokit/oauth-app": "^6.0.0", 59 | "@octokit/plugin-paginate-rest": "^9.0.0", 60 | "@octokit/types": "^12.0.0", 61 | "@octokit/webhooks": "^12.0.4" 62 | }, 63 | "engines": { 64 | "node": ">= 18" 65 | } 66 | }, 67 | "node_modules/@octokit/auth-app": { 68 | "version": "6.0.1", 69 | "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.0.1.tgz", 70 | "integrity": "sha512-tjCD4nzQNZgmLH62+PSnTF6eGerisFgV4v6euhqJik6yWV96e1ZiiGj+NXIqbgnpjLmtnBqVUrNyGKu3DoGEGA==", 71 | "dependencies": { 72 | "@octokit/auth-oauth-app": "^7.0.0", 73 | "@octokit/auth-oauth-user": "^4.0.0", 74 | "@octokit/request": "^8.0.2", 75 | "@octokit/request-error": "^5.0.0", 76 | "@octokit/types": "^12.0.0", 77 | "deprecation": "^2.3.1", 78 | "lru-cache": "^10.0.0", 79 | "universal-github-app-jwt": "^1.1.1", 80 | "universal-user-agent": "^6.0.0" 81 | }, 82 | "engines": { 83 | "node": ">= 18" 84 | } 85 | }, 86 | "node_modules/@octokit/auth-oauth-app": { 87 | "version": "7.0.1", 88 | "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.0.1.tgz", 89 | "integrity": "sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg==", 90 | "dependencies": { 91 | "@octokit/auth-oauth-device": "^6.0.0", 92 | "@octokit/auth-oauth-user": "^4.0.0", 93 | "@octokit/request": "^8.0.2", 94 | "@octokit/types": "^12.0.0", 95 | "@types/btoa-lite": "^1.0.0", 96 | "btoa-lite": "^1.0.0", 97 | "universal-user-agent": "^6.0.0" 98 | }, 99 | "engines": { 100 | "node": ">= 18" 101 | } 102 | }, 103 | "node_modules/@octokit/auth-oauth-device": { 104 | "version": "6.0.1", 105 | "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.0.1.tgz", 106 | "integrity": "sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw==", 107 | "dependencies": { 108 | "@octokit/oauth-methods": "^4.0.0", 109 | "@octokit/request": "^8.0.0", 110 | "@octokit/types": "^12.0.0", 111 | "universal-user-agent": "^6.0.0" 112 | }, 113 | "engines": { 114 | "node": ">= 18" 115 | } 116 | }, 117 | "node_modules/@octokit/auth-oauth-user": { 118 | "version": "4.0.1", 119 | "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.0.1.tgz", 120 | "integrity": "sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw==", 121 | "dependencies": { 122 | "@octokit/auth-oauth-device": "^6.0.0", 123 | "@octokit/oauth-methods": "^4.0.0", 124 | "@octokit/request": "^8.0.2", 125 | "@octokit/types": "^12.0.0", 126 | "btoa-lite": "^1.0.0", 127 | "universal-user-agent": "^6.0.0" 128 | }, 129 | "engines": { 130 | "node": ">= 18" 131 | } 132 | }, 133 | "node_modules/@octokit/auth-token": { 134 | "version": "4.0.0", 135 | "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", 136 | "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", 137 | "engines": { 138 | "node": ">= 18" 139 | } 140 | }, 141 | "node_modules/@octokit/auth-unauthenticated": { 142 | "version": "5.0.1", 143 | "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", 144 | "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", 145 | "dependencies": { 146 | "@octokit/request-error": "^5.0.0", 147 | "@octokit/types": "^12.0.0" 148 | }, 149 | "engines": { 150 | "node": ">= 18" 151 | } 152 | }, 153 | "node_modules/@octokit/core": { 154 | "version": "5.0.2", 155 | "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.2.tgz", 156 | "integrity": "sha512-cZUy1gUvd4vttMic7C0lwPed8IYXWYp8kHIMatyhY8t8n3Cpw2ILczkV5pGMPqef7v0bLo0pOHrEHarsau2Ydg==", 157 | "dependencies": { 158 | "@octokit/auth-token": "^4.0.0", 159 | "@octokit/graphql": "^7.0.0", 160 | "@octokit/request": "^8.0.2", 161 | "@octokit/request-error": "^5.0.0", 162 | "@octokit/types": "^12.0.0", 163 | "before-after-hook": "^2.2.0", 164 | "universal-user-agent": "^6.0.0" 165 | }, 166 | "engines": { 167 | "node": ">= 18" 168 | } 169 | }, 170 | "node_modules/@octokit/endpoint": { 171 | "version": "9.0.6", 172 | "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", 173 | "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", 174 | "license": "MIT", 175 | "dependencies": { 176 | "@octokit/types": "^13.1.0", 177 | "universal-user-agent": "^6.0.0" 178 | }, 179 | "engines": { 180 | "node": ">= 18" 181 | } 182 | }, 183 | "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { 184 | "version": "24.2.0", 185 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", 186 | "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", 187 | "license": "MIT" 188 | }, 189 | "node_modules/@octokit/endpoint/node_modules/@octokit/types": { 190 | "version": "13.10.0", 191 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", 192 | "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", 193 | "license": "MIT", 194 | "dependencies": { 195 | "@octokit/openapi-types": "^24.2.0" 196 | } 197 | }, 198 | "node_modules/@octokit/graphql": { 199 | "version": "7.0.2", 200 | "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", 201 | "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", 202 | "dependencies": { 203 | "@octokit/request": "^8.0.1", 204 | "@octokit/types": "^12.0.0", 205 | "universal-user-agent": "^6.0.0" 206 | }, 207 | "engines": { 208 | "node": ">= 18" 209 | } 210 | }, 211 | "node_modules/@octokit/oauth-app": { 212 | "version": "6.0.0", 213 | "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.0.0.tgz", 214 | "integrity": "sha512-bNMkS+vJ6oz2hCyraT9ZfTpAQ8dZNqJJQVNaKjPLx4ue5RZiFdU1YWXguOPR8AaSHS+lKe+lR3abn2siGd+zow==", 215 | "dependencies": { 216 | "@octokit/auth-oauth-app": "^7.0.0", 217 | "@octokit/auth-oauth-user": "^4.0.0", 218 | "@octokit/auth-unauthenticated": "^5.0.0", 219 | "@octokit/core": "^5.0.0", 220 | "@octokit/oauth-authorization-url": "^6.0.2", 221 | "@octokit/oauth-methods": "^4.0.0", 222 | "@types/aws-lambda": "^8.10.83", 223 | "universal-user-agent": "^6.0.0" 224 | }, 225 | "engines": { 226 | "node": ">= 18" 227 | } 228 | }, 229 | "node_modules/@octokit/oauth-authorization-url": { 230 | "version": "6.0.2", 231 | "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", 232 | "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", 233 | "engines": { 234 | "node": ">= 18" 235 | } 236 | }, 237 | "node_modules/@octokit/oauth-methods": { 238 | "version": "4.0.1", 239 | "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.0.1.tgz", 240 | "integrity": "sha512-1NdTGCoBHyD6J0n2WGXg9+yDLZrRNZ0moTEex/LSPr49m530WNKcCfXDghofYptr3st3eTii+EHoG5k/o+vbtw==", 241 | "dependencies": { 242 | "@octokit/oauth-authorization-url": "^6.0.2", 243 | "@octokit/request": "^8.0.2", 244 | "@octokit/request-error": "^5.0.0", 245 | "@octokit/types": "^12.0.0", 246 | "btoa-lite": "^1.0.0" 247 | }, 248 | "engines": { 249 | "node": ">= 18" 250 | } 251 | }, 252 | "node_modules/@octokit/openapi-types": { 253 | "version": "20.0.0", 254 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", 255 | "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", 256 | "license": "MIT" 257 | }, 258 | "node_modules/@octokit/plugin-paginate-graphql": { 259 | "version": "4.0.0", 260 | "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.0.tgz", 261 | "integrity": "sha512-7HcYW5tP7/Z6AETAPU14gp5H5KmCPT3hmJrS/5tO7HIgbwenYmgw4OY9Ma54FDySuxMwD+wsJlxtuGWwuZuItA==", 262 | "engines": { 263 | "node": ">= 18" 264 | }, 265 | "peerDependencies": { 266 | "@octokit/core": ">=5" 267 | } 268 | }, 269 | "node_modules/@octokit/plugin-paginate-rest": { 270 | "version": "9.2.2", 271 | "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", 272 | "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", 273 | "license": "MIT", 274 | "dependencies": { 275 | "@octokit/types": "^12.6.0" 276 | }, 277 | "engines": { 278 | "node": ">= 18" 279 | }, 280 | "peerDependencies": { 281 | "@octokit/core": "5" 282 | } 283 | }, 284 | "node_modules/@octokit/plugin-rest-endpoint-methods": { 285 | "version": "10.2.0", 286 | "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.2.0.tgz", 287 | "integrity": "sha512-ePbgBMYtGoRNXDyKGvr9cyHjQ163PbwD0y1MkDJCpkO2YH4OeXX40c4wYHKikHGZcpGPbcRLuy0unPUuafco8Q==", 288 | "dependencies": { 289 | "@octokit/types": "^12.3.0" 290 | }, 291 | "engines": { 292 | "node": ">= 18" 293 | }, 294 | "peerDependencies": { 295 | "@octokit/core": ">=5" 296 | } 297 | }, 298 | "node_modules/@octokit/plugin-retry": { 299 | "version": "6.0.1", 300 | "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz", 301 | "integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==", 302 | "dependencies": { 303 | "@octokit/request-error": "^5.0.0", 304 | "@octokit/types": "^12.0.0", 305 | "bottleneck": "^2.15.3" 306 | }, 307 | "engines": { 308 | "node": ">= 18" 309 | }, 310 | "peerDependencies": { 311 | "@octokit/core": ">=5" 312 | } 313 | }, 314 | "node_modules/@octokit/plugin-throttling": { 315 | "version": "8.1.3", 316 | "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.1.3.tgz", 317 | "integrity": "sha512-pfyqaqpc0EXh5Cn4HX9lWYsZ4gGbjnSmUILeu4u2gnuM50K/wIk9s1Pxt3lVeVwekmITgN/nJdoh43Ka+vye8A==", 318 | "dependencies": { 319 | "@octokit/types": "^12.2.0", 320 | "bottleneck": "^2.15.3" 321 | }, 322 | "engines": { 323 | "node": ">= 18" 324 | }, 325 | "peerDependencies": { 326 | "@octokit/core": "^5.0.0" 327 | } 328 | }, 329 | "node_modules/@octokit/request": { 330 | "version": "8.4.1", 331 | "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", 332 | "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", 333 | "license": "MIT", 334 | "dependencies": { 335 | "@octokit/endpoint": "^9.0.6", 336 | "@octokit/request-error": "^5.1.1", 337 | "@octokit/types": "^13.1.0", 338 | "universal-user-agent": "^6.0.0" 339 | }, 340 | "engines": { 341 | "node": ">= 18" 342 | } 343 | }, 344 | "node_modules/@octokit/request-error": { 345 | "version": "5.1.1", 346 | "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", 347 | "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", 348 | "license": "MIT", 349 | "dependencies": { 350 | "@octokit/types": "^13.1.0", 351 | "deprecation": "^2.0.0", 352 | "once": "^1.4.0" 353 | }, 354 | "engines": { 355 | "node": ">= 18" 356 | } 357 | }, 358 | "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { 359 | "version": "24.2.0", 360 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", 361 | "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", 362 | "license": "MIT" 363 | }, 364 | "node_modules/@octokit/request-error/node_modules/@octokit/types": { 365 | "version": "13.10.0", 366 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", 367 | "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", 368 | "license": "MIT", 369 | "dependencies": { 370 | "@octokit/openapi-types": "^24.2.0" 371 | } 372 | }, 373 | "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { 374 | "version": "24.2.0", 375 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", 376 | "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", 377 | "license": "MIT" 378 | }, 379 | "node_modules/@octokit/request/node_modules/@octokit/types": { 380 | "version": "13.10.0", 381 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", 382 | "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", 383 | "license": "MIT", 384 | "dependencies": { 385 | "@octokit/openapi-types": "^24.2.0" 386 | } 387 | }, 388 | "node_modules/@octokit/types": { 389 | "version": "12.6.0", 390 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", 391 | "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", 392 | "license": "MIT", 393 | "dependencies": { 394 | "@octokit/openapi-types": "^20.0.0" 395 | } 396 | }, 397 | "node_modules/@octokit/webhooks": { 398 | "version": "12.0.10", 399 | "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.0.10.tgz", 400 | "integrity": "sha512-Q8d26l7gZ3L1SSr25NFbbP0B431sovU5r0tIqcvy8Z4PrD1LBv0cJEjvDLOieouzPSTzSzufzRIeXD7S+zAESA==", 401 | "dependencies": { 402 | "@octokit/request-error": "^5.0.0", 403 | "@octokit/webhooks-methods": "^4.0.0", 404 | "@octokit/webhooks-types": "7.1.0", 405 | "aggregate-error": "^3.1.0" 406 | }, 407 | "engines": { 408 | "node": ">= 18" 409 | } 410 | }, 411 | "node_modules/@octokit/webhooks-methods": { 412 | "version": "4.0.0", 413 | "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.0.0.tgz", 414 | "integrity": "sha512-M8mwmTXp+VeolOS/kfRvsDdW+IO0qJ8kYodM/sAysk093q6ApgmBXwK1ZlUvAwXVrp/YVHp6aArj4auAxUAOFw==", 415 | "engines": { 416 | "node": ">= 18" 417 | } 418 | }, 419 | "node_modules/@octokit/webhooks-types": { 420 | "version": "7.1.0", 421 | "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.1.0.tgz", 422 | "integrity": "sha512-y92CpG4kFFtBBjni8LHoV12IegJ+KFxLgKRengrVjKmGE5XMeCuGvlfRe75lTRrgXaG6XIWJlFpIDTlkoJsU8w==" 423 | }, 424 | "node_modules/@tsconfig/node10": { 425 | "version": "1.0.9", 426 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 427 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" 428 | }, 429 | "node_modules/@tsconfig/node12": { 430 | "version": "1.0.11", 431 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 432 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" 433 | }, 434 | "node_modules/@tsconfig/node14": { 435 | "version": "1.0.3", 436 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 437 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" 438 | }, 439 | "node_modules/@tsconfig/node16": { 440 | "version": "1.0.3", 441 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 442 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" 443 | }, 444 | "node_modules/@types/aws-lambda": { 445 | "version": "8.10.130", 446 | "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.130.tgz", 447 | "integrity": "sha512-HxTfLeGvD1wTJqIGwcBCpNmHKenja+We1e0cuzeIDFfbEj3ixnlTInyPR/81zAe0Ss/Ip12rFK6XNeMLVucOSg==" 448 | }, 449 | "node_modules/@types/btoa-lite": { 450 | "version": "1.0.2", 451 | "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", 452 | "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==" 453 | }, 454 | "node_modules/@types/jsonwebtoken": { 455 | "version": "9.0.5", 456 | "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", 457 | "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", 458 | "dependencies": { 459 | "@types/node": "*" 460 | } 461 | }, 462 | "node_modules/@types/node": { 463 | "version": "18.8.0", 464 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.0.tgz", 465 | "integrity": "sha512-u+h43R6U8xXDt2vzUaVP3VwjjLyOJk6uEciZS8OSyziUQGOwmk+l+4drxcsDboHXwyTaqS1INebghmWMRxq3LA==" 466 | }, 467 | "node_modules/acorn": { 468 | "version": "8.8.0", 469 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", 470 | "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", 471 | "bin": { 472 | "acorn": "bin/acorn" 473 | }, 474 | "engines": { 475 | "node": ">=0.4.0" 476 | } 477 | }, 478 | "node_modules/acorn-walk": { 479 | "version": "8.2.0", 480 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 481 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 482 | "engines": { 483 | "node": ">=0.4.0" 484 | } 485 | }, 486 | "node_modules/aggregate-error": { 487 | "version": "3.1.0", 488 | "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", 489 | "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", 490 | "dependencies": { 491 | "clean-stack": "^2.0.0", 492 | "indent-string": "^4.0.0" 493 | }, 494 | "engines": { 495 | "node": ">=8" 496 | } 497 | }, 498 | "node_modules/arg": { 499 | "version": "4.1.3", 500 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 501 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 502 | }, 503 | "node_modules/before-after-hook": { 504 | "version": "2.2.3", 505 | "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", 506 | "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" 507 | }, 508 | "node_modules/bottleneck": { 509 | "version": "2.19.5", 510 | "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", 511 | "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" 512 | }, 513 | "node_modules/btoa-lite": { 514 | "version": "1.0.0", 515 | "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", 516 | "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" 517 | }, 518 | "node_modules/buffer-equal-constant-time": { 519 | "version": "1.0.1", 520 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 521 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 522 | }, 523 | "node_modules/clean-stack": { 524 | "version": "2.2.0", 525 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", 526 | "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", 527 | "engines": { 528 | "node": ">=6" 529 | } 530 | }, 531 | "node_modules/create-require": { 532 | "version": "1.1.1", 533 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 534 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 535 | }, 536 | "node_modules/deprecation": { 537 | "version": "2.3.1", 538 | "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", 539 | "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" 540 | }, 541 | "node_modules/diff": { 542 | "version": "4.0.2", 543 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 544 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 545 | "engines": { 546 | "node": ">=0.3.1" 547 | } 548 | }, 549 | "node_modules/dotenv": { 550 | "version": "16.0.3", 551 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", 552 | "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", 553 | "engines": { 554 | "node": ">=12" 555 | } 556 | }, 557 | "node_modules/ecdsa-sig-formatter": { 558 | "version": "1.0.11", 559 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 560 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 561 | "dependencies": { 562 | "safe-buffer": "^5.0.1" 563 | } 564 | }, 565 | "node_modules/indent-string": { 566 | "version": "4.0.0", 567 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 568 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", 569 | "engines": { 570 | "node": ">=8" 571 | } 572 | }, 573 | "node_modules/jsonwebtoken": { 574 | "version": "9.0.2", 575 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", 576 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", 577 | "dependencies": { 578 | "jws": "^3.2.2", 579 | "lodash.includes": "^4.3.0", 580 | "lodash.isboolean": "^3.0.3", 581 | "lodash.isinteger": "^4.0.4", 582 | "lodash.isnumber": "^3.0.3", 583 | "lodash.isplainobject": "^4.0.6", 584 | "lodash.isstring": "^4.0.1", 585 | "lodash.once": "^4.0.0", 586 | "ms": "^2.1.1", 587 | "semver": "^7.5.4" 588 | }, 589 | "engines": { 590 | "node": ">=12", 591 | "npm": ">=6" 592 | } 593 | }, 594 | "node_modules/jwa": { 595 | "version": "1.4.1", 596 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 597 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 598 | "dependencies": { 599 | "buffer-equal-constant-time": "1.0.1", 600 | "ecdsa-sig-formatter": "1.0.11", 601 | "safe-buffer": "^5.0.1" 602 | } 603 | }, 604 | "node_modules/jws": { 605 | "version": "3.2.2", 606 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 607 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 608 | "dependencies": { 609 | "jwa": "^1.4.1", 610 | "safe-buffer": "^5.0.1" 611 | } 612 | }, 613 | "node_modules/lodash.includes": { 614 | "version": "4.3.0", 615 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 616 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" 617 | }, 618 | "node_modules/lodash.isboolean": { 619 | "version": "3.0.3", 620 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 621 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" 622 | }, 623 | "node_modules/lodash.isinteger": { 624 | "version": "4.0.4", 625 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 626 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" 627 | }, 628 | "node_modules/lodash.isnumber": { 629 | "version": "3.0.3", 630 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 631 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" 632 | }, 633 | "node_modules/lodash.isplainobject": { 634 | "version": "4.0.6", 635 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 636 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" 637 | }, 638 | "node_modules/lodash.isstring": { 639 | "version": "4.0.1", 640 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 641 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" 642 | }, 643 | "node_modules/lodash.once": { 644 | "version": "4.1.1", 645 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 646 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" 647 | }, 648 | "node_modules/lru-cache": { 649 | "version": "10.1.0", 650 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", 651 | "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", 652 | "engines": { 653 | "node": "14 || >=16.14" 654 | } 655 | }, 656 | "node_modules/make-error": { 657 | "version": "1.3.6", 658 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 659 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 660 | }, 661 | "node_modules/ms": { 662 | "version": "2.1.3", 663 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 664 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 665 | }, 666 | "node_modules/octokit": { 667 | "version": "3.1.2", 668 | "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", 669 | "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", 670 | "dependencies": { 671 | "@octokit/app": "^14.0.2", 672 | "@octokit/core": "^5.0.0", 673 | "@octokit/oauth-app": "^6.0.0", 674 | "@octokit/plugin-paginate-graphql": "^4.0.0", 675 | "@octokit/plugin-paginate-rest": "^9.0.0", 676 | "@octokit/plugin-rest-endpoint-methods": "^10.0.0", 677 | "@octokit/plugin-retry": "^6.0.0", 678 | "@octokit/plugin-throttling": "^8.0.0", 679 | "@octokit/request-error": "^5.0.0", 680 | "@octokit/types": "^12.0.0" 681 | }, 682 | "engines": { 683 | "node": ">= 18" 684 | } 685 | }, 686 | "node_modules/once": { 687 | "version": "1.4.0", 688 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 689 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 690 | "dependencies": { 691 | "wrappy": "1" 692 | } 693 | }, 694 | "node_modules/safe-buffer": { 695 | "version": "5.2.1", 696 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 697 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 698 | "funding": [ 699 | { 700 | "type": "github", 701 | "url": "https://github.com/sponsors/feross" 702 | }, 703 | { 704 | "type": "patreon", 705 | "url": "https://www.patreon.com/feross" 706 | }, 707 | { 708 | "type": "consulting", 709 | "url": "https://feross.org/support" 710 | } 711 | ] 712 | }, 713 | "node_modules/semver": { 714 | "version": "7.5.4", 715 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 716 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 717 | "dependencies": { 718 | "lru-cache": "^6.0.0" 719 | }, 720 | "bin": { 721 | "semver": "bin/semver.js" 722 | }, 723 | "engines": { 724 | "node": ">=10" 725 | } 726 | }, 727 | "node_modules/semver/node_modules/lru-cache": { 728 | "version": "6.0.0", 729 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 730 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 731 | "dependencies": { 732 | "yallist": "^4.0.0" 733 | }, 734 | "engines": { 735 | "node": ">=10" 736 | } 737 | }, 738 | "node_modules/ts-node": { 739 | "version": "10.9.1", 740 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 741 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 742 | "dependencies": { 743 | "@cspotcode/source-map-support": "^0.8.0", 744 | "@tsconfig/node10": "^1.0.7", 745 | "@tsconfig/node12": "^1.0.7", 746 | "@tsconfig/node14": "^1.0.0", 747 | "@tsconfig/node16": "^1.0.2", 748 | "acorn": "^8.4.1", 749 | "acorn-walk": "^8.1.1", 750 | "arg": "^4.1.0", 751 | "create-require": "^1.1.0", 752 | "diff": "^4.0.1", 753 | "make-error": "^1.1.1", 754 | "v8-compile-cache-lib": "^3.0.1", 755 | "yn": "3.1.1" 756 | }, 757 | "bin": { 758 | "ts-node": "dist/bin.js", 759 | "ts-node-cwd": "dist/bin-cwd.js", 760 | "ts-node-esm": "dist/bin-esm.js", 761 | "ts-node-script": "dist/bin-script.js", 762 | "ts-node-transpile-only": "dist/bin-transpile.js", 763 | "ts-script": "dist/bin-script-deprecated.js" 764 | }, 765 | "peerDependencies": { 766 | "@swc/core": ">=1.2.50", 767 | "@swc/wasm": ">=1.2.50", 768 | "@types/node": "*", 769 | "typescript": ">=2.7" 770 | }, 771 | "peerDependenciesMeta": { 772 | "@swc/core": { 773 | "optional": true 774 | }, 775 | "@swc/wasm": { 776 | "optional": true 777 | } 778 | } 779 | }, 780 | "node_modules/typescript": { 781 | "version": "4.8.4", 782 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", 783 | "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", 784 | "peer": true, 785 | "bin": { 786 | "tsc": "bin/tsc", 787 | "tsserver": "bin/tsserver" 788 | }, 789 | "engines": { 790 | "node": ">=4.2.0" 791 | } 792 | }, 793 | "node_modules/universal-github-app-jwt": { 794 | "version": "1.1.1", 795 | "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz", 796 | "integrity": "sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==", 797 | "dependencies": { 798 | "@types/jsonwebtoken": "^9.0.0", 799 | "jsonwebtoken": "^9.0.0" 800 | } 801 | }, 802 | "node_modules/universal-user-agent": { 803 | "version": "6.0.1", 804 | "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", 805 | "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" 806 | }, 807 | "node_modules/v8-compile-cache-lib": { 808 | "version": "3.0.1", 809 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 810 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" 811 | }, 812 | "node_modules/wrappy": { 813 | "version": "1.0.2", 814 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 815 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 816 | }, 817 | "node_modules/yallist": { 818 | "version": "4.0.0", 819 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 820 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 821 | }, 822 | "node_modules/yn": { 823 | "version": "3.1.1", 824 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 825 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 826 | "engines": { 827 | "node": ">=6" 828 | } 829 | } 830 | }, 831 | "dependencies": { 832 | "@cspotcode/source-map-support": { 833 | "version": "0.8.1", 834 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 835 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 836 | "requires": { 837 | "@jridgewell/trace-mapping": "0.3.9" 838 | } 839 | }, 840 | "@jridgewell/resolve-uri": { 841 | "version": "3.1.0", 842 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", 843 | "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" 844 | }, 845 | "@jridgewell/sourcemap-codec": { 846 | "version": "1.4.14", 847 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", 848 | "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" 849 | }, 850 | "@jridgewell/trace-mapping": { 851 | "version": "0.3.9", 852 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 853 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 854 | "requires": { 855 | "@jridgewell/resolve-uri": "^3.0.3", 856 | "@jridgewell/sourcemap-codec": "^1.4.10" 857 | } 858 | }, 859 | "@octokit/app": { 860 | "version": "14.0.2", 861 | "resolved": "https://registry.npmjs.org/@octokit/app/-/app-14.0.2.tgz", 862 | "integrity": "sha512-NCSCktSx+XmjuSUVn2dLfqQ9WIYePGP95SDJs4I9cn/0ZkeXcPkaoCLl64Us3dRKL2ozC7hArwze5Eu+/qt1tg==", 863 | "requires": { 864 | "@octokit/auth-app": "^6.0.0", 865 | "@octokit/auth-unauthenticated": "^5.0.0", 866 | "@octokit/core": "^5.0.0", 867 | "@octokit/oauth-app": "^6.0.0", 868 | "@octokit/plugin-paginate-rest": "^9.0.0", 869 | "@octokit/types": "^12.0.0", 870 | "@octokit/webhooks": "^12.0.4" 871 | } 872 | }, 873 | "@octokit/auth-app": { 874 | "version": "6.0.1", 875 | "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.0.1.tgz", 876 | "integrity": "sha512-tjCD4nzQNZgmLH62+PSnTF6eGerisFgV4v6euhqJik6yWV96e1ZiiGj+NXIqbgnpjLmtnBqVUrNyGKu3DoGEGA==", 877 | "requires": { 878 | "@octokit/auth-oauth-app": "^7.0.0", 879 | "@octokit/auth-oauth-user": "^4.0.0", 880 | "@octokit/request": "^8.0.2", 881 | "@octokit/request-error": "^5.0.0", 882 | "@octokit/types": "^12.0.0", 883 | "deprecation": "^2.3.1", 884 | "lru-cache": "^10.0.0", 885 | "universal-github-app-jwt": "^1.1.1", 886 | "universal-user-agent": "^6.0.0" 887 | } 888 | }, 889 | "@octokit/auth-oauth-app": { 890 | "version": "7.0.1", 891 | "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.0.1.tgz", 892 | "integrity": "sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg==", 893 | "requires": { 894 | "@octokit/auth-oauth-device": "^6.0.0", 895 | "@octokit/auth-oauth-user": "^4.0.0", 896 | "@octokit/request": "^8.0.2", 897 | "@octokit/types": "^12.0.0", 898 | "@types/btoa-lite": "^1.0.0", 899 | "btoa-lite": "^1.0.0", 900 | "universal-user-agent": "^6.0.0" 901 | } 902 | }, 903 | "@octokit/auth-oauth-device": { 904 | "version": "6.0.1", 905 | "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.0.1.tgz", 906 | "integrity": "sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw==", 907 | "requires": { 908 | "@octokit/oauth-methods": "^4.0.0", 909 | "@octokit/request": "^8.0.0", 910 | "@octokit/types": "^12.0.0", 911 | "universal-user-agent": "^6.0.0" 912 | } 913 | }, 914 | "@octokit/auth-oauth-user": { 915 | "version": "4.0.1", 916 | "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.0.1.tgz", 917 | "integrity": "sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw==", 918 | "requires": { 919 | "@octokit/auth-oauth-device": "^6.0.0", 920 | "@octokit/oauth-methods": "^4.0.0", 921 | "@octokit/request": "^8.0.2", 922 | "@octokit/types": "^12.0.0", 923 | "btoa-lite": "^1.0.0", 924 | "universal-user-agent": "^6.0.0" 925 | } 926 | }, 927 | "@octokit/auth-token": { 928 | "version": "4.0.0", 929 | "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", 930 | "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==" 931 | }, 932 | "@octokit/auth-unauthenticated": { 933 | "version": "5.0.1", 934 | "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-5.0.1.tgz", 935 | "integrity": "sha512-oxeWzmBFxWd+XolxKTc4zr+h3mt+yofn4r7OfoIkR/Cj/o70eEGmPsFbueyJE2iBAGpjgTnEOKM3pnuEGVmiqg==", 936 | "requires": { 937 | "@octokit/request-error": "^5.0.0", 938 | "@octokit/types": "^12.0.0" 939 | } 940 | }, 941 | "@octokit/core": { 942 | "version": "5.0.2", 943 | "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.2.tgz", 944 | "integrity": "sha512-cZUy1gUvd4vttMic7C0lwPed8IYXWYp8kHIMatyhY8t8n3Cpw2ILczkV5pGMPqef7v0bLo0pOHrEHarsau2Ydg==", 945 | "requires": { 946 | "@octokit/auth-token": "^4.0.0", 947 | "@octokit/graphql": "^7.0.0", 948 | "@octokit/request": "^8.0.2", 949 | "@octokit/request-error": "^5.0.0", 950 | "@octokit/types": "^12.0.0", 951 | "before-after-hook": "^2.2.0", 952 | "universal-user-agent": "^6.0.0" 953 | } 954 | }, 955 | "@octokit/endpoint": { 956 | "version": "9.0.6", 957 | "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", 958 | "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", 959 | "requires": { 960 | "@octokit/types": "^13.1.0", 961 | "universal-user-agent": "^6.0.0" 962 | }, 963 | "dependencies": { 964 | "@octokit/openapi-types": { 965 | "version": "24.2.0", 966 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", 967 | "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" 968 | }, 969 | "@octokit/types": { 970 | "version": "13.10.0", 971 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", 972 | "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", 973 | "requires": { 974 | "@octokit/openapi-types": "^24.2.0" 975 | } 976 | } 977 | } 978 | }, 979 | "@octokit/graphql": { 980 | "version": "7.0.2", 981 | "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", 982 | "integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==", 983 | "requires": { 984 | "@octokit/request": "^8.0.1", 985 | "@octokit/types": "^12.0.0", 986 | "universal-user-agent": "^6.0.0" 987 | } 988 | }, 989 | "@octokit/oauth-app": { 990 | "version": "6.0.0", 991 | "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-6.0.0.tgz", 992 | "integrity": "sha512-bNMkS+vJ6oz2hCyraT9ZfTpAQ8dZNqJJQVNaKjPLx4ue5RZiFdU1YWXguOPR8AaSHS+lKe+lR3abn2siGd+zow==", 993 | "requires": { 994 | "@octokit/auth-oauth-app": "^7.0.0", 995 | "@octokit/auth-oauth-user": "^4.0.0", 996 | "@octokit/auth-unauthenticated": "^5.0.0", 997 | "@octokit/core": "^5.0.0", 998 | "@octokit/oauth-authorization-url": "^6.0.2", 999 | "@octokit/oauth-methods": "^4.0.0", 1000 | "@types/aws-lambda": "^8.10.83", 1001 | "universal-user-agent": "^6.0.0" 1002 | } 1003 | }, 1004 | "@octokit/oauth-authorization-url": { 1005 | "version": "6.0.2", 1006 | "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", 1007 | "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==" 1008 | }, 1009 | "@octokit/oauth-methods": { 1010 | "version": "4.0.1", 1011 | "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.0.1.tgz", 1012 | "integrity": "sha512-1NdTGCoBHyD6J0n2WGXg9+yDLZrRNZ0moTEex/LSPr49m530WNKcCfXDghofYptr3st3eTii+EHoG5k/o+vbtw==", 1013 | "requires": { 1014 | "@octokit/oauth-authorization-url": "^6.0.2", 1015 | "@octokit/request": "^8.0.2", 1016 | "@octokit/request-error": "^5.0.0", 1017 | "@octokit/types": "^12.0.0", 1018 | "btoa-lite": "^1.0.0" 1019 | } 1020 | }, 1021 | "@octokit/openapi-types": { 1022 | "version": "20.0.0", 1023 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", 1024 | "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" 1025 | }, 1026 | "@octokit/plugin-paginate-graphql": { 1027 | "version": "4.0.0", 1028 | "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-4.0.0.tgz", 1029 | "integrity": "sha512-7HcYW5tP7/Z6AETAPU14gp5H5KmCPT3hmJrS/5tO7HIgbwenYmgw4OY9Ma54FDySuxMwD+wsJlxtuGWwuZuItA==", 1030 | "requires": {} 1031 | }, 1032 | "@octokit/plugin-paginate-rest": { 1033 | "version": "9.2.2", 1034 | "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", 1035 | "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", 1036 | "requires": { 1037 | "@octokit/types": "^12.6.0" 1038 | } 1039 | }, 1040 | "@octokit/plugin-rest-endpoint-methods": { 1041 | "version": "10.2.0", 1042 | "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.2.0.tgz", 1043 | "integrity": "sha512-ePbgBMYtGoRNXDyKGvr9cyHjQ163PbwD0y1MkDJCpkO2YH4OeXX40c4wYHKikHGZcpGPbcRLuy0unPUuafco8Q==", 1044 | "requires": { 1045 | "@octokit/types": "^12.3.0" 1046 | } 1047 | }, 1048 | "@octokit/plugin-retry": { 1049 | "version": "6.0.1", 1050 | "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz", 1051 | "integrity": "sha512-SKs+Tz9oj0g4p28qkZwl/topGcb0k0qPNX/i7vBKmDsjoeqnVfFUquqrE/O9oJY7+oLzdCtkiWSXLpLjvl6uog==", 1052 | "requires": { 1053 | "@octokit/request-error": "^5.0.0", 1054 | "@octokit/types": "^12.0.0", 1055 | "bottleneck": "^2.15.3" 1056 | } 1057 | }, 1058 | "@octokit/plugin-throttling": { 1059 | "version": "8.1.3", 1060 | "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.1.3.tgz", 1061 | "integrity": "sha512-pfyqaqpc0EXh5Cn4HX9lWYsZ4gGbjnSmUILeu4u2gnuM50K/wIk9s1Pxt3lVeVwekmITgN/nJdoh43Ka+vye8A==", 1062 | "requires": { 1063 | "@octokit/types": "^12.2.0", 1064 | "bottleneck": "^2.15.3" 1065 | } 1066 | }, 1067 | "@octokit/request": { 1068 | "version": "8.4.1", 1069 | "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", 1070 | "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", 1071 | "requires": { 1072 | "@octokit/endpoint": "^9.0.6", 1073 | "@octokit/request-error": "^5.1.1", 1074 | "@octokit/types": "^13.1.0", 1075 | "universal-user-agent": "^6.0.0" 1076 | }, 1077 | "dependencies": { 1078 | "@octokit/openapi-types": { 1079 | "version": "24.2.0", 1080 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", 1081 | "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" 1082 | }, 1083 | "@octokit/types": { 1084 | "version": "13.10.0", 1085 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", 1086 | "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", 1087 | "requires": { 1088 | "@octokit/openapi-types": "^24.2.0" 1089 | } 1090 | } 1091 | } 1092 | }, 1093 | "@octokit/request-error": { 1094 | "version": "5.1.1", 1095 | "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", 1096 | "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", 1097 | "requires": { 1098 | "@octokit/types": "^13.1.0", 1099 | "deprecation": "^2.0.0", 1100 | "once": "^1.4.0" 1101 | }, 1102 | "dependencies": { 1103 | "@octokit/openapi-types": { 1104 | "version": "24.2.0", 1105 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", 1106 | "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==" 1107 | }, 1108 | "@octokit/types": { 1109 | "version": "13.10.0", 1110 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", 1111 | "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", 1112 | "requires": { 1113 | "@octokit/openapi-types": "^24.2.0" 1114 | } 1115 | } 1116 | } 1117 | }, 1118 | "@octokit/types": { 1119 | "version": "12.6.0", 1120 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", 1121 | "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", 1122 | "requires": { 1123 | "@octokit/openapi-types": "^20.0.0" 1124 | } 1125 | }, 1126 | "@octokit/webhooks": { 1127 | "version": "12.0.10", 1128 | "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-12.0.10.tgz", 1129 | "integrity": "sha512-Q8d26l7gZ3L1SSr25NFbbP0B431sovU5r0tIqcvy8Z4PrD1LBv0cJEjvDLOieouzPSTzSzufzRIeXD7S+zAESA==", 1130 | "requires": { 1131 | "@octokit/request-error": "^5.0.0", 1132 | "@octokit/webhooks-methods": "^4.0.0", 1133 | "@octokit/webhooks-types": "7.1.0", 1134 | "aggregate-error": "^3.1.0" 1135 | } 1136 | }, 1137 | "@octokit/webhooks-methods": { 1138 | "version": "4.0.0", 1139 | "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-4.0.0.tgz", 1140 | "integrity": "sha512-M8mwmTXp+VeolOS/kfRvsDdW+IO0qJ8kYodM/sAysk093q6ApgmBXwK1ZlUvAwXVrp/YVHp6aArj4auAxUAOFw==" 1141 | }, 1142 | "@octokit/webhooks-types": { 1143 | "version": "7.1.0", 1144 | "resolved": "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-7.1.0.tgz", 1145 | "integrity": "sha512-y92CpG4kFFtBBjni8LHoV12IegJ+KFxLgKRengrVjKmGE5XMeCuGvlfRe75lTRrgXaG6XIWJlFpIDTlkoJsU8w==" 1146 | }, 1147 | "@tsconfig/node10": { 1148 | "version": "1.0.9", 1149 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 1150 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" 1151 | }, 1152 | "@tsconfig/node12": { 1153 | "version": "1.0.11", 1154 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 1155 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" 1156 | }, 1157 | "@tsconfig/node14": { 1158 | "version": "1.0.3", 1159 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 1160 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" 1161 | }, 1162 | "@tsconfig/node16": { 1163 | "version": "1.0.3", 1164 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", 1165 | "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==" 1166 | }, 1167 | "@types/aws-lambda": { 1168 | "version": "8.10.130", 1169 | "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.130.tgz", 1170 | "integrity": "sha512-HxTfLeGvD1wTJqIGwcBCpNmHKenja+We1e0cuzeIDFfbEj3ixnlTInyPR/81zAe0Ss/Ip12rFK6XNeMLVucOSg==" 1171 | }, 1172 | "@types/btoa-lite": { 1173 | "version": "1.0.2", 1174 | "resolved": "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.2.tgz", 1175 | "integrity": "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg==" 1176 | }, 1177 | "@types/jsonwebtoken": { 1178 | "version": "9.0.5", 1179 | "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", 1180 | "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", 1181 | "requires": { 1182 | "@types/node": "*" 1183 | } 1184 | }, 1185 | "@types/node": { 1186 | "version": "18.8.0", 1187 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.0.tgz", 1188 | "integrity": "sha512-u+h43R6U8xXDt2vzUaVP3VwjjLyOJk6uEciZS8OSyziUQGOwmk+l+4drxcsDboHXwyTaqS1INebghmWMRxq3LA==" 1189 | }, 1190 | "acorn": { 1191 | "version": "8.8.0", 1192 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", 1193 | "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==" 1194 | }, 1195 | "acorn-walk": { 1196 | "version": "8.2.0", 1197 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 1198 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" 1199 | }, 1200 | "aggregate-error": { 1201 | "version": "3.1.0", 1202 | "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", 1203 | "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", 1204 | "requires": { 1205 | "clean-stack": "^2.0.0", 1206 | "indent-string": "^4.0.0" 1207 | } 1208 | }, 1209 | "arg": { 1210 | "version": "4.1.3", 1211 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 1212 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 1213 | }, 1214 | "before-after-hook": { 1215 | "version": "2.2.3", 1216 | "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", 1217 | "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" 1218 | }, 1219 | "bottleneck": { 1220 | "version": "2.19.5", 1221 | "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", 1222 | "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" 1223 | }, 1224 | "btoa-lite": { 1225 | "version": "1.0.0", 1226 | "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", 1227 | "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" 1228 | }, 1229 | "buffer-equal-constant-time": { 1230 | "version": "1.0.1", 1231 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 1232 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" 1233 | }, 1234 | "clean-stack": { 1235 | "version": "2.2.0", 1236 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", 1237 | "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" 1238 | }, 1239 | "create-require": { 1240 | "version": "1.1.1", 1241 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 1242 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 1243 | }, 1244 | "deprecation": { 1245 | "version": "2.3.1", 1246 | "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", 1247 | "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" 1248 | }, 1249 | "diff": { 1250 | "version": "4.0.2", 1251 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 1252 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" 1253 | }, 1254 | "dotenv": { 1255 | "version": "16.0.3", 1256 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", 1257 | "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" 1258 | }, 1259 | "ecdsa-sig-formatter": { 1260 | "version": "1.0.11", 1261 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 1262 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 1263 | "requires": { 1264 | "safe-buffer": "^5.0.1" 1265 | } 1266 | }, 1267 | "indent-string": { 1268 | "version": "4.0.0", 1269 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 1270 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" 1271 | }, 1272 | "jsonwebtoken": { 1273 | "version": "9.0.2", 1274 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", 1275 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", 1276 | "requires": { 1277 | "jws": "^3.2.2", 1278 | "lodash.includes": "^4.3.0", 1279 | "lodash.isboolean": "^3.0.3", 1280 | "lodash.isinteger": "^4.0.4", 1281 | "lodash.isnumber": "^3.0.3", 1282 | "lodash.isplainobject": "^4.0.6", 1283 | "lodash.isstring": "^4.0.1", 1284 | "lodash.once": "^4.0.0", 1285 | "ms": "^2.1.1", 1286 | "semver": "^7.5.4" 1287 | } 1288 | }, 1289 | "jwa": { 1290 | "version": "1.4.1", 1291 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 1292 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 1293 | "requires": { 1294 | "buffer-equal-constant-time": "1.0.1", 1295 | "ecdsa-sig-formatter": "1.0.11", 1296 | "safe-buffer": "^5.0.1" 1297 | } 1298 | }, 1299 | "jws": { 1300 | "version": "3.2.2", 1301 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 1302 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 1303 | "requires": { 1304 | "jwa": "^1.4.1", 1305 | "safe-buffer": "^5.0.1" 1306 | } 1307 | }, 1308 | "lodash.includes": { 1309 | "version": "4.3.0", 1310 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 1311 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" 1312 | }, 1313 | "lodash.isboolean": { 1314 | "version": "3.0.3", 1315 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 1316 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" 1317 | }, 1318 | "lodash.isinteger": { 1319 | "version": "4.0.4", 1320 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 1321 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" 1322 | }, 1323 | "lodash.isnumber": { 1324 | "version": "3.0.3", 1325 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 1326 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" 1327 | }, 1328 | "lodash.isplainobject": { 1329 | "version": "4.0.6", 1330 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 1331 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" 1332 | }, 1333 | "lodash.isstring": { 1334 | "version": "4.0.1", 1335 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 1336 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" 1337 | }, 1338 | "lodash.once": { 1339 | "version": "4.1.1", 1340 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 1341 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" 1342 | }, 1343 | "lru-cache": { 1344 | "version": "10.1.0", 1345 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", 1346 | "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==" 1347 | }, 1348 | "make-error": { 1349 | "version": "1.3.6", 1350 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 1351 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 1352 | }, 1353 | "ms": { 1354 | "version": "2.1.3", 1355 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1356 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1357 | }, 1358 | "octokit": { 1359 | "version": "3.1.2", 1360 | "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.2.tgz", 1361 | "integrity": "sha512-MG5qmrTL5y8KYwFgE1A4JWmgfQBaIETE/lOlfwNYx1QOtCQHGVxkRJmdUJltFc1HVn73d61TlMhMyNTOtMl+ng==", 1362 | "requires": { 1363 | "@octokit/app": "^14.0.2", 1364 | "@octokit/core": "^5.0.0", 1365 | "@octokit/oauth-app": "^6.0.0", 1366 | "@octokit/plugin-paginate-graphql": "^4.0.0", 1367 | "@octokit/plugin-paginate-rest": "^9.0.0", 1368 | "@octokit/plugin-rest-endpoint-methods": "^10.0.0", 1369 | "@octokit/plugin-retry": "^6.0.0", 1370 | "@octokit/plugin-throttling": "^8.0.0", 1371 | "@octokit/request-error": "^5.0.0", 1372 | "@octokit/types": "^12.0.0" 1373 | } 1374 | }, 1375 | "once": { 1376 | "version": "1.4.0", 1377 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1378 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1379 | "requires": { 1380 | "wrappy": "1" 1381 | } 1382 | }, 1383 | "safe-buffer": { 1384 | "version": "5.2.1", 1385 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1386 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1387 | }, 1388 | "semver": { 1389 | "version": "7.5.4", 1390 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1391 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1392 | "requires": { 1393 | "lru-cache": "^6.0.0" 1394 | }, 1395 | "dependencies": { 1396 | "lru-cache": { 1397 | "version": "6.0.0", 1398 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1399 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1400 | "requires": { 1401 | "yallist": "^4.0.0" 1402 | } 1403 | } 1404 | } 1405 | }, 1406 | "ts-node": { 1407 | "version": "10.9.1", 1408 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 1409 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 1410 | "requires": { 1411 | "@cspotcode/source-map-support": "^0.8.0", 1412 | "@tsconfig/node10": "^1.0.7", 1413 | "@tsconfig/node12": "^1.0.7", 1414 | "@tsconfig/node14": "^1.0.0", 1415 | "@tsconfig/node16": "^1.0.2", 1416 | "acorn": "^8.4.1", 1417 | "acorn-walk": "^8.1.1", 1418 | "arg": "^4.1.0", 1419 | "create-require": "^1.1.0", 1420 | "diff": "^4.0.1", 1421 | "make-error": "^1.1.1", 1422 | "v8-compile-cache-lib": "^3.0.1", 1423 | "yn": "3.1.1" 1424 | } 1425 | }, 1426 | "typescript": { 1427 | "version": "4.8.4", 1428 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", 1429 | "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", 1430 | "peer": true 1431 | }, 1432 | "universal-github-app-jwt": { 1433 | "version": "1.1.1", 1434 | "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.1.tgz", 1435 | "integrity": "sha512-G33RTLrIBMFmlDV4u4CBF7dh71eWwykck4XgaxaIVeZKOYZRAAxvcGMRFTUclVY6xoUPQvO4Ne5wKGxYm/Yy9w==", 1436 | "requires": { 1437 | "@types/jsonwebtoken": "^9.0.0", 1438 | "jsonwebtoken": "^9.0.0" 1439 | } 1440 | }, 1441 | "universal-user-agent": { 1442 | "version": "6.0.1", 1443 | "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", 1444 | "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" 1445 | }, 1446 | "v8-compile-cache-lib": { 1447 | "version": "3.0.1", 1448 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 1449 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" 1450 | }, 1451 | "wrappy": { 1452 | "version": "1.0.2", 1453 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1454 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1455 | }, 1456 | "yallist": { 1457 | "version": "4.0.0", 1458 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1459 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1460 | }, 1461 | "yn": { 1462 | "version": "3.1.1", 1463 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1464 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" 1465 | } 1466 | } 1467 | } 1468 | -------------------------------------------------------------------------------- /scripts/approve-dependabot-deploys/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "approve-dependabot-deploys", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "scripts": { 7 | "start": "ts-node-esm approve-dependabot-deploys.ts" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^16.0.3", 13 | "octokit": "^3.1.2", 14 | "ts-node": "^10.9.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /scripts/approve-dependabot-deploys/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "ES2022", 5 | "target": "ES2022", 6 | "noImplicitAny": false, 7 | "esModuleInterop": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /scripts/aws-oidc-role-cloudformation-template.yaml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/aws-actions/configure-aws-credentials#assuming-a-role 2 | Parameters: 3 | GitHubOrg: 4 | Type: String 5 | RepositoryName: 6 | Type: String 7 | OIDCProviderArn: 8 | Description: Arn for the GitHub OIDC Provider. 9 | Default: "" 10 | Type: String 11 | 12 | Conditions: 13 | CreateOIDCProvider: !Equals 14 | - !Ref OIDCProviderArn 15 | - "" 16 | 17 | Resources: 18 | Role: 19 | Type: AWS::IAM::Role 20 | Properties: 21 | RoleName: serverless-aws-static-file-handler-at-github 22 | AssumeRolePolicyDocument: 23 | Statement: 24 | - Effect: Allow 25 | Action: sts:AssumeRoleWithWebIdentity 26 | Principal: 27 | Federated: !If 28 | - CreateOIDCProvider 29 | - !Ref GithubOidc 30 | - !Ref OIDCProviderArn 31 | Condition: 32 | StringLike: 33 | token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/${RepositoryName}:* 34 | ManagedPolicyArns: 35 | - arn:aws:iam::aws:policy/AdministratorAccess 36 | 37 | GithubOidc: 38 | Type: AWS::IAM::OIDCProvider 39 | Condition: CreateOIDCProvider 40 | Properties: 41 | Url: https://token.actions.githubusercontent.com 42 | ClientIdList: 43 | - sts.amazonaws.com 44 | ThumbprintList: 45 | - 6938fd4d98bab03faadb97b34396831e3780aea1 46 | 47 | Outputs: 48 | Role: 49 | Value: !GetAtt Role.Arn 50 | -------------------------------------------------------------------------------- /scripts/aws-oidc-role-provision.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | THISDIR=$(cd $(dirname "$0"); pwd) #this script's directory 3 | THISSCRIPT=$(basename $0) 4 | 5 | GitHubOrg=activescott 6 | RepositoryName=serverless-aws-static-file-handler 7 | # Get OIDCProviderArn via `aws iam list-open-id-connect-providers` or at https://us-east-1.console.aws.amazon.com/iamv2/home#/identity_providers 8 | # Create one according to https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html#manage-oidc-provider-console 9 | # TODO: create it via cloudformation or CLI 10 | OIDCProviderArn=arn:aws:iam::166901232151:oidc-provider/token.actions.githubusercontent.com 11 | 12 | # us-east-1 since IAM is there anyway? 13 | AWS_REGION=us-east-1 14 | 15 | echo "using org '$GitHubOrg' and repo '$RepositoryName'." 16 | 17 | aws cloudformation deploy \ 18 | --region $AWS_REGION \ 19 | --template-file aws-oidc-role-cloudformation-template.yaml \ 20 | --stack-name aws-oidc-role-cloudformation \ 21 | --capabilities CAPABILITY_NAMED_IAM \ 22 | --parameter-overrides GitHubOrg=$GitHubOrg \ 23 | RepositoryName=$RepositoryName \ 24 | OIDCProviderArn=$OIDCProviderArn 25 | #--no-execute-changeset 26 | -------------------------------------------------------------------------------- /src/StaticFileHandler.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | const assert = require("assert") 3 | const fs = require("fs") 4 | const mimetypes = require("mime-types") 5 | const Mustache = require("mustache") 6 | const path = require("path") 7 | const util = require("util") 8 | const readFileAsync = util.promisify(fs.readFile) 9 | const accessAsync = util.promisify(fs.access) 10 | 11 | // originally from lodash, but never called with a defaultValue 12 | // https://gist.github.com/jeneg/9767afdcca45601ea44930ea03e0febf 13 | function __get(value, path, defaultValue) { 14 | return String(path) 15 | .split(".") 16 | .reduce((acc, v) => { 17 | if (v.startsWith("[")) { 18 | const [, arrPart] = v.split("[") 19 | v = arrPart.split("]")[0] 20 | } 21 | 22 | if (v.endsWith("]") && !v.startsWith("[")) { 23 | const [objPart, arrPart, ...rest] = v.split("[") 24 | const [firstIndex] = arrPart.split("]") 25 | const otherParts = rest 26 | .join("") 27 | .replaceAll("[", "") 28 | .replaceAll("]", ".") 29 | .split(".") 30 | .filter((str) => str !== "") 31 | 32 | return [...acc, objPart, firstIndex, ...otherParts] 33 | } 34 | 35 | return [...acc, v] 36 | }, []) 37 | .reduce((acc, v) => { 38 | try { 39 | acc = acc[v] !== undefined ? acc[v] : defaultValue 40 | } catch (e) { 41 | return defaultValue 42 | } 43 | 44 | return acc 45 | }, value) 46 | } 47 | 48 | class StaticFileHandler { 49 | /** 50 | * Initializes a new instance of @see StaticFileHandler 51 | * @param {*string} clientFilesPath The fully qualified path to the client files that this module should serve. 52 | * @param {*string} customErrorPagePath Optional path to a custom error page. Must be relative to @see clientFilesPath . 53 | */ 54 | constructor(clientFilesPath, customErrorPagePath = null) { 55 | if (clientFilesPath == null || clientFilesPath.length === 0) { 56 | throw new Error("clientFilesPath must be specified") 57 | } 58 | this.clientFilesPath = clientFilesPath 59 | this.customErrorPagePath = customErrorPagePath 60 | } 61 | 62 | static getMimeType(filePath) { 63 | return mimetypes.lookup(filePath) || "application/octet-stream" 64 | } 65 | 66 | static isBinaryType(mimeType) { 67 | const mimeCharset = mimetypes.charset(mimeType) 68 | /* Using https://w3techs.com/technologies/overview/character_encoding/all 69 | * to be more comprehensive go through those at https://www.iana.org/assignments/character-sets/character-sets.xhtml 70 | */ 71 | const textualCharSets = [ 72 | "UTF-8", 73 | "ISO-8859-1", 74 | "Windows-1251", 75 | "Windows-1252", 76 | "Shift_JIS", 77 | "GB2312", 78 | "EUC-KR", 79 | "ISO-8859-2", 80 | "GBK", 81 | "Windows-1250", 82 | "EUC-JP", 83 | "Big5", 84 | "ISO-8859-15", 85 | "Windows-1256", 86 | "ISO-8859-9", 87 | ] 88 | const found = textualCharSets.find( 89 | (cs) => 0 === cs.localeCompare(mimeCharset, "en", { sensitivity: "base" }) 90 | ) 91 | return found === undefined || found === null 92 | } 93 | 94 | async get(event, context) { 95 | if (!event) { 96 | throw new Error("event object not specified.") 97 | } 98 | 99 | if (event.rawPath) { 100 | // convert the V2 API to look like v1 like rest of code expects 101 | 102 | // this matches validateLambdaProxyIntegration required props 103 | event.resource = event.requestContext.http.path 104 | event.path = event.rawPath 105 | event.httpMethod = event.requestContext.http.method 106 | // OK as is event.headers 107 | event.multiValueHeaders = event.headers 108 | event.queryStringParameters = event.queryStringParamaters 109 | event.multiValueQueryStringParameters = event.queryStringParameters // Not sure what old code does ? 110 | // OK as is event.pathParameters 111 | event.stageVariables = event.requestContext.stage // Not sure we ever pass these ? 112 | // OK as is event.requestContext 113 | event.body = "" // It is a GET, there never is one ? 114 | // OK as is event.isBase64Encoded 115 | } 116 | 117 | if (!event.path) { 118 | throw new Error("Empty path.") 119 | } 120 | await StaticFileHandler.validateLambdaProxyIntegration(event) 121 | let requestPath 122 | if (event.pathParameters) { 123 | requestPath = "" 124 | /* 125 | * event.path is an object when `integration: lambda` and there is a greedy path parameter 126 | * If there are zero properties, it is just "lambda integration" and no path parameters 127 | * If there are properties, it indicates there are path parameters. 128 | * For example: The path parameter could be mapped like so in serverless.yml: 129 | * - http: 130 | path: fontsdir/{fonts+} 131 | * The {fonts+} in the path indicates the base path and tells APIG to pass along the whole path. 132 | */ 133 | // now enumerate the properties of it: 134 | let propNames = Object.getOwnPropertyNames(event.pathParameters) 135 | if (propNames.length === 0) { 136 | const msg = 137 | "The event.path is an object but there are no properties. Check serverless.yml." 138 | throw new Error(msg) 139 | } 140 | if (propNames.length !== 1) { 141 | const msg = `Expected exactly one property name, but found: ${util.inspect( 142 | propNames 143 | )}. Check that you configured the pathParameter in serverless.yml with a plus sign like \`path/{pathparam+}\`.` 144 | throw new Error(msg) 145 | } 146 | requestPath = "/" + event.pathParameters[propNames[0]] 147 | } else { 148 | assert(typeof event.path === "string", "expected path to be string") 149 | requestPath = event.path 150 | } 151 | let filePath = path.join(this.clientFilesPath, requestPath) 152 | return this.readFileAsResponse(filePath, context).catch((err) => { 153 | throw new Error( 154 | `Unable to read client file '${requestPath}'. Error: ${err}` 155 | ) 156 | }) 157 | } 158 | 159 | /** 160 | * Loads the specified file's content and returns a response that can be called back to lambda for sending the file as the http response. 161 | */ 162 | async readFileAsResponse(filePath, context, statusCode = 200) { 163 | let stream 164 | try { 165 | stream = await readFileAsync(filePath) 166 | } catch (err) { 167 | if (err.code === "ENOENT") { 168 | // NOTE: avoid leaking full local path 169 | const fileName = path.basename(filePath) 170 | return this.responseAsError(`File ${fileName} does not exist`, 404) 171 | } 172 | } 173 | let mimeType = StaticFileHandler.getMimeType(filePath) 174 | return StaticFileHandler.readStreamAsResponse( 175 | stream, 176 | context, 177 | statusCode, 178 | mimeType 179 | ) 180 | } 181 | 182 | static readStreamAsResponse(stream, context, statusCode, mimeType) { 183 | let body 184 | let isBase64Encoded = false 185 | if (StaticFileHandler.isBinaryType(mimeType)) { 186 | isBase64Encoded = true 187 | body = Buffer.from(stream).toString("base64") 188 | } else { 189 | body = stream.toString("utf8") 190 | } 191 | return StaticFileHandler.readStringAsResponse( 192 | body, 193 | context, 194 | statusCode, 195 | mimeType, 196 | isBase64Encoded 197 | ) 198 | } 199 | 200 | static readStringAsResponse( 201 | stringData, 202 | context, 203 | statusCode, 204 | mimeType, 205 | isBase64Encoded 206 | ) { 207 | assert(mimeType, "expected mimeType to always be provided") 208 | if ( 209 | context && 210 | "staticFileHandler" in context && 211 | "viewData" in context.staticFileHandler 212 | ) { 213 | const viewData = context.staticFileHandler.viewData 214 | stringData = Mustache.render(stringData, viewData) 215 | } 216 | const response = { 217 | statusCode: statusCode, 218 | headers: { 219 | "Content-Type": mimeType, 220 | }, 221 | isBase64Encoded, 222 | body: stringData, 223 | } 224 | return response 225 | } 226 | 227 | /** 228 | * Returns a Promise with a response that is an HTML page with the specified error text on it. 229 | * @param {*string} errorText The error to add to the page. 230 | */ 231 | async responseAsError(errorText, statusCode) { 232 | const context = { 233 | staticFileHandler: { 234 | viewData: { 235 | errorText: errorText, 236 | }, 237 | }, 238 | } 239 | if (this.customErrorPagePath) { 240 | let filePath = path.join(this.clientFilesPath, this.customErrorPagePath) 241 | try { 242 | await accessAsync(filePath, fs.constants.R_OK) 243 | return this.readFileAsResponse(filePath, context, statusCode) 244 | } catch (err) { 245 | console.warn( 246 | "serverless-aws-static-file-handler: Error using customErrorPagePath", 247 | this.customErrorPagePath, 248 | ". Using fallback error HTML." 249 | ) 250 | } 251 | } 252 | 253 | const DEFAULT_ERROR_HTML = ` 254 | 255 | 256 | 257 | Error 258 | 259 | 260 | 261 | {{errorText}} 262 | 263 | 264 | ` 265 | return StaticFileHandler.readStringAsResponse( 266 | DEFAULT_ERROR_HTML, 267 | context, 268 | statusCode, 269 | "text/html", 270 | false 271 | ) 272 | } 273 | 274 | /** 275 | * Rejects if the specified event is not Lambda Proxy integration 276 | */ 277 | static async validateLambdaProxyIntegration(event) { 278 | /* 279 | There are two different event schemas in API Gateway + Lambda Proxy APIs. One is known as "REST API" or the old V1 API and the newer one is the V2 or "HTTP API". 280 | Each are described at https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html#services-apigateway-apitypes 281 | You can see examples of each at https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html 282 | TLDR: 283 | - V2 has a { version: "2.0" } field and { requestContext.http.method: "..." } field. 284 | - V1 has a { version: "1.0" } field and { requestContext.httpMethod: "..." } field. 285 | To set each up in serverless.com: 286 | - V1: https://www.serverless.com/framework/docs/providers/aws/events/apigateway 287 | - V2: https://www.serverless.com/framework/docs/providers/aws/events/http-api 288 | */ 289 | function isV2ProxyAPI(evt) { 290 | return ( 291 | evt.version === "2.0" && 292 | typeof __get(evt, "requestContext.http.method") === "string" 293 | ) 294 | } 295 | function isV1ProxyAPI(evt) { 296 | return ( 297 | // docs say there is a .version but there isn't! 298 | // evt.version === "1.0" && 299 | typeof __get(evt, "requestContext.httpMethod") === "string" 300 | ) 301 | } 302 | // serverless-offline doesn't provide the `isBase64Encoded` prop, but does add the isOffline. Fixes issue #10: https://github.com/activescott/serverless-aws-static-file-handler/issues/10 303 | const isServerlessOfflineEnvironment = "isOffline" in event 304 | if (!isV1ProxyAPI(event) && !isV2ProxyAPI(event)) { 305 | const logProps = [ 306 | "version", 307 | "requestContext.httpMethod", 308 | "requestContext.http.method", 309 | ] 310 | const addendum = logProps 311 | .map((propName) => `event.${propName} was '${__get(event, propName)}'`) 312 | .join(" ") 313 | throw new Error( 314 | "API Gateway method does not appear to be setup for Lambda Proxy Integration. Please confirm that `integration` property of the http event is not specified or set to `integration: proxy`." + 315 | addendum 316 | ) 317 | } 318 | } 319 | } 320 | 321 | module.exports = StaticFileHandler 322 | -------------------------------------------------------------------------------- /src/plugins/BinaryMediaTypes.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | const util = require("util") 3 | const _ = require("lodash") 4 | 5 | class BinaryMediaTypes { 6 | constructor(serverless, options) { 7 | if (!serverless) { 8 | throw new Error("Expected serverless to be provided as argument") 9 | } 10 | this.serverless = serverless 11 | this.options = options 12 | this.provider = this.serverless.getProvider("aws") 13 | this.hooks = { 14 | "package:compileEvents": this.packageCompileEvents.bind(this), 15 | } 16 | } 17 | 18 | log(...args) { 19 | args.unshift("aws-static-file-handler (BinaryMediaTypes):") 20 | const msg = util.format(...args) 21 | this.serverless.cli.log(msg) 22 | } 23 | 24 | getRestApi() { 25 | const resources = 26 | this.serverless.service.provider.compiledCloudFormationTemplate.Resources 27 | return _.find(resources, (r) => r.Type === "AWS::ApiGateway::RestApi") 28 | } 29 | 30 | readConfig() { 31 | const service = this.serverless.service 32 | if ( 33 | !service.custom || 34 | !service.custom.apiGateway || 35 | !service.custom.apiGateway.binaryMediaTypes || 36 | _.isEmpty(service.custom.apiGateway.binaryMediaTypes) 37 | ) { 38 | throw new Error(BinaryMediaTypes.Strings.CONFIG_ERROR) 39 | } 40 | return service.custom.apiGateway.binaryMediaTypes 41 | } 42 | 43 | addBinaryMediaTypes(restApi) { 44 | if (!restApi) { 45 | this.log( 46 | "Amazon API Gateway RestApi resource not found. No BinaryMediaTypes will be added." 47 | ) 48 | return 49 | } 50 | if (!restApi.Properties) { 51 | throw new Error("RestApi Properties property does not exist!") 52 | } 53 | // see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-binarymediatypes 54 | const newTypes = this.readConfig() 55 | this.log("Adding the following BinaryMediaTypes to RestApi:", newTypes) 56 | const oldTypes = restApi.Properties["BinaryMediaTypes"] || [] 57 | const combined = _.concat(oldTypes, newTypes) 58 | restApi.Properties["BinaryMediaTypes"] = combined 59 | this.log("RestApi BinaryMediaTypes are now:", combined) 60 | } 61 | 62 | packageCompileEvents() { 63 | this.log("Preparing to add binary media types (package:compileEvents)...") 64 | const restApi = this.getRestApi() 65 | this.addBinaryMediaTypes(restApi) 66 | } 67 | } 68 | 69 | BinaryMediaTypes.Strings = { 70 | CONFIG_ERROR: 71 | "No BinaryMediaTypes configured. See https://github.com/activescott/serverless-aws-static-file-handler#usage for information on how to configure", 72 | } 73 | 74 | module.exports = BinaryMediaTypes 75 | -------------------------------------------------------------------------------- /src/test/BinaryMediaTypes.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const chai = require("chai") 4 | const chaiAsPromised = require("chai-as-promised") 5 | chai.use(chaiAsPromised) 6 | const expect = chai.expect 7 | const sinon = require("sinon") 8 | 9 | const BinaryMediaTypes = require("../plugins/BinaryMediaTypes") 10 | 11 | describe("BinaryMediaTypes", function () { 12 | afterEach(() => { 13 | // Restore the default sandbox here 14 | sinon.restore() 15 | }) 16 | 17 | describe("source path", function () { 18 | it("should load from '/plugins/BinaryMediaTypes'", () => { 19 | // this is the proper path 20 | require("../../plugins/BinaryMediaTypes") 21 | }) 22 | 23 | it("should load from 'src/plugins/BinaryMediaTypes'", () => { 24 | // this was inadvertently introduced in v2.0.3 per https://github.com/activescott/serverless-aws-static-file-handler/issues/32 25 | require("../plugins/BinaryMediaTypes") 26 | }) 27 | }) 28 | 29 | describe("constructor", function () { 30 | it("should not allow empty serverless arg", function () { 31 | expect(() => new BinaryMediaTypes()).to.throw( 32 | /Expected serverless to be provided as argument/ 33 | ) 34 | }) 35 | }) 36 | 37 | describe("package:compileEvents", function () { 38 | let logSpy 39 | beforeEach(() => { 40 | //logSpy = sinon.spy(console.log) 41 | logSpy = sinon.spy() 42 | }) 43 | 44 | function createPlugin(restApi) { 45 | const provider = { 46 | compiledCloudFormationTemplate: { 47 | Resources: [], 48 | }, 49 | } 50 | provider.compiledCloudFormationTemplate.Resources.push(restApi) 51 | const serverless = { 52 | getProvider: (str) => { 53 | str === "aws" ? provider : null 54 | }, 55 | cli: { 56 | log: logSpy, 57 | }, 58 | service: { 59 | provider, 60 | }, 61 | } 62 | return new BinaryMediaTypes(serverless) 63 | } 64 | 65 | function createRestApi() { 66 | return { 67 | Type: "AWS::ApiGateway::RestApi", 68 | Properties: [], 69 | } 70 | } 71 | 72 | const getHook = (plugin) => plugin.hooks["package:compileEvents"] 73 | 74 | it("should gracefully fail with logging when no RestApi", function () { 75 | const api = createRestApi() 76 | const p = createPlugin(api) 77 | // remove Rest API from service (maybe user isn't using http events/APIG?): 78 | p.serverless.service.provider.compiledCloudFormationTemplate.Resources = 79 | [] 80 | const hook = getHook(p) 81 | expect(hook).to.not.throw() 82 | expect(logSpy.callCount).to.equal(2) 83 | expect( 84 | logSpy.calledWithExactly( 85 | sinon.match( 86 | /Amazon API Gateway RestApi resource not found. No BinaryMediaTypes will be added.$/ 87 | ) 88 | ) 89 | ).to.be.true 90 | }) 91 | 92 | it("should ungracefully fail when RestApi has no Properties property", function () { 93 | const api = createRestApi() 94 | delete api.Properties 95 | const p = createPlugin(api) 96 | const hook = getHook(p) 97 | expect(hook).to.throw(/RestApi Properties property does not exist/) 98 | }) 99 | 100 | describe("service configuration", function () { 101 | /** 102 | * expecting serverless.yml yaml: 103 | * custom: 104 | * apiGateway: 105 | * binaryMediaTypes: 106 | * - image/png 107 | * - application/octet-stream 108 | * ... 109 | * Plugin is expecting to read this like: expecting: serverless.service.custom.apiGateway.binaryMediatypes: [] 110 | */ 111 | const CONFIG_ERROR_REGEX = 112 | /github\.com\/activescott\/serverless\-aws\-static\-file\-handler\#usage for information on how to configure$/ 113 | it("should throw helpful messages when no custom config", function () { 114 | const api = createRestApi() 115 | const p = createPlugin(api) 116 | p.serverless.service.custom = null 117 | const hook = getHook(p) 118 | expect(hook).to.throw(CONFIG_ERROR_REGEX) 119 | }) 120 | 121 | it("should throw helpful messages when no apiGateway config", function () { 122 | const api = createRestApi() 123 | const p = createPlugin(api) 124 | p.serverless.service.custom = {} 125 | const hook = getHook(p) 126 | expect(hook).to.throw(CONFIG_ERROR_REGEX) 127 | }) 128 | 129 | it("should throw helpful messages when no binaryMediatypes config", function () { 130 | const api = createRestApi() 131 | const p = createPlugin(api) 132 | p.serverless.service.custom = { 133 | apiGateway: {}, 134 | } 135 | const hook = getHook(p) 136 | expect(hook).to.throw(CONFIG_ERROR_REGEX) 137 | }) 138 | 139 | it("should throw helpful messages when binaryMediatypes config null", function () { 140 | const api = createRestApi() 141 | const p = createPlugin(api) 142 | p.serverless.service.custom = { 143 | apiGateway: { 144 | binaryMediaTypes: null, 145 | }, 146 | } 147 | const hook = getHook(p) 148 | expect(hook).to.throw(CONFIG_ERROR_REGEX) 149 | }) 150 | 151 | it("should throw helpful messages when binaryMediatypes config empty", function () { 152 | const api = createRestApi() 153 | const p = createPlugin(api) 154 | p.serverless.service.custom = { 155 | apiGateway: { 156 | binaryMediaTypes: [], 157 | }, 158 | } 159 | const hook = getHook(p) 160 | expect(hook).to.throw(CONFIG_ERROR_REGEX) 161 | }) 162 | 163 | it("should read configured media types", function () { 164 | const api = createRestApi() 165 | const p = createPlugin(api) 166 | p.serverless.service.custom = { 167 | apiGateway: { 168 | binaryMediaTypes: ["image/png", "image/jpeg"], 169 | }, 170 | } 171 | const hook = getHook(p) 172 | hook() 173 | expect(api.Properties.BinaryMediaTypes).to.deep.equal([ 174 | "image/png", 175 | "image/jpeg", 176 | ]) 177 | }) 178 | }) 179 | 180 | it("should not overwrite any existing media types in stack", function () { 181 | const api = createRestApi() 182 | api.Properties.BinaryMediaTypes = ["image/jpeg"] 183 | const p = createPlugin(api) 184 | p.serverless.service.custom = { 185 | apiGateway: { 186 | binaryMediaTypes: ["image/png"], 187 | }, 188 | } 189 | const hook = getHook(p) 190 | hook() 191 | expect(api.Properties.BinaryMediaTypes).to.deep.equal([ 192 | "image/jpeg", 193 | "image/png", 194 | ]) 195 | }) 196 | 197 | it("should not add duplicates to existing media types in stack", function () { 198 | const api = createRestApi() 199 | api.Properties = ["image/jpeg"] 200 | const p = createPlugin(api) 201 | p.serverless.service.custom = { 202 | apiGateway: { 203 | binaryMediaTypes: ["image/jpeg"], 204 | }, 205 | } 206 | const hook = getHook(p) 207 | hook() 208 | expect(api.Properties.BinaryMediaTypes).to.deep.equal(["image/jpeg"]) 209 | }) 210 | }) 211 | }) 212 | -------------------------------------------------------------------------------- /src/test/StaticFileHandler.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint-disable padded-blocks */ 3 | "use strict" 4 | 5 | const chai = require("chai") 6 | const chaiAsPromised = require("chai-as-promised") 7 | chai.use(chaiAsPromised) 8 | const expect = chai.expect 9 | 10 | const path = require("path") 11 | const StaticFileHandler = require("../StaticFileHandler.js") 12 | 13 | const STATIC_FILES_PATH = path.join(__dirname, "./data/testfiles/") 14 | 15 | function mockEvent(event) { 16 | return { 17 | resource: null, 18 | httpMethod: "GET", 19 | requestContext: { 20 | httpMethod: "GET", 21 | }, 22 | headers: {}, 23 | multiValueHeaders: {}, 24 | queryStringParameters: null, 25 | multiValueQueryStringParameters: null, 26 | pathParameters: null, 27 | stageVariables: null, 28 | body: null, 29 | isBase64Encoded: false, 30 | ...event, 31 | } 32 | } 33 | 34 | // https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html 35 | function mockEventV2(path, event) { 36 | return { 37 | version: "2.0", 38 | routeKey: "$default", 39 | rawPath: path, 40 | requestContext: { 41 | http: { 42 | method: "GET", 43 | path: path, 44 | }, 45 | }, 46 | ...event, 47 | } 48 | } 49 | 50 | describe("StaticFileHandler", function () { 51 | describe("constructor", function () { 52 | it("should not allow empty arg", function () { 53 | expect(() => new StaticFileHandler()).to.throw( 54 | /^clientFilesPath must be specified$/ 55 | ) 56 | }) 57 | 58 | it("should accept string arg", function () { 59 | expect(() => new StaticFileHandler("some/path")).to.not.throw(Error) 60 | }) 61 | }) 62 | 63 | describe("get", function () { 64 | it("should return index.html", function () { 65 | const event = mockEvent({ path: "index.html" }) 66 | let h = new StaticFileHandler(STATIC_FILES_PATH) 67 | return h.get(event, null).then((response) => { 68 | expect(response).to.have.property("statusCode", 200) 69 | expect(response) 70 | .to.have.property("body") 71 | .to.match(/^/) 72 | return response 73 | }) 74 | }) 75 | 76 | it("should validate event exist", function () { 77 | const event = null 78 | let h = new StaticFileHandler(STATIC_FILES_PATH) 79 | return expect(h.get(event, null)).to.be.rejectedWith( 80 | /event object not specified.$/ 81 | ) 82 | }) 83 | 84 | it("should validate event.path", function () { 85 | const event = mockEvent({ NOPATH: "index.html" }) 86 | let h = new StaticFileHandler(STATIC_FILES_PATH) 87 | return expect(h.get(event, null)).to.be.rejectedWith(/Empty path.$/) 88 | }) 89 | 90 | it.skip("(integration:lambda no longer supported) should work with non-lambdaproxy requests", function () { 91 | const event = { 92 | path: { 93 | fonts: "fonts/glyphicons-halflings-regular.woff2", 94 | }, 95 | } 96 | let h = new StaticFileHandler(STATIC_FILES_PATH) 97 | return h.get(event, null).then((response) => { 98 | return expect(response.body.length).to.equal(24040) 99 | }) 100 | }) 101 | 102 | it("integration:lambda no longer supported; should fail if not using lambda-proxy", function () { 103 | const event = { 104 | path: { 105 | fonts: "fonts/glyphicons-halflings-regular.woff2", 106 | }, 107 | } 108 | let h = new StaticFileHandler(STATIC_FILES_PATH) 109 | return expect(h.get(event, null)).to.be.rejectedWith( 110 | /^API Gateway method does not appear to be setup for Lambda Proxy Integration/ 111 | ) 112 | }) 113 | 114 | it("should succeed in serverless-offline environment", function () { 115 | // see issue #10: 116 | const event = mockEvent({ path: "README.md" }) 117 | delete event["isBase64Encoded"] 118 | event["isOffline"] = true 119 | let h = new StaticFileHandler(STATIC_FILES_PATH) 120 | return h.get(event, null).then((response) => { 121 | let expectedContent = "This directory is not empty. Is it?\n" 122 | return expect(response.body).to.equal(expectedContent) 123 | }) 124 | }) 125 | 126 | it("should return text as text", function () { 127 | const event = mockEvent({ path: "README.md" }) 128 | let h = new StaticFileHandler(STATIC_FILES_PATH) 129 | return h.get(event, null).then((response) => { 130 | let expectedContent = "This directory is not empty. Is it?\n" 131 | return expect(response.body).to.equal(expectedContent) 132 | }) 133 | }) 134 | 135 | it("should insert viewdata", function () { 136 | const event = mockEvent({ path: "index.html" }) 137 | let h = new StaticFileHandler(STATIC_FILES_PATH) 138 | const context = { 139 | staticFileHandler: { 140 | viewData: { 141 | csrftoken: "MY_FAKE_CSRF_TOKEN", 142 | }, 143 | }, 144 | } 145 | return h.get(event, context).then((response) => { 146 | return expect(response.body).to.match( 147 | /.*CUSTOM<\/title>/) 204 | }) 205 | }) 206 | 207 | /** 208 | * This is to support a greedy path parameter like: 209 | * ``` 210 | * events: 211 | * - http: 212 | * path: /binary/{pathvar+} 213 | * ``` 214 | */ 215 | it("should support path parameters", function () { 216 | const event = mockEvent({ 217 | path: "/binary/vendor/output.css.map", 218 | pathParameters: { pathvar: "vendor/output.css.map" }, 219 | }) 220 | let h = new StaticFileHandler(STATIC_FILES_PATH) 221 | const response = h.get(event, null) 222 | expect(response) 223 | .to.eventually.have.ownProperty("statusCode") 224 | .that.equals(200) 225 | return expect(response) 226 | .to.eventually.haveOwnProperty("body") 227 | .that.is.a("string") 228 | .and.has.length(107) 229 | }) 230 | 231 | it("should return 404 with path parameters", function () { 232 | const event = mockEvent({ 233 | path: "/binary/does-not-exist.file", 234 | pathParameters: { pathvar: "vendor/does-not-exist.file" }, 235 | }) 236 | let h = new StaticFileHandler(STATIC_FILES_PATH) 237 | const response = h.get(event, null) 238 | return expect(response) 239 | .to.eventually.haveOwnProperty("statusCode") 240 | .that.equals(404) 241 | }) 242 | 243 | /** 244 | * This is a `integration: lambda` test WITH NO path parameters. 245 | * Defined in serverless.yml something like: 246 | * 247 | * myfunc: 248 | * handler: handler.myfunc 249 | * events: 250 | * - http: 251 | * path: /no_path_params_here 252 | * method: get 253 | * integration: lambda 254 | * contentHandling: CONVERT_TO_BINARY 255 | */ 256 | it.skip("(integration:lambda no longer supported) should error on lambda integration without path parameters", function () { 257 | const event = { 258 | body: {}, 259 | method: "GET", 260 | stage: "dev", 261 | headers: { Accept: "*/*" }, 262 | query: {}, 263 | path: {}, // <<< this is the critical input to this test. It is an object (indicating lambda integration), but no properties. 264 | } 265 | const context = {} 266 | const h = new StaticFileHandler(STATIC_FILES_PATH) 267 | return expect(h.get(event, context)).to.eventually.be.rejectedWith( 268 | /The event.path is an object but there are no properties. This likely means it is a lambda integration but there are no path parameters\/variables defined. Check your serverless.yml./ 269 | ) 270 | }) 271 | 272 | describe("MIME Types", function () { 273 | it("js.map => application/json", async function () { 274 | const event = mockEvent({ path: "vendor/output.js.map" }) 275 | let h = new StaticFileHandler(STATIC_FILES_PATH) 276 | const response = await h.get(event, null) 277 | expect(response).to.haveOwnProperty("statusCode").that.equals(200) 278 | expect(response) 279 | .to.have.property("headers") 280 | .that.has.property("Content-Type") 281 | .that.equals("application/json") 282 | expect(response).to.have.property("isBase64Encoded").that.equals(false) 283 | return response 284 | }) 285 | 286 | it("css.map => application/json", async function () { 287 | const event = mockEvent({ path: "vendor/output.css.map" }) 288 | let h = new StaticFileHandler(STATIC_FILES_PATH) 289 | const response = await h.get(event, null) 290 | expect(response).to.haveOwnProperty("statusCode").that.equals(200) 291 | expect(response) 292 | .to.have.property("headers") 293 | .that.has.property("Content-Type") 294 | .that.equals("application/json") 295 | expect(response).to.have.property("isBase64Encoded").that.equals(false) 296 | return response 297 | }) 298 | 299 | it("unknown-mime-type => application/octet-stream", async function () { 300 | const event = mockEvent({ path: "unknown-mime-type.unknowntype" }) 301 | let h = new StaticFileHandler(STATIC_FILES_PATH) 302 | const response = await h.get(event, null) 303 | expect(response).to.haveOwnProperty("statusCode").that.equals(200) 304 | expect(response) 305 | .to.have.property("headers") 306 | .that.has.property("Content-Type") 307 | .that.equals("application/octet-stream") 308 | expect(response).to.have.property("isBase64Encoded").that.equals(true) 309 | return response 310 | }) 311 | 312 | describe("Binary types are also base64", async function () { 313 | it(".png => image/png", async function () { 314 | const event = mockEvent({ path: "png.png" }) 315 | let h = new StaticFileHandler(STATIC_FILES_PATH) 316 | const response = await h.get(event, null) 317 | expect(response).to.haveOwnProperty("statusCode").that.equals(200) 318 | expect(response).to.have.property("headers") 319 | expect(response.headers) 320 | .to.have.property("Content-Type") 321 | .that.equals("image/png") 322 | expect(response).to.have.property("isBase64Encoded").that.equals(true) 323 | return response 324 | }) 325 | 326 | it(".jpg => image/jpeg", async function () { 327 | const event = mockEvent({ path: "jpg.jpg" }) 328 | let h = new StaticFileHandler(STATIC_FILES_PATH) 329 | const response = await h.get(event, null) 330 | expect(response).to.haveOwnProperty("statusCode").that.equals(200) 331 | expect(response).to.have.property("headers") 332 | expect(response.headers) 333 | .to.have.property("Content-Type") 334 | .that.equals("image/jpeg") 335 | expect(response).to.have.property("isBase64Encoded").that.equals(true) 336 | return response 337 | }) 338 | 339 | it(".woff2 => font/woff2", async function () { 340 | const event = mockEvent({ 341 | path: "fonts/glyphicons-halflings-regular.woff2", 342 | }) 343 | let h = new StaticFileHandler(STATIC_FILES_PATH) 344 | const response = await h.get(event, null) 345 | expect(response).to.haveOwnProperty("statusCode").that.equals(200) 346 | expect(response).to.have.property("headers") 347 | expect(response.headers) 348 | .to.have.property("Content-Type") 349 | .that.equals("font/woff2") 350 | expect(response).to.have.property("isBase64Encoded").that.equals(true) 351 | return response 352 | }) 353 | 354 | it(".bin", async function () { 355 | const event = mockEvent({ path: "blah.bin" }) 356 | let h = new StaticFileHandler(STATIC_FILES_PATH) 357 | const response = await h.get(event, null) 358 | expect(response).to.haveOwnProperty("statusCode").that.equals(200) 359 | expect(response).to.have.property("isBase64Encoded").that.equals(true) 360 | return response 361 | }) 362 | }) 363 | }) 364 | }) 365 | 366 | describe("get (httpApi v2)", function () { 367 | it("should return index.html", function () { 368 | const event = mockEventV2("index.html") 369 | let h = new StaticFileHandler(STATIC_FILES_PATH) 370 | return h.get(event, null).then((response) => { 371 | expect(response).to.have.property("statusCode", 200) 372 | expect(response) 373 | .to.have.property("body") 374 | .to.match(/^/) 375 | return response 376 | }) 377 | }) 378 | it("should support path parameters", function () { 379 | const event = mockEventV2("/binary/vendor/output.css.map", { 380 | pathParameters: { pathvar: "vendor/output.css.map" }, 381 | }) 382 | let h = new StaticFileHandler(STATIC_FILES_PATH) 383 | const response = h.get(event, null) 384 | expect(response) 385 | .to.eventually.have.ownProperty("statusCode") 386 | .that.equals(200) 387 | return expect(response) 388 | .to.eventually.haveOwnProperty("body") 389 | .that.is.a("string") 390 | .and.has.length(107) 391 | }) 392 | }) 393 | }) 394 | -------------------------------------------------------------------------------- /src/test/data/testfiles/README.md: -------------------------------------------------------------------------------- 1 | This directory is not empty. Is it? 2 | -------------------------------------------------------------------------------- /src/test/data/testfiles/blah.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/src/test/data/testfiles/blah.bin -------------------------------------------------------------------------------- /src/test/data/testfiles/custom-error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CUSTOM 6 | 7 | 8 | 9 | CUSTOM {{errorText}} 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/test/data/testfiles/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/src/test/data/testfiles/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/test/data/testfiles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/test/data/testfiles/jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/src/test/data/testfiles/jpg.jpg -------------------------------------------------------------------------------- /src/test/data/testfiles/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/src/test/data/testfiles/png.png -------------------------------------------------------------------------------- /src/test/data/testfiles/unknown-mime-type.unknowntype: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/src/test/data/testfiles/unknown-mime-type.unknowntype -------------------------------------------------------------------------------- /src/test/data/testfiles/vendor/output.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["input.less"],"names":[],"mappings":"AAGA;EACE,WAAA;EACA,YAAA","file":"output.css"} -------------------------------------------------------------------------------- /src/test/data/testfiles/vendor/output.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"hello.js","sourceRoot":"","sources":["hello.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA"} -------------------------------------------------------------------------------- /src/test/e2e.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const chai = require("chai") 4 | const chaiAsPromised = require("chai-as-promised") 5 | chai.use(chaiAsPromised) 6 | const expect = chai.expect 7 | const path = require("path") 8 | const { spawnSync } = require("child_process") 9 | const { versions } = require("process") 10 | 11 | function nodeVersionMajor() /* : Number */ { 12 | return Number(versions.node.split(".")[0]) 13 | } 14 | 15 | describe("e2e", function () { 16 | // npm install & serverless loading a project is pretty slow (apparently damn slow on node8: https://travis-ci.org/activescott/serverless-aws-static-file-handler/jobs/632405805?utm_medium=notification&utm_source=github_status) 17 | this.timeout(60000) 18 | 19 | it("should load plugin", function () { 20 | // Serverless Framework v3 does not support Node.js v10. Please upgrade Node.js to the latest LTS version (v12 is a minimum supported version): 21 | if (nodeVersionMajor() < 20) { 22 | console.warn( 23 | "Serverless Framework v3 does not support Node.js v10, skipping test" 24 | ) 25 | this.skip() 26 | return 27 | } 28 | // does a simple load of a plugin per https://github.com/activescott/serverless-aws-static-file-handler/issues/32 29 | const proc = loadServerless("../../test-files/basic-project") 30 | expect(proc).to.haveOwnProperty("status", 0) 31 | }) 32 | }) 33 | 34 | function loadServerless(projectDir) { 35 | // serverless print will load all the plugins and fail if they fail to load 36 | const cwd = path.join(__dirname, projectDir) 37 | // first run npm install 38 | console.log("Running npm install for project at '" + projectDir + "'...") 39 | const npmProc = spawnSync("npm", ["install"], { 40 | cwd: cwd, 41 | }) 42 | if (npmProc.status !== 0) { 43 | console.error( 44 | "npm install failed for project '" + projectDir + "'.\nsdtout:", 45 | npmProc.stdout.toString(), 46 | "\n stderr:", 47 | npmProc.stderr.toString() 48 | ) 49 | return npmProc 50 | } 51 | console.log( 52 | "Running npm install for project at '" + projectDir + "' complete." 53 | ) 54 | const slsProc = spawnSync("./node_modules/.bin/serverless", ["print"], { 55 | cwd: cwd, 56 | }) 57 | if (slsProc.status !== 0) { 58 | console.error("proc.stdout:", slsProc.stdout.toString()) 59 | console.error("proc.stderr:", slsProc.stderr.toString()) 60 | } 61 | return slsProc 62 | } 63 | -------------------------------------------------------------------------------- /test-files/basic-project/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | -------------------------------------------------------------------------------- /test-files/basic-project/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /test-files/basic-project/README.md: -------------------------------------------------------------------------------- 1 | This is a test project that is only used for the internal e2e tests. 2 | -------------------------------------------------------------------------------- /test-files/basic-project/data-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | These should all load and either be a valid image or a font: 9 |
10 | 11 | 67 | 68 | -------------------------------------------------------------------------------- /test-files/basic-project/data-files/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/test-files/basic-project/data-files/png.png -------------------------------------------------------------------------------- /test-files/basic-project/handler.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | const path = require("path") 3 | const StaticFileHandler = require("serverless-aws-static-file-handler") 4 | const clientFilesPath = path.join(__dirname, "./data-files/") 5 | const fileHandler = new StaticFileHandler(clientFilesPath) 6 | 7 | module.exports.html = async (event, context) => { 8 | event.path = "index.html" // forcing a specific page for this handler; ignore requested path 9 | return fileHandler.get(event, context) 10 | } 11 | 12 | module.exports.png = async (event, context) => { 13 | event.path = "png.png" 14 | return fileHandler.get(event, context) 15 | } 16 | -------------------------------------------------------------------------------- /test-files/basic-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-file-handler-demo-proxy", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "deploy": "serverless deploy", 8 | "destroy": "serverless remove" 9 | }, 10 | "devDependencies": { 11 | "serverless": "^3.1.1" 12 | }, 13 | "dependencies": { 14 | "serverless-aws-static-file-handler": "file:../../serverless-aws-static-file-handler-0.0.0.tgz" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test-files/basic-project/serverless.yml: -------------------------------------------------------------------------------- 1 | service: static-file-handler-test-basic 2 | 3 | plugins: 4 | - serverless-aws-static-file-handler/plugins/BinaryMediaTypes 5 | 6 | custom: 7 | apiGateway: 8 | binaryMediaTypes: 9 | # You can use the wildcard character (*) to cover multiple media types per https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html 10 | # NOTE: Using */* has a side effect as noted at https://github.com/activescott/serverless-aws-static-file-handler 11 | # IANA descrete type wildcards from: https://www.iana.org/assignments/media-types/media-types.xhtml 12 | - application/* 13 | - audio/* 14 | - font/* 15 | - image/* 16 | - video/* 17 | 18 | provider: 19 | name: aws 20 | runtime: nodejs20.x 21 | 22 | functions: 23 | html: 24 | handler: handler.html 25 | events: 26 | - http: 27 | path: / 28 | method: get 29 | png: 30 | handler: handler.png 31 | events: 32 | - http: 33 | path: png 34 | method: get 35 | -------------------------------------------------------------------------------- /test-files/scripts/test-http.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | ## allows curl to some known HTTP endpoint and confirm an expected status code 3 | URL_PATH=$1 4 | if [ -z "$URL_PATH" ]; then 5 | echo "path not specified" 6 | return 1 7 | fi 8 | EXPECT_CODE=$2 9 | if [ -z "$EXPECT_CODE" ]; then 10 | echo "EXPECT_CODE not specified; defaulting to 200" 11 | EXPECT_CODE=200 12 | fi 13 | 14 | echo "Testing $URL_PATH for code $EXPECT_CODE..." 15 | HTTP_CODE=$(curl -s -w '%{http_code}' --compressed --output /dev/null "$URL_PATH") 16 | if [ "$HTTP_CODE" = "$EXPECT_CODE" ]; then 17 | echo "Testing $URL_PATH succeeded (returned expected code $HTTP_CODE)" 18 | exit 0 19 | else 20 | echo "\n˅˅˅˅˅ FAILURE ˅˅˅˅˅" 21 | echo "*** $URL_PATH *FAILED*. . Expected to have code $EXPECT_CODE but was $HTTP_CODE ***" 22 | echo "^^^^^ FAILURE ^^^^^\n" 23 | exit 1 24 | fi 25 | -------------------------------------------------------------------------------- /test-files/scripts/test-local-e2e.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | THISDIR=$(cd $(dirname "$0"); pwd) #this script's directory 3 | 4 | ################################################## 5 | # Runs an end-to-end test locally using serverless-offline 6 | # TLDR; this test curls to some known good endpoints looking for an expected HTTP response code: 7 | ################################################## 8 | 9 | die () { 10 | echo >&2 "$@" 11 | stop_serverless_offline 12 | help 13 | exit 1 14 | } 15 | 16 | help () { 17 | echo 18 | cat << END_DOC 19 | The script $THISSCRIPT failed. This likely means the e2e tests failed. See output above to confirm. 20 | END_DOC 21 | 22 | } 23 | 24 | REPO_ROOT_DIR=$(cd "$THISDIR/../.." ; pwd) 25 | echo "Using REPO_ROOT_DIR $REPO_ROOT_DIR" 26 | 27 | cd "$REPO_ROOT_DIR/examples/serverless-offline" 28 | 29 | echo "Running npm install..." 30 | npm install -s 31 | echo "Running npm install completed." 32 | 33 | # run severless-offline in background: 34 | start_serverless_offline() { 35 | echo "starting serverless-offline in background..." 36 | ./node_modules/.bin/serverless offline & 37 | 38 | # wait on serverless-offline to start 39 | SECS=10 40 | while [ $SECS -gt 0 ]; do 41 | printf "waiting for $SECS seconds for serverless-offline to start serving...\n" 42 | sleep 1 43 | SECS=`expr $SECS - 1` 44 | done 45 | printf "waiting for seconds for serverless-offline complete. Continuing with test\n" 46 | } 47 | 48 | start_serverless_offline 49 | 50 | stop_serverless_offline() { 51 | # shut down serverles offline now 52 | echo "Killing serverless offline process..." 53 | pkill -f -n "serverless offline" 54 | # sleep for a sec just to get clean output due to the background process 55 | sleep 1 56 | echo "Killing serverless offline process complete." 57 | } 58 | 59 | ROOT_URL=http://localhost:3000/dev 60 | 61 | test_url() { 62 | TEST_URL=$1 63 | TEST_HTTP_RESP_CODE=$2 64 | echo "testing URL '$TEST_URL' with HTTP response code '$TEST_HTTP_RESP_CODE'" 65 | $REPO_ROOT_DIR/test-files/scripts/test-http.sh $TEST_URL $TEST_HTTP_RESP_CODE || die "\n*FAILURE* testing URL '$TEST_URL' with HTTP response code '$TEST_HTTP_RESP_CODE'!" 66 | } 67 | 68 | test_url $ROOT_URL/binary/jpg.jpg 69 | 70 | # 200; these all should succeed 71 | test_url $ROOT_URL/binary/png.png 72 | test_url $ROOT_URL/binary/jpg.jpg 73 | test_url $ROOT_URL/binary/glyphicons-halflings-regular.woff2 74 | test_url $ROOT_URL/binary/subdir/png.png 75 | 76 | # 403 for APIG, but 404 for serverless-offline 77 | test_url "$ROOT_URL/ff404.png" 404 78 | test_url "$ROOT_URL/jpeg404.jpg" 404 79 | test_url "$ROOT_URL/subdir404/ff.png" 404 80 | test_url "$ROOT_URL/subdir/ff404.png" 404 81 | 82 | # 404 83 | test_url "$ROOT_URL/binary/404-glyphicons-halflings-regular.woff2" 404 84 | test_url "$ROOT_URL/binary/subdir/404-png.png" 404 85 | 86 | stop_serverless_offline 87 | -------------------------------------------------------------------------------- /test-files/webpack-project/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "presets": [ 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "node": "12" 9 | } 10 | } 11 | ] 12 | ], 13 | "plugins": [ 14 | "source-map-support" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test-files/webpack-project/.gitignore: -------------------------------------------------------------------------------- 1 | .webpack/ -------------------------------------------------------------------------------- /test-files/webpack-project/README.md: -------------------------------------------------------------------------------- 1 | # webpack example for serverless-aws-static-file-handler 2 | 3 | Things can be a bit different when packed such as paths to files as noted in [issue #109](https://github.com/activescott/serverless-aws-static-file-handler/issues/109). This example/test is a repro of essentially that issue. 4 | Shamelessly stolen from https://github.com/serverless-heaven/serverless-webpack/tree/master/examples/babel-webpack-4 and ever-so-slightly modified to include serverless-aws-static-file-handler. 5 | -------------------------------------------------------------------------------- /test-files/webpack-project/data-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | These should all load and either be a valid image or a font: 9 |
10 | 11 | 67 | 68 | -------------------------------------------------------------------------------- /test-files/webpack-project/data-files/png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/activescott/serverless-aws-static-file-handler/e1435cebaa3d3348caeb93edde14154c39a92a21/test-files/webpack-project/data-files/png.png -------------------------------------------------------------------------------- /test-files/webpack-project/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "resource": null, 3 | "httpMethod": "GET", 4 | "headers": {}, 5 | "multiValueHeaders": {}, 6 | "queryStringParameters": null, 7 | "multiValueQueryStringParameters": null, 8 | "pathParameters": null, 9 | "stageVariables": null, 10 | "requestContext": {}, 11 | "body": null, 12 | "isBase64Encoded": false 13 | } 14 | -------------------------------------------------------------------------------- /test-files/webpack-project/handler.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | const path = require("path") 3 | const StaticFileHandler = require("serverless-aws-static-file-handler") 4 | const clientFilesPath = path.join(__dirname, "./data-files/") 5 | const fileHandler = new StaticFileHandler(clientFilesPath) 6 | 7 | // require.context triggers file-loader to copy the data-files directory. see https://webpack.js.org/guides/dependency-management/#requirecontext 8 | require.context("./data-files") 9 | 10 | export const html = async (event, context) => { 11 | event.path = "index.html" // forcing a specific page for this handler; ignore requested path 12 | return fileHandler.get(event, context) 13 | } 14 | 15 | export const png = async (event, context) => { 16 | event.path = "png.png" 17 | return fileHandler.get(event, context) 18 | } 19 | 20 | export const notfound = async (event, context) => { 21 | event.path = "notfound.html" 22 | return fileHandler.get(event, context) 23 | } 24 | -------------------------------------------------------------------------------- /test-files/webpack-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-aws-static-file-handler-serverless-webpack-example", 3 | "version": "1.0.0", 4 | "description": "serverless-aws-static-file-handler and serverless-webpack example", 5 | "scripts": { 6 | "html": "serverless invoke local --function=html --path=./event.json", 7 | "png": "serverless invoke local --function=png --path=./event.json", 8 | "notfound": "serverless invoke local --function=notfound --path=./event.json" 9 | }, 10 | "license": "MIT", 11 | "dependencies": { 12 | "serverless-aws-static-file-handler": "file:../..", 13 | "source-map-support": "^0.5.21" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.26.10", 17 | "@babel/plugin-transform-runtime": "^7.26.10", 18 | "@babel/preset-env": "^7.26.9", 19 | "babel-loader": "^10.0.0", 20 | "babel-plugin-source-map-support": "^2.2.0", 21 | "file-loader": "^6.2.0", 22 | "serverless": "^3.35.2", 23 | "serverless-offline": "^13.3.3", 24 | "serverless-webpack": "^5.15", 25 | "webpack": "^5.98.0", 26 | "webpack-node-externals": "^3.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test-files/webpack-project/serverless.yml: -------------------------------------------------------------------------------- 1 | service: babel-webpack-4-example 2 | 3 | # Add the serverless-webpack plugin 4 | plugins: 5 | - serverless-webpack 6 | - serverless-offline 7 | 8 | provider: 9 | name: aws 10 | runtime: nodejs12.x 11 | 12 | custom: 13 | webpack: 14 | webpackConfig: ./webpack.config.js 15 | includeModules: true 16 | # If you use Yarn instead of NPM in your environment, uncomment the following line. 17 | # packager: yarn 18 | 19 | package: 20 | individually: true 21 | 22 | functions: 23 | html: 24 | handler: handler.html 25 | events: 26 | - http: 27 | path: / 28 | method: get 29 | png: 30 | handler: handler.png 31 | events: 32 | - http: 33 | path: png 34 | method: get 35 | notfound: 36 | handler: handler.notfound 37 | events: 38 | - http: 39 | path: notfound 40 | method: get 41 | -------------------------------------------------------------------------------- /test-files/webpack-project/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const slsw = require("serverless-webpack") 3 | const nodeExternals = require("webpack-node-externals") 4 | 5 | module.exports = { 6 | entry: slsw.lib.entries, 7 | target: "node", 8 | mode: slsw.lib.webpack.isLocal ? "development" : "production", 9 | optimization: { 10 | // We no not want to minimize our code. 11 | minimize: false, 12 | }, 13 | performance: { 14 | // Turn off size warnings for entry points 15 | hints: false, 16 | }, 17 | devtool: "nosources-source-map", 18 | externals: [nodeExternals()], 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules/, 24 | use: [ 25 | { 26 | loader: "babel-loader", 27 | }, 28 | ], 29 | }, 30 | { 31 | test: /\.(png|jpe?g|gif|html)$/i, 32 | use: [ 33 | { 34 | loader: "file-loader", 35 | options: { 36 | name: "[path][name].[ext]", 37 | }, 38 | }, 39 | ], 40 | }, 41 | ], 42 | }, 43 | output: { 44 | libraryTarget: "commonjs2", 45 | path: path.join(__dirname, ".webpack"), 46 | filename: "[name].js", 47 | sourceMapFilename: "[file].map", 48 | }, 49 | } 50 | --------------------------------------------------------------------------------