diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..6de8a8a --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0=" + +use devenv \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 8446d98..469f59f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,16 +1,17 @@ module.exports = { env: { es6: true, - node: true + node: true, + mocha: true, }, extends: "eslint:recommended", parserOptions: { - sourceType: "module" + sourceType: "module", }, rules: { indent: ["error", 2], "linebreak-style": ["error", "unix"], quotes: ["warn", "double", { avoidEscape: true }], - semi: ["error", "always"] - } + semi: ["error", "always"], + }, }; diff --git a/.flake8 b/.flake8 index 9091b1c..692adb8 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,2 @@ [flake8] -ignore = E203,E501 +ignore = E203,E501,W503 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..bb50b0c --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,50 @@ +name: Tests +on: [push, pull_request] +jobs: + tests: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { name: "3.13", python: "3.13", os: ubuntu-latest } + - { name: "3.9", python: "3.9", os: ubuntu-latest } + - { name: "3.8", python: "3.8", os: ubuntu-latest } + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "14" + - run: npm install + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: update pip + run: | + pip install -U wheel + pip install -U setuptools + python -m pip install -U pip + - name: get pip cache dir + id: pip-cache + run: echo "::set-output name=dir::$(pip cache dir)" + - name: cache pip + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip|${{ runner.os }}|${{ matrix.python }}|${{ hashFiles('setup.py') }}|${{ hashFiles('requirements.txt') }} + - run: pip install pytest pytest-cov flake8 virtualenv urllib3[secure] + - run: pip install -r requirements.txt + + - run: npm test + - run: npm run lint + - run: npm run pytest + - run: npm run pylint + + - uses: codecov/codecov-action@v4 + with: + token: "89d22de7-bfaf-43a0-81da-33cc733fd294" + fail_ci_if_error: true + verbose: true diff --git a/.gitignore b/.gitignore index f0a3cca..c3d4768 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,14 @@ __pycache__ env/ build/ *.egg-info/ + +# Devenv +.devenv* +devenv.local.nix + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml + diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..dcfc712 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +nodejs 16.13.1 +python 3.10.0 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f72c271..0000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -language: python -python: - - "2.7" - - "3.6" - - "3.7" - - "3.8" - -env: - - TRAVIS_NODE_VERSION="12" - -install: - - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION - - npm install -g codecov - - npm install - - pip install --upgrade pytest pytest-cov codecov flake8 virtualenv urllib3[secure] - - pip install -r requirements.txt - -script: - - export CODECOV_TOKEN="89d22de7-bfaf-43a0-81da-33cc733fd294" - - npm test - - npm run lint - - npm run pytest - - npm run pylint - - which -a codecov | bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9efc6..2cd94e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,110 @@ +# 3.1.0 + +## Bugs + +- Fix missing CloudWatch log messages for errors occurring during app start-up + + _Kieren Eaton_ + +# 3.0.5 + +## Security + +- Remove hasbin dependency + +## Features + +- Add ability to provide function to execute when calling sls wsgi command + + _ayaan-qadri_ + +# 3.0.4 + +## Bugs + +- Fix lambda response processing + + _Eric Petway_ + +# 3.0.3 + +## Features + +- As of Werkzeug 3.0.0, url_encode is no longer available, use the urllib counterparts + + _Ryan Whittaker_ + +# 3.0.2 + +## Features + +- Support base path stripping for v2 events +- Default to https when protocol not specified in event +- Add blacklist entries + + _arsoni20_ + +## Bugs + +- Fix handling of v1 event payload emitted by Serverless Offline plugin +- Handle event with None body emitted by Serverless Offline plugin +- Fixes for Scaleway + + _Andrej Shadura_ + +# 3.0.1 + +## Bugs + +- Fix console output on commands/manage + + _Justin Lyons_ + +# 3.0.0 + +## Features + +- Update serverless integration for v3 compatibility (breaks integration with serverless < 2.32.0) (#193) + + _Mariusz Nowak_ + +- Add options for specifying SSL cert location when serving locally (#195) + + _Nathaniel J. Padgett_ + +- Support wsgi handler placed in a subfolder (#198) + + _Felipe Passos_ + +# 2.0.2 + +## Bugs + +- Compatibility upgrade for serverless 2.32 (#189) + + _Justin Lyons_ + +# 2.0.1 + +## Bugs + +- Lambda integration handler invoked for API Gateway proxy events, caused by #185 (#188) + +# 2.0.0 + +## Features + +- Drops Python 2 support and require Werkzeug 2 or later +- Remove deprecated API_GATEWAY_AUTHORIZER, event and context variables from WSGI environment. (Use serverless.authorizer, serverless.event and serverless.context instead) + +# 1.7.8 + +## Bugs + +- Pin Werkzeug version (#178) + + _Adam Chelminski_ + # 1.7.7 ## Features diff --git a/README.md b/README.md index da2d9bb..96ad576 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,10 @@ [![npm package](https://nodei.co/npm/serverless-wsgi.svg?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/serverless-wsgi/) [![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com) -[![Build Status](https://travis-ci.org/logandk/serverless-wsgi.svg?branch=master)](https://travis-ci.org/logandk/serverless-wsgi) +[![Tests](https://github.com/logandk/serverless-wsgi/actions/workflows/tests.yaml/badge.svg)](https://github.com/logandk/serverless-wsgi/actions/workflows/tests.yaml) [![Coverage Status](https://codecov.io/gh/logandk/serverless-wsgi/branch/master/graph/badge.svg)](https://codecov.io/gh/logandk/serverless-wsgi) -[![Dependency Status](https://david-dm.org/logandk/serverless-wsgi.svg)](https://david-dm.org/logandk/serverless-wsgi) -[![Dev Dependency Status](https://david-dm.org/logandk/serverless-wsgi/dev-status.svg)](https://david-dm.org/logandk/serverless-wsgi?type=dev) -A Serverless v1.x plugin to build your deploy Python WSGI applications using Serverless. Compatible +A Serverless Framework plugin to build your deploy Python WSGI applications using Serverless. Compatible WSGI application frameworks include Flask, Django and Pyramid - for a complete list, see: http://wsgi.readthedocs.io/en/latest/frameworks.html. @@ -213,6 +211,8 @@ locally. This command requires the `werkzeug` Python package to be installed, and acts as a simple wrapper for starting werkzeug's built-in HTTP server. By default, the server will start on port 5000. +(Note: macOS [reserves port 5000](https://twitter.com/mitsuhiko/status/1462734023164416009) +for AirPlay by default, see below for instructions on changing the port.) ``` $ sls wsgi serve @@ -369,6 +369,7 @@ custom: ``` **Note**: The **API_GATEWAY_BASE_PATH** configuration is only needed when using the payload V1. In the V2, the path does not have the **basePath** in the beginning. + ### Using CloudFront If you're configuring CloudFront manually in front of your API and setting diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..566c143 --- /dev/null +++ b/devenv.lock @@ -0,0 +1,156 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1696344641, + "narHash": "sha256-cfGsdtDvzYaFA7oGWSgcd1yST6LFwvjMcHvtVj56VcU=", + "owner": "cachix", + "repo": "devenv", + "rev": "05e26941f34486bff6ebeb4b9c169b6f637f1758", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1696419054, + "narHash": "sha256-EdR+dIKCfqL3voZUDYwcvgRDOektQB9KbhBVcE0/3Mo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7131f3c223a2d799568e4b278380cd9dac2b8579", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1696158581, + "narHash": "sha256-h0vY4E7Lx95lpYQbG2w4QH4yG5wCYOvPJzK93wVQbT0=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "033453f85064ccac434dfd957f95d8457901ecd6", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..9948558 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,7 @@ +{ ... }: + +{ + languages.python.enable = true; + languages.python.venv.enable = true; + languages.javascript.enable = true; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..c7cb5ce --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,3 @@ +inputs: + nixpkgs: + url: github:NixOS/nixpkgs/nixpkgs-unstable diff --git a/index.js b/index.js index 384fa8e..309ad9d 100644 --- a/index.js +++ b/index.js @@ -5,16 +5,20 @@ const _ = require("lodash"); const path = require("path"); const fse = BbPromise.promisifyAll(require("fs-extra")); const child_process = require("child_process"); -const hasbin = require("hasbin"); +const commandExists = require("command-exists"); +const overrideStdoutWrite = require("process-utils/override-stdout-write"); class ServerlessWSGI { validate() { - return new BbPromise(resolve => { + return new BbPromise((resolve) => { let handlersFixed = false; - _.each(this.serverless.service.functions, func => { + _.each(this.serverless.service.functions, (func) => { if (func.handler == "wsgi.handler") { - func.handler = "wsgi_handler.handler"; + func.handler = func.handler.replace( + "wsgi.handler", + "wsgi_handler.handler" + ); handlersFixed = true; } }); @@ -48,7 +52,8 @@ class ServerlessWSGI { } if (_.isBoolean(this.serverless.service.custom.wsgi.packRequirements)) { - this.enableRequirements = this.serverless.service.custom.wsgi.packRequirements; + this.enableRequirements = + this.serverless.service.custom.wsgi.packRequirements; } this.pipArgs = this.serverless.service.custom.wsgi.pipArgs; @@ -65,9 +70,8 @@ class ServerlessWSGI { this.serverless.service.package.individually && this.serverless.config.servicePath != this.appPath ) { - let handler = _.find( - this.serverless.service.functions, - fun => fun.handler == "wsgi_handler.handler" + let handler = _.find(this.serverless.service.functions, (fun) => + _.includes(fun.handler, "wsgi_handler.handler") ); // serverless-python-requirements supports packaging individual functions @@ -84,18 +88,16 @@ class ServerlessWSGI { } configurePackaging() { - return new BbPromise(resolve => { + return new BbPromise((resolve) => { this.serverless.service.package = this.serverless.service.package || {}; - this.serverless.service.package.include = - this.serverless.service.package.include || []; - this.serverless.service.package.exclude = - this.serverless.service.package.exclude || []; + this.serverless.service.package.patterns = + this.serverless.service.package.patterns || []; - this.serverless.service.package.include = _.union( - this.serverless.service.package.include, + this.serverless.service.package.patterns = _.union( + this.serverless.service.package.patterns, _.map( ["wsgi_handler.py", "serverless_wsgi.py", ".serverless-wsgi"], - artifact => + (artifact) => path.join( path.relative( this.serverless.config.servicePath, @@ -107,11 +109,11 @@ class ServerlessWSGI { ); if (this.enableRequirements) { - this.serverless.service.package.exclude.push( - path.join( + this.serverless.service.package.patterns.push( + `!${path.join( path.relative(this.serverless.config.servicePath, this.appPath), ".requirements/**" - ) + )}` ); } @@ -120,16 +122,14 @@ class ServerlessWSGI { } locatePython() { - return new BbPromise(resolve => { + return new BbPromise((resolve) => { if ( this.serverless.service.custom && this.serverless.service.custom.wsgi && this.serverless.service.custom.wsgi.pythonBin ) { this.serverless.cli.log( - `Using Python specified in "pythonBin": ${ - this.serverless.service.custom.wsgi.pythonBin - }` + `Using Python specified in "pythonBin": ${this.serverless.service.custom.wsgi.pythonBin}` ); this.pythonBin = this.serverless.service.custom.wsgi.pythonBin; @@ -137,20 +137,16 @@ class ServerlessWSGI { } if (this.serverless.service.provider.runtime) { - if (hasbin.sync(this.serverless.service.provider.runtime)) { + if (commandExists.sync(this.serverless.service.provider.runtime)) { this.serverless.cli.log( - `Using Python specified in "runtime": ${ - this.serverless.service.provider.runtime - }` + `Using Python specified in "runtime": ${this.serverless.service.provider.runtime}` ); this.pythonBin = this.serverless.service.provider.runtime; return resolve(); } else { this.serverless.cli.log( - `Python executable not found for "runtime": ${ - this.serverless.service.provider.runtime - }` + `Python executable not found for "runtime": ${this.serverless.service.provider.runtime}` ); } } @@ -167,7 +163,8 @@ class ServerlessWSGI { const config = { app: this.wsgiApp }; if (_.isArray(this.serverless.service.custom.wsgi.textMimeTypes)) { - config.text_mime_types = this.serverless.service.custom.wsgi.textMimeTypes; + config.text_mime_types = + this.serverless.service.custom.wsgi.textMimeTypes; } return config; @@ -197,7 +194,7 @@ class ServerlessWSGI { fse.writeFileAsync( path.join(this.packageRootPath, ".serverless-wsgi"), JSON.stringify(this.getWsgiHandlerConfiguration()) - ) + ), ]); } @@ -232,13 +229,13 @@ class ServerlessWSGI { this.serverless.cli.log("Packaging required Python packages..."); - const res = child_process.spawnSync(this.pythonBin, args, {'encoding': 'utf8'}); + const res = child_process.spawnSync(this.pythonBin, args, { + encoding: "utf8", + }); if (res.error) { if (res.error.code == "ENOENT") { return reject( - `Unable to run Python executable: ${ - this.pythonBin - }. Use the "pythonBin" option to set your Python executable explicitly.` + `Unable to run Python executable: ${this.pythonBin}. Use the "pythonBin" option to set your Python executable explicitly.` ); } else { return reject(res.error); @@ -262,14 +259,14 @@ class ServerlessWSGI { if (fse.existsSync(this.requirementsInstallPath)) { this.serverless.cli.log("Linking required Python packages..."); - fse.readdirSync(this.requirementsInstallPath).map(file => { + fse.readdirSync(this.requirementsInstallPath).map((file) => { let relativePath = path.join( path.relative(this.serverless.config.servicePath, this.appPath), file ); - this.serverless.service.package.include.push(relativePath); - this.serverless.service.package.include.push(`${relativePath}/**`); + this.serverless.service.package.patterns.push(relativePath); + this.serverless.service.package.patterns.push(`${relativePath}/**`); try { fse.symlinkSync(`${this.requirementsInstallPath}/${file}`, file); @@ -285,7 +282,7 @@ class ServerlessWSGI { if (linkConflict) { return reject( `Unable to link dependency '${file}' ` + - "because a file by the same name exists in this service" + "because a file by the same name exists in this service" ); } } @@ -297,7 +294,7 @@ class ServerlessWSGI { } checkWerkzeugPresent() { - return new BbPromise(resolve => { + return new BbPromise((resolve) => { if (!this.wsgiApp || !this.enableRequirements) { return resolve(); } @@ -315,7 +312,7 @@ class ServerlessWSGI { } unlinkRequirements() { - return new BbPromise(resolve => { + return new BbPromise((resolve) => { if (!this.enableRequirements) { return resolve(); } @@ -323,7 +320,7 @@ class ServerlessWSGI { if (fse.existsSync(this.requirementsInstallPath)) { this.serverless.cli.log("Unlinking required Python packages..."); - fse.readdirSync(this.requirementsInstallPath).map(file => { + fse.readdirSync(this.requirementsInstallPath).map((file) => { if (fse.existsSync(file)) { fse.unlinkSync(file); } @@ -346,26 +343,26 @@ class ServerlessWSGI { const artifacts = [ "wsgi_handler.py", "serverless_wsgi.py", - ".serverless-wsgi" + ".serverless-wsgi", ]; return BbPromise.all( - _.map(artifacts, artifact => + _.map(artifacts, (artifact) => fse.removeAsync(path.join(this.packageRootPath, artifact)) ) ); } loadEnvVars() { - return new BbPromise(resolve => { + return new BbPromise((resolve) => { const providerEnvVars = _.omitBy( this.serverless.service.provider.environment || {}, _.isObject ); _.merge(process.env, providerEnvVars); - _.each(this.serverless.service.functions, func => { - if (func.handler == "wsgi_handler.handler") { + _.each(this.serverless.service.functions, (func) => { + if (_.includes(func.handler, "wsgi_handler.handler")) { const functionEnvVars = _.omitBy(func.environment || {}, _.isObject); _.merge(process.env, functionEnvVars); } @@ -388,13 +385,15 @@ class ServerlessWSGI { const disable_threading = this.options["disable-threading"] || false; const num_processes = this.options["num-processes"] || 1; const ssl = this.options.ssl || false; + const ssl_pub = this.options["ssl-pub"] || ""; + const ssl_pri = this.options["ssl-pri"] || ""; var args = [ path.resolve(__dirname, "serve.py"), this.packageRootPath, this.wsgiApp, port, - host + host, ]; if (num_processes > 1) { @@ -409,15 +408,21 @@ class ServerlessWSGI { args.push("--ssl"); } + if (ssl_pub) { + args.push("--ssl-pub", ssl_pub); + } + + if (ssl_pri) { + args.push("--ssl-pri", ssl_pri); + } + var status = child_process.spawnSync(this.pythonBin, args, { - stdio: "inherit" + stdio: "inherit", }); if (status.error) { if (status.error.code == "ENOENT") { reject( - `Unable to run Python executable: ${ - this.pythonBin - }. Use the "pythonBin" option to set your Python executable explicitly.` + `Unable to run Python executable: ${this.pythonBin}. Use the "pythonBin" option to set your Python executable explicitly.` ); } else { reject(status.error); @@ -429,14 +434,29 @@ class ServerlessWSGI { } findHandler() { - return _.findKey( - this.serverless.service.functions, - fun => fun.handler == "wsgi_handler.handler" - ); + const functionName = this.options.function || this.options.f; + + if (functionName) { + // If the function name is specified, return it directly + if (this.serverless.service.functions[functionName]) { + return functionName; + } else { + throw new Error(`Function "${functionName}" not found.`); + } + } else { + return _.findKey(this.serverless.service.functions, (fun) => + _.includes(fun.handler, "wsgi_handler.handler") + ); + } } invokeHandler(command, data, local) { - const handlerFunction = this.findHandler(); + let handlerFunction; + try { + handlerFunction = this.findHandler(); + } catch (error) { + return BbPromise.reject(error.message); + } if (!handlerFunction) { return BbPromise.reject( @@ -448,28 +468,56 @@ class ServerlessWSGI { // no proper plugin-facing API. Instead, the current CLI options are modified // to match those of an invoke call. this.serverless.pluginManager.cliOptions.function = handlerFunction; + this.options.function = handlerFunction; + this.options.data = JSON.stringify({ + "_serverless-wsgi": { + command: command, + data: data, + }, + }); this.serverless.pluginManager.cliOptions.data = JSON.stringify({ "_serverless-wsgi": { command: command, - data: data - } + data: data, + }, }); + this.serverless.pluginManager.cliOptions.context = undefined; - this.serverless.pluginManager.cliOptions.f = this.serverless.pluginManager.cliOptions.function; - this.serverless.pluginManager.cliOptions.d = this.serverless.pluginManager.cliOptions.data; - this.serverless.pluginManager.cliOptions.c = this.serverless.pluginManager.cliOptions.context; + this.serverless.pluginManager.cliOptions.f = + this.serverless.pluginManager.cliOptions.function; + this.serverless.pluginManager.cliOptions.d = + this.serverless.pluginManager.cliOptions.data; + this.serverless.pluginManager.cliOptions.c = + this.serverless.pluginManager.cliOptions.context; + this.options.context = undefined; + this.options.f = this.options.function; + this.options.d = this.options.data; + this.options.c = this.options.context; // The invoke plugin prints the response to the console as JSON. When invoking commands // remotely, we get a string back and we want it to appear in the console as it would have // if it was invoked locally. // - // Thus, console.log is temporarily hijacked to capture the output and parse it as JSON. This - // hack is needed to avoid having to call the provider-specific invoke plugins. + // We capture stdout output in order to parse the array returned from the lambda invocation, + // then restore stdout. let output = ""; - /* eslint-disable no-console */ - const native_log = console.log; - console.log = msg => (output += msg + "\n"); + /* eslint-disable no-unused-vars */ + const { + originalStdoutWrite, // Original `write` bound to `process.stdout`#noqa + originalWrite, // Original `write` on its own + restoreStdoutWrite, // Allows to restore previous state + } = overrideStdoutWrite( + // process.stdout.write replacement + ( + orig, // data input + originalStdoutWrite // // Original `write` bound to `process.stdout` + ) => { + output += orig; + } + ); + /* eslint-enable no-unused-vars */ + return this.serverless.pluginManager .run(local ? ["invoke", "local"] : ["invoke"]) @@ -488,18 +536,18 @@ class ServerlessWSGI { ? _.trimEnd(output[1], "\n") : output[1]; if (return_code == 0) { - native_log(output_data); + this.serverless.cli.log(output_data); } else { - return reject(output_data); + return reject(new this.serverless.classes.Error(output_data)); } } else { - native_log(output); + this.serverless.cli.log(output); } return resolve(); }) ) .finally(() => { - console.log = native_log; + restoreStdoutWrite(); }); /* eslint-enable no-console */ } @@ -561,33 +609,41 @@ class ServerlessWSGI { port: { type: "string", usage: "Local server port, defaults to 5000", - shortcut: "p" + shortcut: "p", }, host: { type: "string", - usage: "Server host, defaults to 'localhost'" + usage: "Server host, defaults to 'localhost'", }, "disable-threading": { - type: 'boolean', - usage: "Disables multi-threaded mode" + type: "boolean", + usage: "Disables multi-threaded mode", }, "num-processes": { type: "string", - usage: "Number of processes for server, defaults to 1" + usage: "Number of processes for server, defaults to 1", }, ssl: { type: "boolean", - usage: "Enable local serving using HTTPS" - } - } + usage: "Enable local serving using HTTPS", + }, + "ssl-pub": { + type: "string", + usage: "local ssl pem file to use for ssl", + }, + "ssl-pri": { + type: "string", + usage: "local ssl pem file to use for ssl private key", + }, + }, }, install: { usage: "Install WSGI handler and requirements for local use", - lifecycleEvents: ["install"] + lifecycleEvents: ["install"], }, clean: { usage: "Remove cached requirements", - lifecycleEvents: ["clean"] + lifecycleEvents: ["clean"], }, command: { usage: "Execute shell commands or scripts remotely", @@ -596,13 +652,13 @@ class ServerlessWSGI { command: { type: "string", usage: "Command to execute", - shortcut: "c" + shortcut: "c", }, file: { type: "string", usage: "Path to a shell script to execute", - shortcut: "f" - } + shortcut: "f", + }, }, commands: { local: { @@ -612,16 +668,16 @@ class ServerlessWSGI { command: { type: "string", usage: "Command to execute", - shortcut: "c" + shortcut: "c", }, file: { type: "string", usage: "Path to a shell script to execute", - shortcut: "f" - } - } - } - } + shortcut: "f", + }, + }, + }, + }, }, exec: { usage: "Evaluate Python code remotely", @@ -630,13 +686,13 @@ class ServerlessWSGI { command: { type: "string", usage: "Python code to execute", - shortcut: "c" + shortcut: "c", }, file: { type: "string", usage: "Path to a Python script to execute", - shortcut: "f" - } + shortcut: "f", + }, }, commands: { local: { @@ -646,16 +702,16 @@ class ServerlessWSGI { command: { type: "string", usage: "Python code to execute", - shortcut: "c" + shortcut: "c", }, file: { type: "string", usage: "Path to a Python script to execute", - shortcut: "f" - } - } - } - } + shortcut: "f", + }, + }, + }, + }, }, manage: { usage: "Run Django management commands remotely", @@ -665,8 +721,8 @@ class ServerlessWSGI { type: "string", usage: "Management command", shortcut: "c", - required: true - } + required: true, + }, }, commands: { local: { @@ -677,11 +733,11 @@ class ServerlessWSGI { type: "string", usage: "Management command", shortcut: "c", - required: true - } - } - } - } + required: true, + }, + }, + }, + }, }, flask: { usage: "Run Flask CLI commands remotely", @@ -691,8 +747,8 @@ class ServerlessWSGI { type: "string", usage: "Flask CLI command", shortcut: "c", - required: true - } + required: true, + }, }, commands: { local: { @@ -703,14 +759,14 @@ class ServerlessWSGI { type: "string", usage: "Flask CLI command", shortcut: "c", - required: true - } - } - } - } - } - } - } + required: true, + }, + }, + }, + }, + }, + }, + }, }; const deployBeforeHook = () => @@ -791,7 +847,9 @@ class ServerlessWSGI { "after:package:createDeploymentArtifacts": deployAfterHook, "before:deploy:function:packageFunction": () => { - if (this.options.functionObj.handler == "wsgi_handler.handler") { + if ( + _.includes(this.options.functionObj.handler, "wsgi_handler.handler") + ) { return deployBeforeHook(); } else { return deployBeforeHookWithoutHandler(); @@ -810,7 +868,7 @@ class ServerlessWSGI { return BbPromise.bind(this) .then(this.validate) .then(() => { - if (functionObj.handler == "wsgi_handler.handler") { + if (_.includes(functionObj.handler, "wsgi_handler.handler")) { return this.packWsgiHandler(false); } else { return BbPromise.resolve(); @@ -818,9 +876,7 @@ class ServerlessWSGI { }); }, "after:invoke:local:invoke": () => - BbPromise.bind(this) - .then(this.validate) - .then(this.cleanup) + BbPromise.bind(this).then(this.validate).then(this.cleanup), }; } } diff --git a/index.test.js b/index.test.js index a497c1f..9e89e97 100644 --- a/index.test.js +++ b/index.test.js @@ -1,6 +1,5 @@ "use strict"; -/* global describe it */ const chai = require("chai"); const expect = chai.expect; const sinon = require("sinon"); @@ -8,7 +7,7 @@ const Plugin = require("./index"); const child_process = require("child_process"); const path = require("path"); const fse = require("fs-extra"); -const hasbin = require("hasbin"); +const commandExists = require("command-exists"); const BbPromise = require("bluebird"); const chaiAsPromised = require("chai-as-promised"); @@ -46,10 +45,10 @@ describe("serverless-wsgi", () => { var plugin = new Plugin( { cli: { - generateCommandsHelp: command => { + generateCommandsHelp: (command) => { expect(command).to.deep.equal(["wsgi"]); - } - } + }, + }, }, {} ); @@ -65,23 +64,25 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var procStub = sandbox.stub(child_process, "spawnSync"); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(copyStub.called).to.be.false; - expect(writeStub.called).to.be.false; - expect(procStub.called).to.be.false; - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(copyStub.called).to.be.false; + expect(writeStub.called).to.be.false; + expect(procStub.called).to.be.false; + sandbox.restore(); + } + ); }); it("packages wsgi handler", () => { @@ -90,56 +91,56 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "wsgi_handler.py"), - "/tmp/wsgi_handler.py" - ) - ).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "serverless_wsgi.py"), - "/tmp/serverless_wsgi.py" - ) - ).to.be.true; - expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ - app: "api.app" - }); - expect( - procStub.calledWith("python2.7", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/.requirements" - ]) - ).to.be.true; - sandbox.restore(); - expect(plugin.serverless.service.package.include).to.have.members([ - "wsgi_handler.py", - "serverless_wsgi.py", - ".serverless-wsgi" - ]); - expect(plugin.serverless.service.package.exclude).to.have.members([ - ".requirements/**" - ]); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "wsgi_handler.py"), + "/tmp/wsgi_handler.py" + ) + ).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "serverless_wsgi.py"), + "/tmp/serverless_wsgi.py" + ) + ).to.be.true; + expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ + app: "api.app", + }); + expect( + procStub.calledWith("python2.7", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/.requirements", + ]) + ).to.be.true; + sandbox.restore(); + expect(plugin.serverless.service.package.patterns).to.have.members([ + "wsgi_handler.py", + "serverless_wsgi.py", + ".serverless-wsgi", + "!.requirements/**", + ]); + } + ); }); it("packages wsgi handler with additional text mime types", () => { @@ -151,30 +152,32 @@ describe("serverless-wsgi", () => { custom: { wsgi: { app: "api.app", - textMimeTypes: ["application/custom+json"] - } - } + textMimeTypes: ["application/custom+json"], + }, + }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); sandbox.stub(child_process, "spawnSync").returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ - app: "api.app", - text_mime_types: ["application/custom+json"] - }); - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ + app: "api.app", + text_mime_types: ["application/custom+json"], + }); + sandbox.restore(); + } + ); }); it("falls back to default python if runtime version is not found", () => { @@ -185,34 +188,36 @@ describe("serverless-wsgi", () => { provider: { runtime: "python3.6" }, custom: { wsgi: { - app: "api.app" - } - } + app: "api.app", + }, + }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(false); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(false); sandbox.stub(fse, "copyAsync"); sandbox.stub(fse, "writeFileAsync"); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python3.6")).to.be.true; - expect( - procStub.calledWith("python", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/.requirements" - ]) - ).to.be.true; - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python3.6")).to.be.true; + expect( + procStub.calledWith("python", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/.requirements", + ]) + ).to.be.true; + sandbox.restore(); + } + ); }); it("cleans up after deployment", () => { @@ -221,23 +226,25 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); var removeStub = sandbox.stub(fse, "removeAsync"); - plugin.hooks["after:package:createDeploymentArtifacts"]().then(() => { - expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; - expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; - expect(removeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(removeStub.calledWith("/tmp/.requirements")).to.be.false; - sandbox.restore(); - }); + return plugin.hooks["after:package:createDeploymentArtifacts"]().then( + () => { + expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; + expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; + expect(removeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(removeStub.calledWith("/tmp/.requirements")).to.be.false; + sandbox.restore(); + } + ); }); it("packages wsgi handler with individual include and exclude patterns", () => { @@ -249,17 +256,17 @@ describe("serverless-wsgi", () => { package: { individually: true }, custom: { wsgi: { app: "web/api.app" } }, functions: { - api: { handler: "wsgi.handler" } - } + api: { handler: "wsgi.handler" }, + }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var symlinkStub = sandbox.stub(fse, "symlinkSync"); @@ -268,47 +275,47 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "wsgi_handler.py"), - "/tmp/wsgi_handler.py" - ) - ).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "serverless_wsgi.py"), - "/tmp/serverless_wsgi.py" - ) - ).to.be.true; - expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ - app: "web/api.app" - }); - expect(symlinkStub.called).to.be.true; - expect( - procStub.calledWith("python2.7", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/web/requirements.txt", - "/tmp/web/.requirements" - ]) - ).to.be.true; - sandbox.restore(); - expect(plugin.serverless.service.package.include).to.have.members([ - "wsgi_handler.py", - "serverless_wsgi.py", - ".serverless-wsgi", - "web/flask", - "web/flask/**", - "web/werkzeug", - "web/werkzeug/**" - ]); - expect(plugin.serverless.service.package.exclude).to.have.members([ - "web/.requirements/**" - ]); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "wsgi_handler.py"), + "/tmp/wsgi_handler.py" + ) + ).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "serverless_wsgi.py"), + "/tmp/serverless_wsgi.py" + ) + ).to.be.true; + expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ + app: "web/api.app", + }); + expect(symlinkStub.called).to.be.true; + expect( + procStub.calledWith("python2.7", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/web/requirements.txt", + "/tmp/web/.requirements", + ]) + ).to.be.true; + sandbox.restore(); + expect(plugin.serverless.service.package.patterns).to.have.members([ + "wsgi_handler.py", + "serverless_wsgi.py", + ".serverless-wsgi", + "web/flask", + "web/flask/**", + "web/werkzeug", + "web/werkzeug/**", + "!web/.requirements/**", + ]); + } + ); }); it("packages wsgi handler in individually packaged modules by serverless-python-requirements", () => { @@ -320,17 +327,17 @@ describe("serverless-wsgi", () => { package: { individually: true }, custom: { wsgi: { app: "web/api.app" } }, functions: { - api: { handler: "wsgi.handler", module: "web" } - } + api: { handler: "wsgi.handler", module: "web" }, + }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var symlinkStub = sandbox.stub(fse, "symlinkSync"); @@ -339,47 +346,47 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "wsgi_handler.py"), - "/tmp/web/wsgi_handler.py" - ) - ).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "serverless_wsgi.py"), - "/tmp/web/serverless_wsgi.py" - ) - ).to.be.true; - expect(writeStub.calledWith("/tmp/web/.serverless-wsgi")).to.be.true; - expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ - app: "api.app" - }); - expect(symlinkStub.called).to.be.true; - expect( - procStub.calledWith("python2.7", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/web/requirements.txt", - "/tmp/web/.requirements" - ]) - ).to.be.true; - sandbox.restore(); - expect(plugin.serverless.service.package.include).to.have.members([ - "web/wsgi_handler.py", - "web/serverless_wsgi.py", - "web/.serverless-wsgi", - "web/flask", - "web/flask/**", - "web/werkzeug", - "web/werkzeug/**" - ]); - expect(plugin.serverless.service.package.exclude).to.have.members([ - "web/.requirements/**" - ]); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "wsgi_handler.py"), + "/tmp/web/wsgi_handler.py" + ) + ).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "serverless_wsgi.py"), + "/tmp/web/serverless_wsgi.py" + ) + ).to.be.true; + expect(writeStub.calledWith("/tmp/web/.serverless-wsgi")).to.be.true; + expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ + app: "api.app", + }); + expect(symlinkStub.called).to.be.true; + expect( + procStub.calledWith("python2.7", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/web/requirements.txt", + "/tmp/web/.requirements", + ]) + ).to.be.true; + sandbox.restore(); + expect(plugin.serverless.service.package.patterns).to.have.members([ + "web/wsgi_handler.py", + "web/serverless_wsgi.py", + "web/.serverless-wsgi", + "web/flask", + "web/flask/**", + "web/werkzeug", + "web/werkzeug/**", + "!web/.requirements/**", + ]); + } + ); }); }); @@ -391,16 +398,16 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - package: { include: ["sample.txt"] } + package: { patterns: ["sample.txt"] }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var symlinkStub = sandbox.stub(fse, "symlinkSync"); @@ -409,34 +416,34 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(copyStub.called).to.be.true; - expect(writeStub.called).to.be.true; - expect(symlinkStub.called).to.be.true; - expect( - procStub.calledWith("python2.7", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/requirements.txt", - "/tmp/.requirements" - ]) - ).to.be.true; - expect(plugin.serverless.service.package.include).to.have.members([ - "sample.txt", - "wsgi_handler.py", - "serverless_wsgi.py", - ".serverless-wsgi", - "flask", - "flask/**", - "werkzeug", - "werkzeug/**" - ]); - expect(plugin.serverless.service.package.exclude).to.have.members([ - ".requirements/**" - ]); - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(copyStub.called).to.be.true; + expect(writeStub.called).to.be.true; + expect(symlinkStub.called).to.be.true; + expect( + procStub.calledWith("python2.7", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/requirements.txt", + "/tmp/.requirements", + ]) + ).to.be.true; + expect(plugin.serverless.service.package.patterns).to.have.members([ + "sample.txt", + "wsgi_handler.py", + "serverless_wsgi.py", + ".serverless-wsgi", + "!.requirements/**", + "flask", + "flask/**", + "werkzeug", + "werkzeug/**", + ]); + sandbox.restore(); + } + ); }); it("allows setting the python binary", () => { @@ -446,16 +453,16 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app", pythonBin: "my-python" } }, - package: { include: ["sample.txt"] } + package: { patterns: ["sample.txt"] }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var symlinkStub = sandbox.stub(fse, "symlinkSync"); @@ -464,32 +471,32 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.called).to.be.false; - expect(copyStub.called).to.be.true; - expect(writeStub.called).to.be.true; - expect(symlinkStub.called).to.be.true; - expect( - procStub.calledWith("my-python", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/requirements.txt", - "/tmp/.requirements" - ]) - ).to.be.true; - expect(plugin.serverless.service.package.include).to.have.members([ - "sample.txt", - "wsgi_handler.py", - "serverless_wsgi.py", - ".serverless-wsgi", - "flask", - "flask/**" - ]); - expect(plugin.serverless.service.package.exclude).to.have.members([ - ".requirements/**" - ]); - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.called).to.be.false; + expect(copyStub.called).to.be.true; + expect(writeStub.called).to.be.true; + expect(symlinkStub.called).to.be.true; + expect( + procStub.calledWith("my-python", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/requirements.txt", + "/tmp/.requirements", + ]) + ).to.be.true; + expect(plugin.serverless.service.package.patterns).to.have.members([ + "sample.txt", + "wsgi_handler.py", + "serverless_wsgi.py", + ".serverless-wsgi", + "flask", + "flask/**", + "!.requirements/**", + ]); + sandbox.restore(); + } + ); }); it("packages user requirements for wsgi app inside directory", () => { @@ -498,10 +505,10 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: {}, - custom: { wsgi: { app: "api/api.app" } } + custom: { wsgi: { app: "api/api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); @@ -515,29 +522,29 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(copyStub.called).to.be.true; - expect(writeStub.called).to.be.true; - expect( - procStub.calledWith("python", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/api/requirements.txt", - "/tmp/api/.requirements" - ]) - ).to.be.true; - expect(plugin.serverless.service.package.include).to.have.members([ - "wsgi_handler.py", - "serverless_wsgi.py", - ".serverless-wsgi", - "api/werkzeug", - "api/werkzeug/**" - ]); - expect(plugin.serverless.service.package.exclude).to.have.members([ - "api/.requirements/**" - ]); - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(copyStub.called).to.be.true; + expect(writeStub.called).to.be.true; + expect( + procStub.calledWith("python", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/api/requirements.txt", + "/tmp/api/.requirements", + ]) + ).to.be.true; + expect(plugin.serverless.service.package.patterns).to.have.members([ + "wsgi_handler.py", + "serverless_wsgi.py", + ".serverless-wsgi", + "api/werkzeug", + "api/werkzeug/**", + "!api/.requirements/**", + ]); + sandbox.restore(); + } + ); }); it("throws an error when a file already exists in the service root", () => { @@ -546,13 +553,13 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - sandbox.stub(hasbin, "sync").returns(true); + sandbox.stub(commandExists, "sync").returns(true); sandbox.stub(fse, "copyAsync"); sandbox.stub(fse, "writeFileAsync"); sandbox.stub(fse, "symlinkSync").throws(); @@ -573,13 +580,13 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - sandbox.stub(hasbin, "sync").returns(true); + sandbox.stub(commandExists, "sync").returns(true); sandbox.stub(fse, "copyAsync"); sandbox.stub(fse, "writeFileAsync"); sandbox.stub(fse, "symlinkSync").throws(); @@ -600,13 +607,13 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); sandbox.stub(fse, "symlinkSync").throws(); @@ -616,19 +623,21 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(copyStub.called).to.be.false; - expect(writeStub.called).to.be.false; - expect( - procStub.calledWith("python2.7", [ - path.resolve(__dirname, "requirements.py"), - "/tmp/requirements.txt", - "/tmp/.requirements" - ]) - ).to.be.true; - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(copyStub.called).to.be.false; + expect(writeStub.called).to.be.false; + expect( + procStub.calledWith("python2.7", [ + path.resolve(__dirname, "requirements.py"), + "/tmp/requirements.txt", + "/tmp/.requirements", + ]) + ).to.be.true; + sandbox.restore(); + } + ); }); it("packages user requirements with additional pip args", () => { @@ -637,16 +646,16 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python3.6" }, - custom: { wsgi: { pipArgs: "--no-deps 'imaginary \"value\"'" } } + custom: { wsgi: { pipArgs: "--no-deps 'imaginary \"value\"'" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); sandbox.stub(fse, "symlinkSync").throws(); @@ -656,21 +665,23 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python3.6")).to.be.true; - expect(copyStub.called).to.be.false; - expect(writeStub.called).to.be.false; - expect( - procStub.calledWith("python3.6", [ - path.resolve(__dirname, "requirements.py"), - "--pip-args", - "--no-deps 'imaginary \"value\"'", - "/tmp/requirements.txt", - "/tmp/.requirements" - ]) - ).to.be.true; - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python3.6")).to.be.true; + expect(copyStub.called).to.be.false; + expect(writeStub.called).to.be.false; + expect( + procStub.calledWith("python3.6", [ + path.resolve(__dirname, "requirements.py"), + "--pip-args", + "--no-deps 'imaginary \"value\"'", + "/tmp/requirements.txt", + "/tmp/.requirements", + ]) + ).to.be.true; + sandbox.restore(); + } + ); }); it("skips packaging for non-wsgi app without user requirements", () => { @@ -679,29 +690,34 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); sandbox.stub(fse, "existsSync").returns(false); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(copyStub.called).to.be.false; - expect(writeStub.called).to.be.false; - expect(procStub.called).to.be.false; - expect(plugin.serverless.service.package.exclude).to.have.members([ - ".requirements/**" - ]); - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(copyStub.called).to.be.false; + expect(writeStub.called).to.be.false; + expect(procStub.called).to.be.false; + expect(plugin.serverless.service.package.patterns).to.have.members([ + "wsgi_handler.py", + "serverless_wsgi.py", + ".serverless-wsgi", + "!.requirements/**", + ]); + sandbox.restore(); + } + ); }); it("rejects with non successful exit code", () => { @@ -710,13 +726,13 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - sandbox.stub(hasbin, "sync").returns(true); + sandbox.stub(commandExists, "sync").returns(true); sandbox.stub(fse, "existsSync").returns(true); sandbox.stub(child_process, "spawnSync").returns({ status: 1 }); @@ -733,13 +749,13 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - sandbox.stub(hasbin, "sync").returns(true); + sandbox.stub(commandExists, "sync").returns(true); sandbox.stub(fse, "existsSync").returns(true); sandbox .stub(child_process, "spawnSync") @@ -758,13 +774,13 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - sandbox.stub(hasbin, "sync").returns(true); + sandbox.stub(commandExists, "sync").returns(true); sandbox.stub(fse, "existsSync").returns(true); sandbox .stub(child_process, "spawnSync") @@ -783,33 +799,35 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app", packRequirements: false } } + custom: { wsgi: { app: "api.app", packRequirements: false } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var existsStub = sandbox.stub(fse, "existsSync").returns(true); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(copyStub.called).to.be.true; - expect(writeStub.called).to.be.true; - expect(existsStub.called).to.be.false; - expect(procStub.called).to.be.false; - expect(plugin.serverless.service.package.include).not.to.have.members([ - ".requirements/**" - ]); - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(copyStub.called).to.be.true; + expect(writeStub.called).to.be.true; + expect(existsStub.called).to.be.false; + expect(procStub.called).to.be.false; + expect( + plugin.serverless.service.package.patterns + ).not.to.have.members([".requirements/**"]); + sandbox.restore(); + } + ); }); it("skips requirements cleanup if disabled", () => { @@ -818,23 +836,25 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app", packRequirements: false } } + custom: { wsgi: { app: "api.app", packRequirements: false } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); var removeStub = sandbox.stub(fse, "removeAsync"); - plugin.hooks["after:package:createDeploymentArtifacts"]().then(() => { - expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; - expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; - expect(removeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(removeStub.calledWith("/tmp/.requirements")).to.be.false; - sandbox.restore(); - }); + return plugin.hooks["after:package:createDeploymentArtifacts"]().then( + () => { + expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; + expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; + expect(removeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(removeStub.calledWith("/tmp/.requirements")).to.be.false; + sandbox.restore(); + } + ); }); it("skips packaging if serverless-python-requirements is present", () => { @@ -844,40 +864,42 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - plugins: ["serverless-wsgi", "serverless-python-requirements"] + plugins: ["serverless-wsgi", "serverless-python-requirements"], }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var existsStub = sandbox.stub(fse, "existsSync").returns(true); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:package:createDeploymentArtifacts"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(copyStub.called).to.be.true; - expect(writeStub.called).to.be.true; - expect(existsStub.called).to.be.false; - expect(procStub.called).to.be.false; - expect(plugin.serverless.service.package.include).not.to.have.members([ - ".requirements/**" - ]); - sandbox.restore(); - }); + return plugin.hooks["before:package:createDeploymentArtifacts"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(copyStub.called).to.be.true; + expect(writeStub.called).to.be.true; + expect(existsStub.called).to.be.false; + expect(procStub.called).to.be.false; + expect( + plugin.serverless.service.package.patterns + ).not.to.have.members([".requirements/**"]); + sandbox.restore(); + } + ); }); }); describe("function deployment", () => { it("skips packaging for non-wsgi function", () => { var functions = { - app: {} + app: {}, }; var plugin = new Plugin( { @@ -885,33 +907,35 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: functions + functions: functions, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { functionObj: functions.app } ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:deploy:function:packageFunction"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect(copyStub.called).to.be.false; - expect(writeStub.called).to.be.false; - expect(procStub.called).to.be.true; - sandbox.restore(); - }); + return plugin.hooks["before:deploy:function:packageFunction"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect(copyStub.called).to.be.false; + expect(writeStub.called).to.be.false; + expect(procStub.called).to.be.true; + sandbox.restore(); + } + ); }); it("packages wsgi handler", () => { var functions = { - app: { handler: "wsgi_handler.handler" } + app: { handler: "wsgi_handler.handler" }, }; var plugin = new Plugin( { @@ -919,16 +943,16 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: functions + functions: functions, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { functionObj: functions.app } ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); sandbox.stub(fse, "readdirSync").returns([]); @@ -936,39 +960,41 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["before:deploy:function:packageFunction"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "wsgi_handler.py"), - "/tmp/wsgi_handler.py" - ) - ).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "serverless_wsgi.py"), - "/tmp/serverless_wsgi.py" - ) - ).to.be.true; - expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ - app: "api.app" - }); - expect( - procStub.calledWith("python2.7", [ - path.resolve(__dirname, "requirements.py"), - path.resolve(__dirname, "requirements.txt"), - "/tmp/requirements.txt", - "/tmp/.requirements" - ]) - ).to.be.true; - sandbox.restore(); - }); + return plugin.hooks["before:deploy:function:packageFunction"]().then( + () => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "wsgi_handler.py"), + "/tmp/wsgi_handler.py" + ) + ).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "serverless_wsgi.py"), + "/tmp/serverless_wsgi.py" + ) + ).to.be.true; + expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ + app: "api.app", + }); + expect( + procStub.calledWith("python2.7", [ + path.resolve(__dirname, "requirements.py"), + path.resolve(__dirname, "requirements.txt"), + "/tmp/requirements.txt", + "/tmp/.requirements", + ]) + ).to.be.true; + sandbox.restore(); + } + ); }); it("cleans up after deployment", () => { var functions = { - app: { handler: "wsgi_handler.handler" } + app: { handler: "wsgi_handler.handler" }, }; var plugin = new Plugin( { @@ -976,10 +1002,10 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: functions + functions: functions, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { functionObj: functions.app } ); @@ -990,16 +1016,18 @@ describe("serverless-wsgi", () => { existsStub.withArgs("werkzeug").returns(false); sandbox.stub(fse, "readdirSync").returns(["flask", "werkzeug"]); var unlinkStub = sandbox.stub(fse, "unlinkSync"); - plugin.hooks["after:deploy:function:packageFunction"]().then(() => { - expect(existsStub.calledWith("/tmp/.requirements")).to.be.true; - expect(unlinkStub.calledWith("flask")).to.be.true; - expect(unlinkStub.calledWith("werkzeug")).to.be.false; - expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; - expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; - expect(removeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(removeStub.calledWith("/tmp/.requirements")).to.be.false; - sandbox.restore(); - }); + return plugin.hooks["after:deploy:function:packageFunction"]().then( + () => { + expect(existsStub.calledWith("/tmp/.requirements")).to.be.true; + expect(unlinkStub.calledWith("flask")).to.be.true; + expect(unlinkStub.calledWith("werkzeug")).to.be.false; + expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; + expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; + expect(removeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(removeStub.calledWith("/tmp/.requirements")).to.be.false; + sandbox.restore(); + } + ); }); }); @@ -1009,7 +1037,7 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" } }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }); return expect(plugin.hooks["wsgi:serve:serve"]()).to.be.rejected; @@ -1021,19 +1049,19 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox.stub(child_process, "spawnSync").returns({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1042,7 +1070,7 @@ describe("serverless-wsgi", () => { "/tmp", "api.app", 5000, - "localhost" + "localhost", ], { stdio: "inherit" } ) @@ -1057,23 +1085,23 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ error: "Something failed" }); - expect( + return expect( plugin.hooks["wsgi:serve:serve"]() ).to.eventually.be.rejected.and.notify(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1082,7 +1110,7 @@ describe("serverless-wsgi", () => { "/tmp", "api.app", 5000, - "localhost" + "localhost", ], { stdio: "inherit" } ) @@ -1097,23 +1125,23 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox .stub(child_process, "spawnSync") .returns({ error: { code: "ENOENT" } }); - expect( + return expect( plugin.hooks["wsgi:serve:serve"]() ).to.eventually.be.rejected.and.notify(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1122,7 +1150,7 @@ describe("serverless-wsgi", () => { "/tmp", "api.app", 5000, - "localhost" + "localhost", ], { stdio: "inherit" } ) @@ -1137,19 +1165,19 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { port: 8000 } ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox.stub(child_process, "spawnSync").returns({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1158,7 +1186,7 @@ describe("serverless-wsgi", () => { "/tmp", "api.app", 8000, - "localhost" + "localhost", ], { stdio: "inherit" } ) @@ -1173,19 +1201,19 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { host: "0.0.0.0" } ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox.stub(child_process, "spawnSync").returns({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1194,7 +1222,7 @@ describe("serverless-wsgi", () => { "/tmp", "api.app", 5000, - "0.0.0.0" + "0.0.0.0", ], { stdio: "inherit" } ) @@ -1209,19 +1237,19 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { "disable-threading": true } ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox.stub(child_process, "spawnSync").returns({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1231,7 +1259,7 @@ describe("serverless-wsgi", () => { "api.app", 5000, "localhost", - "--disable-threading" + "--disable-threading", ], { stdio: "inherit" } ) @@ -1246,19 +1274,19 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { "num-processes": 10 } ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox.stub(child_process, "spawnSync").returns({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1269,7 +1297,7 @@ describe("serverless-wsgi", () => { 5000, "localhost", "--num-processes", - 10 + 10, ], { stdio: "inherit" } ) @@ -1284,19 +1312,19 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { ssl: true } ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox.stub(child_process, "spawnSync").returns({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1306,7 +1334,7 @@ describe("serverless-wsgi", () => { "api.app", 5000, "localhost", - "--ssl" + "--ssl", ], { stdio: "inherit" } ) @@ -1324,30 +1352,30 @@ describe("serverless-wsgi", () => { runtime: "python2.7", environment: { SOME_ENV_VAR: 42, - ANOTHER_ONE: { Ref: "AWS::StackId" } - } + ANOTHER_ONE: { Ref: "AWS::StackId" }, + }, }, functions: { func1: { handler: "wsgi_handler.handler", - environment: { SECOND_VAR: 33 } + environment: { SECOND_VAR: 33 }, }, func2: { handler: "x.x", environment: { THIRD_VAR: 66 } }, - func3: { handler: "wsgi_handler.handler" } + func3: { handler: "wsgi_handler.handler" }, }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { port: 8000 } ); var sandbox = sinon.createSandbox(); - sandbox.stub(hasbin, "sync").returns(true); + sandbox.stub(commandExists, "sync").returns(true); sandbox.stub(child_process, "spawnSync").returns({}); sandbox.stub(process, "env").value({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { + return plugin.hooks["wsgi:serve:serve"]().then(() => { expect(process.env.SOME_ENV_VAR).to.equal(42); expect(process.env.SECOND_VAR).to.equal(33); expect(process.env.THIRD_VAR).to.be.undefined; @@ -1365,20 +1393,20 @@ describe("serverless-wsgi", () => { package: { individually: true }, custom: { wsgi: { app: "site/api.app" } }, functions: { - api: { handler: "wsgi.handler", module: "site" } - } + api: { handler: "wsgi.handler", module: "site" }, + }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var procStub = sandbox.stub(child_process, "spawnSync").returns({}); - plugin.hooks["wsgi:serve:serve"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:serve:serve"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( procStub.calledWith( "python2.7", @@ -1387,7 +1415,7 @@ describe("serverless-wsgi", () => { "/tmp/site", "api.app", 5000, - "localhost" + "localhost", ], { stdio: "inherit" } ) @@ -1404,16 +1432,16 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); var sandbox = sinon.createSandbox(); - var hasbinStub = sandbox.stub(hasbin, "sync").returns(true); + var commandExistsStub = sandbox.stub(commandExists, "sync").returns(true); var copyStub = sandbox.stub(fse, "copyAsync"); var writeStub = sandbox.stub(fse, "writeFileAsync"); sandbox.stub(fse, "readdirSync").returns([]); @@ -1421,8 +1449,8 @@ describe("serverless-wsgi", () => { var procStub = sandbox .stub(child_process, "spawnSync") .returns({ status: 0 }); - plugin.hooks["wsgi:install:install"]().then(() => { - expect(hasbinStub.calledWith("python2.7")).to.be.true; + return plugin.hooks["wsgi:install:install"]().then(() => { + expect(commandExistsStub.calledWith("python2.7")).to.be.true; expect( copyStub.calledWith( path.resolve(__dirname, "wsgi_handler.py"), @@ -1437,14 +1465,14 @@ describe("serverless-wsgi", () => { ).to.be.true; expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ - app: "api.app" + app: "api.app", }); expect( procStub.calledWith("python2.7", [ path.resolve(__dirname, "requirements.py"), path.resolve(__dirname, "requirements.txt"), "/tmp/requirements.txt", - "/tmp/.requirements" + "/tmp/.requirements", ]) ).to.be.true; sandbox.restore(); @@ -1459,10 +1487,10 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); @@ -1472,7 +1500,7 @@ describe("serverless-wsgi", () => { var existsStub = sandbox.stub(fse, "existsSync").returns(true); sandbox.stub(fse, "readdirSync").returns(["flask"]); var unlinkStub = sandbox.stub(fse, "unlinkSync"); - plugin.hooks["wsgi:clean:clean"]().then(() => { + return plugin.hooks["wsgi:clean:clean"]().then(() => { expect(existsStub.calledWith("/tmp/.requirements")).to.be.true; expect(unlinkStub.calledWith("flask")).to.be.true; expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; @@ -1485,7 +1513,7 @@ describe("serverless-wsgi", () => { it("skips cleaning requirements if packaging not enabled", () => { var functions = { - app: { handler: "wsgi_handler.handler" } + app: { handler: "wsgi_handler.handler" }, }; var plugin = new Plugin( { @@ -1493,10 +1521,10 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app", packRequirements: false } }, - functions: functions + functions: functions, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { functionObj: functions.app } ); @@ -1504,7 +1532,7 @@ describe("serverless-wsgi", () => { var sandbox = sinon.createSandbox(); var removeStub = sandbox.stub(fse, "removeAsync"); var existsStub = sandbox.stub(fse, "existsSync").returns(true); - plugin.hooks["wsgi:clean:clean"]().then(() => { + return plugin.hooks["wsgi:clean:clean"]().then(() => { expect(existsStub.calledWith("/tmp/.requirements")).to.be.false; expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; @@ -1516,16 +1544,18 @@ describe("serverless-wsgi", () => { }); describe("exec", () => { + const mockCli = Object({ log: () => { } }); + it("fails when invoked without command or file", () => { var plugin = new Plugin( { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, {} ); @@ -1542,40 +1572,38 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke"]); console.log('[0, "5"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "print(1+4)" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:exec:exec"]().then(() => { + let loggerSpy = sandbox.spy(mockCli, "log"); + return plugin.hooks["wsgi:exec:exec"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.c).to.be.undefined; expect(plugin.serverless.pluginManager.cliOptions.context).to.be .undefined; expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(consoleSpy.calledWith("5")).to.be.true; + expect(loggerSpy.calledWith("5")).to.be.true; sandbox.restore(); }); }); @@ -1587,38 +1615,36 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke"]); console.log('[0, {"response": "5"}]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { file: "script.py" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); + let loggerSpy = sandbox.spy(mockCli, "log"); sandbox.stub(fse, "readFileSync").returns("print(1+4)"); - plugin.hooks["wsgi:exec:exec"]().then(() => { + return plugin.hooks["wsgi:exec:exec"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(consoleSpy.calledWith({ response: "5" })).to.be.true; + expect(loggerSpy.calledWith({ response: "5" })).to.be.true; sandbox.restore(); }); }); @@ -1630,43 +1656,47 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: { log: () => { } }, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke"]); console.log('[1, "Error"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "print(1+4)" } ); - expect(plugin.hooks["wsgi:exec:exec"]()).to.be.rejectedWith("Error"); + return expect(plugin.hooks["wsgi:exec:exec"]()).to.be.rejectedWith( + "Error" + ); }); }); describe("exec local", () => { + const mockCli = { log: () => { } }; + it("fails when invoked without command or file", () => { var plugin = new Plugin( { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: mockCli, }, {} ); - expect(plugin.hooks["wsgi:exec:local:exec"]()).to.be.rejectedWith( + return expect(plugin.hooks["wsgi:exec:local:exec"]()).to.be.rejectedWith( "Please provide either a command (-c) or a file (-f)" ); }); @@ -1678,40 +1708,38 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke", "local"]); console.log('[0, "5"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "print(1+4)" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:exec:local:exec"]().then(() => { + let loggerSpy = sandbox.spy(mockCli, "log"); + return plugin.hooks["wsgi:exec:local:exec"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.c).to.be.undefined; expect(plugin.serverless.pluginManager.cliOptions.context).to.be .undefined; expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(consoleSpy.calledWith("5")).to.be.true; + expect(loggerSpy.calledWith("5")).to.be.true; sandbox.restore(); }); }); @@ -1723,44 +1751,44 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke", "local"]); console.log('[0, {"response": "5"}]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { file: "script.py" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); + let loggerSpy = sandbox.spy(mockCli, "log"); sandbox.stub(fse, "readFileSync").returns("print(1+4)"); - plugin.hooks["wsgi:exec:local:exec"]().then(() => { + return plugin.hooks["wsgi:exec:local:exec"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"exec","data":"print(1+4)"}}' ); - expect(consoleSpy.calledWith({ response: "5" })).to.be.true; + expect(loggerSpy.calledWith({ response: "5" })).to.be.true; sandbox.restore(); }); }); }); describe("command", () => { + const mockCli = { log: () => { } }; + it("fails when no wsgi handler is set", () => { var plugin = new Plugin( { @@ -1768,15 +1796,15 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "other.handler" } } + functions: { app: { handler: "other.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: mockCli, }, { command: "pwd" } ); - expect(plugin.hooks["wsgi:command:command"]()).to.be.rejectedWith( + return expect(plugin.hooks["wsgi:command:command"]()).to.be.rejectedWith( "No functions were found with handler: wsgi_handler.handler" ); }); @@ -1787,15 +1815,15 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: mockCli, }, {} ); - expect(plugin.hooks["wsgi:command:command"]()).to.be.rejectedWith( + return expect(plugin.hooks["wsgi:command:command"]()).to.be.rejectedWith( "Please provide either a command (-c) or a file (-f)" ); }); @@ -1807,40 +1835,38 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke"]); console.log("non-json output"); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "pwd" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:command:command"]().then(() => { + let loggerSpy = sandbox.spy(mockCli, "log"); + return plugin.hooks["wsgi:command:command"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.c).to.be.undefined; expect(plugin.serverless.pluginManager.cliOptions.context).to.be .undefined; expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(consoleSpy.calledWith("non-json output")).to.be.true; + expect(loggerSpy.calledWith("non-json output")).to.be.true; sandbox.restore(); }); }); @@ -1852,44 +1878,44 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke"]); console.log('[0, "/var/task"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { file: "script.sh" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); + let loggerSpy = sandbox.spy(mockCli, "log"); sandbox.stub(fse, "readFileSync").returns("pwd"); - plugin.hooks["wsgi:command:command"]().then(() => { + return plugin.hooks["wsgi:command:command"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(consoleSpy.calledWith("/var/task")).to.be.true; + expect(loggerSpy.calledWith("/var/task")).to.be.true; sandbox.restore(); }); }); }); describe("command local", () => { + const mockCli = { log: () => { } }; + it("fails when no wsgi handler is set", () => { var plugin = new Plugin( { @@ -1897,15 +1923,17 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "other.handler" } } + functions: { app: { handler: "other.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { mockCli }, }, { command: "pwd" } ); - expect(plugin.hooks["wsgi:command:local:command"]()).to.be.rejectedWith( + return expect( + plugin.hooks["wsgi:command:local:command"]() + ).to.be.rejectedWith( "No functions were found with handler: wsgi_handler.handler" ); }); @@ -1916,15 +1944,17 @@ describe("serverless-wsgi", () => { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, - custom: { wsgi: { app: "api.app" } } + custom: { wsgi: { app: "api.app" } }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { mockCli }, }, {} ); - expect(plugin.hooks["wsgi:command:local:command"]()).to.be.rejectedWith( + return expect( + plugin.hooks["wsgi:command:local:command"]() + ).to.be.rejectedWith( "Please provide either a command (-c) or a file (-f)" ); }); @@ -1936,40 +1966,38 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke", "local"]); - console.log("non-json output"); // eslint-disable-line no-console + mockCli.log("non-json output"); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "pwd" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:command:local:command"]().then(() => { + let loggerSpy = sandbox.spy(mockCli, "log"); + return plugin.hooks["wsgi:command:local:command"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.c).to.be.undefined; expect(plugin.serverless.pluginManager.cliOptions.context).to.be .undefined; expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(consoleSpy.calledWith("non-json output")).to.be.true; + expect(loggerSpy.calledWith("non-json output")).to.be.true; sandbox.restore(); }); }); @@ -1981,88 +2009,153 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke", "local"]); console.log('[0, "/var/task"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { file: "script.sh" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); + let loggerSpy = sandbox.spy(mockCli, "log"); sandbox.stub(fse, "readFileSync").returns("pwd"); - plugin.hooks["wsgi:command:local:command"]().then(() => { + return plugin.hooks["wsgi:command:local:command"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"command","data":"pwd"}}' ); - expect(consoleSpy.calledWith("/var/task")).to.be.true; + expect(loggerSpy.calledWith("/var/task")).to.be.true; sandbox.restore(); }); }); }); describe("manage", () => { - it("calls handler to execute manage commands remotely from argument", () => { - var plugin = new Plugin( + const mockCli = { + log: sinon.spy(), + }; + + let plugin; + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + plugin = new Plugin( { config: { servicePath: "/tmp" }, service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { + app: { handler: "wsgi_handler.handler" }, + otherFunc: { handler: "other_handler.handler" }, + }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { - expect(command).to.deep.equal(["invoke"]); - console.log('[0, "manage command output"]'); // eslint-disable-line no-console - resolve(); - }) - } + run: sandbox.stub().resolves(), + }, }, { command: "check" } ); + }); - var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:manage:manage"]().then(() => { + afterEach(() => { + sandbox.restore(); + }); + + it("calls handler to execute manage commands remotely from argument", () => { + plugin.serverless.pluginManager.run.callsFake((command) => { + expect(command).to.deep.equal(["invoke"]); + console.log('[0, "manage command output"]'); + return BbPromise.resolve(); + }); + + return plugin.hooks["wsgi:manage:manage"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"manage","data":"check"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"manage","data":"check"}}' ); - expect(consoleSpy.calledWith("manage command output")).to.be.true; - sandbox.restore(); + expect(mockCli.log.calledWith("manage command output")).to.be.true; + }); + }); + + it("uses the function specified by --function", () => { + plugin.options.function = "otherFunc"; + + return plugin.hooks["wsgi:manage:manage"]().then(() => { + expect(plugin.serverless.pluginManager.cliOptions.f).to.equal( + "otherFunc" + ); + expect(plugin.options.function).to.equal("otherFunc"); + }); + }); + + it("uses the function specified by -f", () => { + plugin.options.f = "otherFunc"; + + return plugin.hooks["wsgi:manage:manage"]().then(() => { + expect(plugin.serverless.pluginManager.cliOptions.f).to.equal( + "otherFunc" + ); + expect(plugin.options.function).to.equal("otherFunc"); + }); + }); + + it("throws an error when specified function is not found", () => { + plugin.options.function = "nonExistentFunc"; + + return expect(plugin.hooks["wsgi:manage:manage"]()).to.be.rejectedWith( + 'Function "nonExistentFunc" not found.' + ); + }); + + it("falls back to finding wsgi handler when no function is specified", () => { + delete plugin.options.function; + delete plugin.options.f; + + return plugin.hooks["wsgi:manage:manage"]().then(() => { + expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); + expect(plugin.options.function).to.equal("app"); }); }); + + it("rejects when no wsgi handler is found and no function is specified", () => { + delete plugin.options.function; + delete plugin.options.f; + plugin.serverless.service.functions = { + someFunc: { handler: "some_handler.handler" }, + }; + + return expect(plugin.hooks["wsgi:manage:manage"]()).to.be.rejectedWith( + "No functions were found with handler: wsgi_handler.handler" + ); + }); }); describe("manage local", () => { + const mockCli = Object({ log: () => { } }); + it("calls handler to execute manage commands locally from argument", () => { var plugin = new Plugin( { @@ -2070,46 +2163,45 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke", "local"]); console.log('[0, "manage command output"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "check" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:manage:local:manage"]().then(() => { + let loggerSpy = sandbox.spy(mockCli, "log"); + return plugin.hooks["wsgi:manage:local:manage"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.c).to.be.undefined; expect(plugin.serverless.pluginManager.cliOptions.context).to.be .undefined; expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"manage","data":"check"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"manage","data":"check"}}' ); - expect(consoleSpy.calledWith("manage command output")).to.be.true; + expect(loggerSpy.calledWith("manage command output")).to.be.true; sandbox.restore(); }); }); }); describe("flask", () => { + const mockCli = Object({ log: () => { } }); it("calls handler to execute flask commands remotely from argument", () => { var plugin = new Plugin( { @@ -2117,43 +2209,42 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke"]); console.log('[0, "flask command output"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "check" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:flask:flask"]().then(() => { + let loggerSpy = sandbox.spy(mockCli, "log"); + return plugin.hooks["wsgi:flask:flask"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"flask","data":"check"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"flask","data":"check"}}' ); - expect(consoleSpy.calledWith("flask command output")).to.be.true; + expect(loggerSpy.calledWith("flask command output")).to.be.true; sandbox.restore(); }); }); }); describe("flask local", () => { + const mockCli = Object({ log: () => { } }); it("calls handler to execute flask commands locally from argument", () => { var plugin = new Plugin( { @@ -2161,40 +2252,38 @@ describe("serverless-wsgi", () => { service: { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, - functions: { app: { handler: "wsgi_handler.handler" } } + functions: { app: { handler: "wsgi_handler.handler" } }, }, classes: { Error: Error }, - cli: { log: () => {} }, + cli: mockCli, pluginManager: { cliOptions: {}, - run: command => - new BbPromise(resolve => { + run: (command) => + new BbPromise((resolve) => { expect(command).to.deep.equal(["invoke", "local"]); console.log('[0, "flask command output"]'); // eslint-disable-line no-console resolve(); - }) - } + }), + }, }, { command: "check" } ); var sandbox = sinon.createSandbox(); - let consoleSpy = sandbox.spy(console, "log"); - plugin.hooks["wsgi:flask:local:flask"]().then(() => { + let loggerSpy = sandbox.spy(mockCli, "log"); + return plugin.hooks["wsgi:flask:local:flask"]().then(() => { expect(plugin.serverless.pluginManager.cliOptions.c).to.be.undefined; expect(plugin.serverless.pluginManager.cliOptions.context).to.be .undefined; expect(plugin.serverless.pluginManager.cliOptions.f).to.equal("app"); - expect(plugin.serverless.pluginManager.cliOptions.function).to.equal( - "app" - ); + expect(plugin.options.function).to.equal("app"); expect(plugin.serverless.pluginManager.cliOptions.d).to.equal( '{"_serverless-wsgi":{"command":"flask","data":"check"}}' ); - expect(plugin.serverless.pluginManager.cliOptions.data).to.equal( + expect(plugin.options.data).to.equal( '{"_serverless-wsgi":{"command":"flask","data":"check"}}' ); - expect(consoleSpy.calledWith("flask command output")).to.be.true; + expect(loggerSpy.calledWith("flask command output")).to.be.true; sandbox.restore(); }); }); @@ -2204,7 +2293,7 @@ describe("serverless-wsgi", () => { it("installs handler before invocation", () => { var functions = { app: { handler: "wsgi.handler" }, - other: { handler: "other.handler" } + other: { handler: "other.handler" }, }; var plugin = new Plugin( { @@ -2213,40 +2302,42 @@ describe("serverless-wsgi", () => { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, functions: functions, - getFunction: name => functions[name] + getFunction: (name) => functions[name], }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { function: "other" } ); // Test invocation for non-WSGI function, should do nothing - expect(plugin.hooks["before:invoke:local:invoke"]()).to.be.fulfilled; - - plugin.options.function = "app"; - - var sandbox = sinon.createSandbox(); - var copyStub = sandbox.stub(fse, "copyAsync"); - var writeStub = sandbox.stub(fse, "writeFileAsync"); - plugin.hooks["before:invoke:local:invoke"]().then(() => { - expect( - copyStub.calledWith( - path.resolve(__dirname, "wsgi_handler.py"), - "/tmp/wsgi_handler.py" - ) - ).to.be.true; - expect( - copyStub.calledWith( - path.resolve(__dirname, "serverless_wsgi.py"), - "/tmp/serverless_wsgi.py" - ) - ).to.be.true; - expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; - expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ - app: "api.app" + return expect( + plugin.hooks["before:invoke:local:invoke"]() + ).to.be.fulfilled.then(() => { + plugin.options.function = "app"; + + var sandbox = sinon.createSandbox(); + var copyStub = sandbox.stub(fse, "copyAsync"); + var writeStub = sandbox.stub(fse, "writeFileAsync"); + return plugin.hooks["before:invoke:local:invoke"]().then(() => { + expect( + copyStub.calledWith( + path.resolve(__dirname, "wsgi_handler.py"), + "/tmp/wsgi_handler.py" + ) + ).to.be.true; + expect( + copyStub.calledWith( + path.resolve(__dirname, "serverless_wsgi.py"), + "/tmp/serverless_wsgi.py" + ) + ).to.be.true; + expect(writeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; + expect(JSON.parse(writeStub.lastCall.args[1])).to.deep.equal({ + app: "api.app", + }); + sandbox.restore(); }); - sandbox.restore(); }); }); @@ -2258,18 +2349,18 @@ describe("serverless-wsgi", () => { provider: { runtime: "python2.7" }, custom: { wsgi: { app: "api.app" } }, functions: { - app: { handler: "wsgi_handler.handler" } - } + app: { handler: "wsgi_handler.handler" }, + }, }, classes: { Error: Error }, - cli: { log: () => {} } + cli: { log: () => { } }, }, { function: "app" } ); var sandbox = sinon.createSandbox(); var removeStub = sandbox.stub(fse, "removeAsync"); - plugin.hooks["after:invoke:local:invoke"]().then(() => { + return plugin.hooks["after:invoke:local:invoke"]().then(() => { expect(removeStub.calledWith("/tmp/wsgi_handler.py")).to.be.true; expect(removeStub.calledWith("/tmp/serverless_wsgi.py")).to.be.true; expect(removeStub.calledWith("/tmp/.serverless-wsgi")).to.be.true; diff --git a/package-lock.json b/package-lock.json index d7d6fa5..bf41bfb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1015 +1,1401 @@ { "name": "serverless-wsgi", - "version": "1.7.7", - "lockfileVersion": 1, + "version": "3.1.0", + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" + "packages": { + "": { + "name": "serverless-wsgi", + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "bluebird": "^3.7.2", + "command-exists": "^1.2.9", + "fs-extra": "^11.2.0", + "lodash": "^4.17.21", + "process-utils": "^4.0.0" + }, + "devDependencies": { + "chai": "^4.3.10", + "chai-as-promised": "^7.1.1", + "eslint": "^8.50.0", + "istanbul": "^0.4.5", + "mocha": "^10.2.0", + "sinon": "^16.0.0" + }, + "engines": { + "node": ">=10" } }, - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } }, - "@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "@eslint/eslintrc": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz", - "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "requires": { + "dependencies": { "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, "dependencies": { - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - } + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "requires": { + "dependencies": { "type-detect": "4.0.8" } }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "node_modules/@sinonjs/commons/node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" + "dependencies": { + "@sinonjs/commons": "^3.0.0" } }, - "@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", + "node_modules/@sinonjs/samsam": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", + "dependencies": { + "@sinonjs/commons": "^3.0.1", "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" } }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "abbrev": { + "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", "dev": true }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "node_modules/acorn": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.13.0.tgz", + "integrity": "sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } }, - "ajv": { + "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "requires": { + "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "amdefine": { + "node_modules/amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", "dev": true, - "optional": true + "optional": true, + "engines": { + "node": ">=0.4.2" + } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { - "color-convert": "^1.9.0" + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "requires": { + "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" } }, - "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, - "assertion-error": { + "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "async": { + "node_modules/async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", "dev": true }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "bluebird": { + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "requires": { - "fill-range": "^7.0.1" + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" } }, - "browser-stdout": { + "node_modules/browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, - "callsites": { + "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, - "requires": { + "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" } }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "node_modules/chai-as-promised": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", + "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", "dev": true, - "requires": { + "dependencies": { "check-error": "^1.0.2" + }, + "peerDependencies": { + "chai": ">= 2.1.2 < 6" } }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "requires": { - "anymatch": "~3.1.1", + "dependencies": { + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "cliui": { + "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, - "requires": { + "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { - "color-name": "1.1.3" + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "concat-map": { + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" + }, + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "cross-spawn": { + "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "requires": { + "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" } }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, - "requires": { - "ms": "2.1.2" + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "decamelize": { + "node_modules/decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, - "requires": { + "dependencies": { "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" } }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "node_modules/deferred": { + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/deferred/-/deferred-0.7.11.tgz", + "integrity": "sha512-8eluCl/Blx4YOGwMapBvXRKxHXhA8ejDXYzEaK8+/gtcm8hRMhSLmXSqDmNUKNc/C8HNSmuyyp/hflhqDAvK2A==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.50", + "event-emitter": "^0.3.5", + "next-tick": "^1.0.0", + "timers-ext": "^0.1.7" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } }, - "doctrine": { + "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "requires": { + "dependencies": { "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "emoji-regex": { + "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" } }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "escodegen": { + "node_modules/escodegen": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", "dev": true, - "requires": { + "dependencies": { "esprima": "^2.7.1", "estraverse": "^1.9.1", "esutils": "^2.0.2", - "optionator": "^0.8.1", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=0.12.0" + }, + "optionalDependencies": { "source-map": "~0.2.0" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - } + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" } }, - "eslint": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.25.0.tgz", - "integrity": "sha512-TVpSovpvCNpLURIScDRB6g5CYu/ZFq9GfX2hLNIV4dSBKxIWojeDODvYl3t0k0VtMxYeR8OXPCFE5+oHMlGfhw==", + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.0", - "ajv": "^6.10.0", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.21", - "minimatch": "^3.0.4", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.4", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - } + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "requires": { + "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "funding": { + "url": "https://opencollective.com/eslint" } }, - "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", - "dev": true + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true + "node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.10.0" + } }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "requires": { + "dependencies": { "estraverse": "^5.1.0" }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } + "engines": { + "node": ">=0.10" } }, - "esrecurse": { + "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "requires": { + "dependencies": { "estraverse": "^5.2.0" }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } + "engines": { + "node": ">=4.0" } }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } }, - "fast-deep-equal": { + "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "fast-json-stable-stringify": { + "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "fast-levenshtein": { + "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "file-entry-cache": { + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, - "requires": { + "dependencies": { "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "requires": { + "dependencies": { "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "find-up": { + "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "requires": { + "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "flat": { + "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true + "dev": true, + "bin": { + "flat": "cli.js" + } }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, - "requires": { - "flatted": "^3.1.0", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "requires": { - "at-least-node": "^1.0.0", + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true + "node_modules/fs2": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/fs2/-/fs2-0.3.15.tgz", + "integrity": "sha512-T684iG2bR/3g5byqXvYYnJyqkXA7MQdlJx5DvCe0BJ5CH9aMRRc4C11bl75D1MnypvERdJ7Cft5BFpU/eClCMw==", + "dependencies": { + "d": "^1.0.2", + "deferred": "^0.7.11", + "es5-ext": "^0.10.64", + "event-emitter": "^0.3.5", + "ext": "^1.7.0", + "ignore": "^5.3.2", + "memoizee": "^0.4.17", + "type": "^2.7.3" + }, + "engines": { + "node": ">=6" + } }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } }, - "get-caller-file": { + "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "node_modules/glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "requires": { - "fs.realpath": "^1.0.0", + "dependencies": { "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "2 || 3", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "requires": { - "is-glob": "^4.0.1" + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "globals": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz", - "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==", + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "requires": { + "dependencies": { "type-fest": "^0.20.2" }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "requires": { + "dependencies": { "minimist": "^1.2.5", - "neo-async": "^2.6.0", + "neo-async": "^2.6.2", "source-map": "^0.6.1", - "uglify-js": "^3.1.4", "wordwrap": "^1.0.0" }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "hasbin": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/hasbin/-/hasbin-1.2.3.tgz", - "integrity": "sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA=", - "requires": { - "async": "~1.5" + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" } }, - "he": { + "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "dev": true, + "bin": { + "he": "bin/he" + } }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } }, - "import-fresh": { + "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "requires": { + "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "imurmurhash": { + "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "is-binary-path": { + "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, - "requires": { + "dependencies": { "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" } }, - "is-extglob": { + "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "is-fullwidth-code-point": { + "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "requires": { + "dependencies": { "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" } }, - "is-number": { + "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.12.0" + } }, - "is-plain-obj": { + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "istanbul": { + "node_modules/istanbul": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "integrity": "sha512-nMtdn4hvK0HjUlzr1DrKSUY8ychprt8dzHOgY2KXsIhHu5PuQQEOTM27gV9Xblyon7aUH/TSFIjRHEODF/FRPg==", + "deprecated": "This module is no longer maintained, try this instead:\n npm i nyc\nVisit https://istanbul.js.org/integrations for other alternatives.", "dev": true, - "requires": { + "dependencies": { "abbrev": "1.0.x", "async": "1.x", "escodegen": "1.8.x", @@ -1025,870 +1411,1018 @@ "which": "^1.1.1", "wordwrap": "^1.0.0" }, + "bin": { + "istanbul": "lib/cli.js" + } + }, + "node_modules/istanbul/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } + "sprintf-js": "~1.0.2" } }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "node_modules/istanbul/node_modules/has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "node_modules/istanbul/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, - "requires": { + "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/istanbul/node_modules/js-yaml/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/istanbul/node_modules/supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", + "dev": true, + "dependencies": { + "has-flag": "^1.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/istanbul/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "json-schema-traverse": { + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "json-stable-stringify-without-jsonify": { + "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "jsonfile": { + "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "requires": { - "graceful-fs": "^4.1.6", + "dependencies": { "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", "dev": true }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "locate-path": { + "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, - "requires": { + "dependencies": { "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "lodash": { + "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", - "dev": true - }, - "lodash.get": { + "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, - "requires": { - "chalk": "^4.0.0" + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" } }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, - "requires": { - "yallist": "^4.0.0" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "requires": { - "brace-expansion": "^1.1.7" + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.3.2.tgz", - "integrity": "sha512-UdmISwr/5w+uXLPKspgoV7/RXZwKRTiTjJ2/AC5ZiEztIoOYdfKb19+9jNmEInzx5pBsCyJQzarAxqIGBNYJhg==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } }, - "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "natural-compare": { + "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" + "dependencies": { + "@sinonjs/commons": "^3.0.1" } }, - "nopt": { + "node_modules/nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", "dev": true, - "requires": { + "dependencies": { "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" } }, - "normalize-path": { + "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" } }, - "p-limit": { + "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { + "dependencies": { "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-locate": { + "node_modules/p-locate": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "requires": { + "dependencies": { "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "parent-module": { + "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "requires": { + "dependencies": { "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "path-exists": { + "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "path-key": { + "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dev": true, - "requires": { - "isarray": "0.0.1" + "engines": { + "node": ">=8" } }, - "pathval": { + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, + "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", - "dev": true + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "node_modules/process-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-utils/-/process-utils-4.0.0.tgz", + "integrity": "sha512-fMyMQbKCxX51YxR7YGCzPjLsU3yDzXFkP4oi1/Mt5Ixnk7GO/7uUTj8mrCHUwuvozWzI+V7QSJR9cZYnwNOZPg==", + "dependencies": { + "ext": "^1.4.0", + "fs2": "^0.3.9", + "memoizee": "^0.4.14", + "type": "^2.1.0" + }, + "engines": { + "node": ">=10.0" + } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "randombytes": { + "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "requires": { + "dependencies": { "safe-buffer": "^5.1.0" } }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, - "requires": { + "dependencies": { "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "require-directory": { + "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "resolve": { + "node_modules/resolve": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", "dev": true }, - "resolve-from": { + "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } }, - "rimraf": { + "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "requires": { + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, - "requires": { + "dependencies": { "randombytes": "^2.1.0" } }, - "shebang-command": { + "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { + "dependencies": { "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "sinon": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", - "integrity": "sha512-XAn5DxtGVJBlBWYrcYKEhWCz7FLwZGdyvANRyK06419hyEpdT0dMc5A8Vcxg5SCGHc40CsqoKsc1bt1CbJPfNw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "dev": true, + "engines": { + "node": ">=8" } }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/sinon": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-16.1.3.tgz", + "integrity": "sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==", "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, - "source-map": { + "node_modules/source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", "dev": true, "optional": true, - "requires": { + "dependencies": { "amdefine": ">=0.0.4" + }, + "engines": { + "node": ">=0.8.0" } }, - "sprintf-js": { + "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { - "ansi-regex": "^5.0.0" + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "requires": { - "has-flag": "^3.0.0" + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "table": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.5.1.tgz", - "integrity": "sha512-xGDXWTBJxahkzPQCsn1S9ESHEenU7TbMD5Iv4FeopXv/XwJyWatFjfbor+6ipI10/MNPXBYUamYukOrbPZ9L/w==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "ajv": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.1.0.tgz", - "integrity": "sha512-B/Sk2Ix7A36fs/ZkuGLIR86EdjbgR6fsAcbx9lOP/QBSXujDNbVmIS/U4Itz5k8fPFDeVZl/zQ/gJW4Jrq6XjQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "text-table": { + "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "to-regex-range": { + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "requires": { + "dependencies": { "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { - "prelude-ls": "~1.1.2" + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "engines": { + "node": ">=4" + } }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "uglify-js": { - "version": "3.10.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.3.tgz", - "integrity": "sha512-Lh00i69Uf6G74mvYpHCI9KVVXLcHW/xu79YTvH7Mkc9zyKUeSPz0owW0dguj0Scavns3ZOh3wY63J0Zb97Za2g==", + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, - "optional": true + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } }, - "uri-js": { + "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { + "dependencies": { "punycode": "^2.1.0" } }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "engines": { + "node": ">=0.10.0" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { + "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true }, - "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, - "wrap-ansi": { + "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "yargs": { + "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, - "requires": { + "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", @@ -1896,31 +2430,46 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" } }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } }, - "yargs-unparser": { + "node_modules/yargs-unparser": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, - "requires": { + "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index eec6345..c845871 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-wsgi", - "version": "1.7.7", + "version": "3.1.0", "engines": { "node": ">=10" }, @@ -44,20 +44,24 @@ "test": "istanbul cover -x '*.test.js' node_modules/mocha/bin/_mocha '*.test.js' -- -R spec", "lint": "eslint *.js", "pytest": "py.test --cov=serve --cov=requirements --cov=wsgi_handler --cov=serverless_wsgi --cov-report=html", - "pylint": "flake8 --exclude node_modules" + "pylint": "flake8 --exclude node_modules,.devenv" }, "devDependencies": { - "chai": "^4.3.4", - "chai-as-promised": "^7.0.0", - "eslint": "^7.25.0", - "istanbul": "^0.4.4", - "mocha": "^8.3.2", - "sinon": "^10.0.0" + "chai": "^4.3.10", + "chai-as-promised": "^7.1.1", + "eslint": "^8.50.0", + "istanbul": "^0.4.5", + "mocha": "^10.2.0", + "sinon": "^16.0.0" }, "dependencies": { "bluebird": "^3.7.2", - "fs-extra": "^9.1.0", - "hasbin": "^1.2.3", - "lodash": "^4.17.21" + "command-exists": "^1.2.9", + "fs-extra": "^11.2.0", + "lodash": "^4.17.21", + "process-utils": "^4.0.0" + }, + "peerDependences": { + "serverless": "^2.32.0" } } diff --git a/requirements.py b/requirements.py index 7fe6960..fcd2458 100644 --- a/requirements.py +++ b/requirements.py @@ -94,6 +94,9 @@ def package(req_files, target_dir, pip_args=""): "*.pyc", "__pycache__", "_virtualenv.*", + "distutils-precedence.pth", + "six.py", + "zipp.py" ] shutil.copytree( diff --git a/requirements.txt b/requirements.txt index 6fe8da8..60c3568 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -werkzeug +werkzeug>2 diff --git a/serve.py b/serve.py index fc9e14e..854a1c2 100644 --- a/serve.py +++ b/serve.py @@ -34,13 +34,15 @@ def parse_args(): # pragma: no cover parser.add_argument("--disable-threading", action="store_false", dest="use_threads") parser.add_argument("--num-processes", type=int, dest="processes", default=1) - # Optional serving using HTTPS + # Optional serving using HTTPS and passing pub and pri key parser.add_argument("--ssl", action="store_true", dest="ssl") + parser.add_argument("--ssl-pub", dest="ssl_pub") + parser.add_argument("--ssl-pri", dest="ssl_pri") return parser.parse_args() -def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1, ssl=False): +def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1, ssl=False, ssl_keys=None): sys.path.insert(0, cwd) os.environ["IS_OFFLINE"] = "True" @@ -53,7 +55,7 @@ def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1, ssl wsgi_app = getattr(wsgi_module, wsgi_fqn[1]) if ssl: - ssl_context = "adhoc" + ssl_context = ssl_keys or "adhoc" else: ssl_context = None @@ -76,6 +78,18 @@ def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1, ssl ) +def _validate_ssl_keys(cert_file, private_key_file): + if not cert_file and not private_key_file: + return None + if not cert_file or not private_key_file: + sys.exit("Missing either cert file or private key file (hint: --ssl-pub and --ssl-pri )") + if not os.path.exists(cert_file): + sys.exit("Cert file can't be found") + if not os.path.exists(private_key_file): + sys.exit("Private key file can't be found") + return (cert_file, private_key_file) + + if __name__ == "__main__": # pragma: no cover args = parse_args() @@ -86,5 +100,6 @@ def serve(cwd, app, port=5000, host="localhost", threaded=True, processes=1, ssl host=args.host, threaded=args.use_threads, processes=args.processes, - ssl=args.ssl, + ssl=args.ssl or (bool(args.ssl_pub) and bool(args.ssl_pri)), + ssl_keys=_validate_ssl_keys(args.ssl_pub, args.ssl_pri) ) diff --git a/serve_test.py b/serve_test.py index 7713eaf..9fb44e1 100644 --- a/serve_test.py +++ b/serve_test.py @@ -4,6 +4,7 @@ import pytest import serve import sys +import os from werkzeug import serving @@ -46,6 +47,19 @@ def mock_path(monkeypatch): return path +@pytest.fixture +def mock_os_path_exists(monkeypatch): + mock_path = ObjectStub() + mock_path.file_names_that_exist = [] + + def mock_exists(file_name): + return file_name in mock_path.file_names_that_exist + + monkeypatch.setattr(os.path, 'exists', mock_exists) + + return mock_path + + def test_serve(mock_path, mock_importlib, mock_werkzeug): serve.serve("/tmp1", "app.app", "5000") assert len(mock_path) == 1 @@ -160,3 +174,42 @@ def test_serve_non_debuggable_app(mock_path, mock_importlib, mock_werkzeug): serve.serve("/tmp1", "app.app", "5000") assert mock_werkzeug.lastcall.app is None + + +def test_validate_ssl_keys_with_no_keys_passed(): + return_value = serve._validate_ssl_keys(None, None) + assert return_value is None + + +def test_validate_ssl_keys_with_sys_exit_for_missing_key(): + with pytest.raises(SystemExit) as pytest_wrapped_e: + serve._validate_ssl_keys('test.pem', None) + + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == "Missing either cert file or private key file (hint: --ssl-pub and --ssl-pri )" + + +def test_validate_ssl_keys_with_non_existant_cert_file(mock_os_path_exists): + with pytest.raises(SystemExit) as pytest_wrapped_e: + serve._validate_ssl_keys('test.pem', 'test-key.pem') + + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == "Cert file can't be found" + + +def test_validate_ssl_keys_with_non_existant_key_file(mock_os_path_exists): + mock_os_path_exists.file_names_that_exist = ['test.pem'] + with pytest.raises(SystemExit) as pytest_wrapped_e: + serve._validate_ssl_keys('test.pem', 'test-key.pem') + + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == "Private key file can't be found" + + +def test_validate_ssl_keys_with_actual_keys(mock_os_path_exists): + mock_os_path_exists.file_names_that_exist = ['test.pem', 'test-key.pem'] + return_value = serve._validate_ssl_keys('test.pem', 'test-key.pem') + + assert type(return_value) is tuple + assert return_value[0] == 'test.pem' + assert return_value[1] == 'test-key.pem' diff --git a/serverless_wsgi.py b/serverless_wsgi.py index 9cf7ea9..9058eb1 100644 --- a/serverless_wsgi.py +++ b/serverless_wsgi.py @@ -8,14 +8,15 @@ Author: Logan Raarup """ import base64 +import io import json import os import sys -from werkzeug.datastructures import Headers, iter_multi_items, MultiDict -from werkzeug.wrappers import Response -from werkzeug.urls import url_encode, url_unquote, url_unquote_plus +from urllib.parse import urlencode, unquote, unquote_plus + +from werkzeug.datastructures import Headers, iter_multi_items from werkzeug.http import HTTP_STATUS_CODES -from werkzeug._compat import BytesIO, string_types, to_bytes, wsgi_encoding_dance +from werkzeug.wrappers import Response # List of MIME types that should not be base64 encoded. MIME types within `text/*` # are included by default. @@ -81,19 +82,19 @@ def is_alb_event(event): def encode_query_string(event): - params = event.get(u"multiValueQueryStringParameters") + params = event.get("multiValueQueryStringParameters") if not params: - params = event.get(u"queryStringParameters") + params = event.get("queryStringParameters") if not params: - params = event.get(u"query") + params = event.get("query") if not params: params = "" if is_alb_event(event): - params = MultiDict( - (url_unquote_plus(k), url_unquote_plus(v)) + params = [ + (unquote_plus(k), unquote_plus(v)) for k, v in iter_multi_items(params) - ) - return url_encode(params) + ] + return urlencode(params, doseq=True) def get_script_name(headers, request_context): @@ -105,8 +106,8 @@ def get_script_name(headers, request_context): "1", ] - if u"amazonaws.com" in headers.get(u"Host", u"") and not strip_stage_path: - script_name = "/{}".format(request_context.get(u"stage", "")) + if "amazonaws.com" in headers.get("Host", "") and not strip_stage_path: + script_name = "/{}".format(request_context.get("stage", "")) else: script_name = "" return script_name @@ -115,15 +116,15 @@ def get_script_name(headers, request_context): def get_body_bytes(event, body): if event.get("isBase64Encoded", False): body = base64.b64decode(body) - if isinstance(body, string_types): - body = to_bytes(body, charset="utf-8") + if isinstance(body, str): + body = body.encode("utf-8") return body def setup_environ_items(environ, headers): for key, value in environ.items(): - if isinstance(value, string_types): - environ[key] = wsgi_encoding_dance(value) + if isinstance(value, str): + environ[key] = value.encode("utf-8").decode("latin1", "replace") for key, value in headers.items(): key = "HTTP_" + key.upper().replace("-", "_") @@ -133,16 +134,16 @@ def setup_environ_items(environ, headers): def generate_response(response, event): - returndict = {u"statusCode": response.status_code} + returndict = {"statusCode": response.status_code} - if u"multiValueHeaders" in event: - returndict[u"multiValueHeaders"] = group_headers(response.headers) + if "multiValueHeaders" in event and event["multiValueHeaders"]: + returndict["multiValueHeaders"] = group_headers(response.headers) else: - returndict[u"headers"] = split_headers(response.headers) + returndict["headers"] = split_headers(response.headers) if is_alb_event(event): # If the request comes from ALB we need to add a status description - returndict["statusDescription"] = u"%d %s" % ( + returndict["statusDescription"] = "%d %s" % ( response.status_code, HTTP_STATUS_CODES[response.status_code], ) @@ -150,23 +151,39 @@ def generate_response(response, event): if response.data: mimetype = response.mimetype or "text/plain" if ( - mimetype.startswith("text/") or mimetype in TEXT_MIME_TYPES + mimetype.startswith("text/") or mimetype in TEXT_MIME_TYPES ) and not response.headers.get("Content-Encoding", ""): returndict["body"] = response.get_data(as_text=True) returndict["isBase64Encoded"] = False else: - returndict["body"] = base64.b64encode(response.data).decode("utf-8") + returndict["body"] = base64.b64encode( + response.data).decode("utf-8") returndict["isBase64Encoded"] = True return returndict +def strip_express_gateway_query_params(path): + """Contrary to regular AWS lambda HTTP events, Express Gateway + (https://github.com/ExpressGateway/express-gateway-plugin-lambda) + adds query parameters to the path, which we need to strip. + """ + if "?" in path: + path = path.split("?")[0] + return path + + def handle_request(app, event, context): if event.get("source") in ["aws.events", "serverless-plugin-warmup"]: print("Lambda warming event received, skipping handler") return {} - if event.get("version") is None and event.get("isBase64Encoded") is None: + if ( + event.get("version") is None + and event.get("isBase64Encoded") is None + and event.get("requestPath") is not None + and not is_alb_event(event) + ): return handle_lambda_integration(app, event, context) if event.get("version") == "2.0": @@ -176,63 +193,53 @@ def handle_request(app, event, context): def handle_payload_v1(app, event, context): - if u"multiValueHeaders" in event: - headers = Headers(event[u"multiValueHeaders"]) + if "multiValueHeaders" in event and event["multiValueHeaders"]: + headers = Headers(event["multiValueHeaders"]) else: - headers = Headers(event[u"headers"]) + headers = Headers(event["headers"]) script_name = get_script_name(headers, event.get("requestContext", {})) # If a user is using a custom domain on API Gateway, they may have a base # path in their URL. This allows us to strip it out via an optional # environment variable. - path_info = event[u"path"] + path_info = strip_express_gateway_query_params(event["path"]) base_path = os.environ.get("API_GATEWAY_BASE_PATH") if base_path: script_name = "/" + base_path if path_info.startswith(script_name): - path_info = path_info[len(script_name) :] + path_info = path_info[len(script_name):] - body = event[u"body"] or "" + body = event.get("body") or "" body = get_body_bytes(event, body) environ = { "CONTENT_LENGTH": str(len(body)), - "CONTENT_TYPE": headers.get(u"Content-Type", ""), - "PATH_INFO": url_unquote(path_info), + "CONTENT_TYPE": headers.get("Content-Type", ""), + "PATH_INFO": unquote(path_info), "QUERY_STRING": encode_query_string(event), - "REMOTE_ADDR": event.get(u"requestContext", {}) - .get(u"identity", {}) - .get(u"sourceIp", ""), - "REMOTE_USER": event.get(u"requestContext", {}) - .get(u"authorizer", {}) - .get(u"principalId", ""), - "REQUEST_METHOD": event.get(u"httpMethod", {}), + "REMOTE_ADDR": event.get("requestContext", {}) + .get("identity", {}) + .get("sourceIp", ""), + "REMOTE_USER": (event.get("requestContext", {}) + .get("authorizer") or {}) + .get("principalId", ""), + "REQUEST_METHOD": event.get("httpMethod", {}), "SCRIPT_NAME": script_name, - "SERVER_NAME": headers.get(u"Host", "lambda"), - "SERVER_PORT": headers.get(u"X-Forwarded-Port", "80"), + "SERVER_NAME": headers.get("Host", "lambda"), + "SERVER_PORT": headers.get("X-Forwarded-Port", "443"), "SERVER_PROTOCOL": "HTTP/1.1", "wsgi.errors": sys.stderr, - "wsgi.input": BytesIO(body), + "wsgi.input": io.BytesIO(body), "wsgi.multiprocess": False, "wsgi.multithread": False, "wsgi.run_once": False, - "wsgi.url_scheme": headers.get(u"X-Forwarded-Proto", "http"), + "wsgi.url_scheme": headers.get("X-Forwarded-Proto", "https"), "wsgi.version": (1, 0), - "serverless.authorizer": event.get(u"requestContext", {}).get(u"authorizer"), + "serverless.authorizer": event.get("requestContext", {}).get("authorizer"), "serverless.event": event, "serverless.context": context, - # TODO: Deprecate the following entries, as they do not comply with the WSGI - # spec. For custom variables, the spec says: - # - # Finally, the environ dictionary may also contain server-defined variables. - # These variables should be named using only lower-case letters, numbers, dots, - # and underscores, and should be prefixed with a name that is unique to the - # defining server or gateway. - "API_GATEWAY_AUTHORIZER": event.get(u"requestContext", {}).get(u"authorizer"), - "event": event, - "context": context, } environ = setup_environ_items(environ, headers) @@ -244,11 +251,17 @@ def handle_payload_v1(app, event, context): def handle_payload_v2(app, event, context): - headers = Headers(event[u"headers"]) + headers = Headers(event["headers"]) script_name = get_script_name(headers, event.get("requestContext", {})) - path_info = event[u"rawPath"] + path_info = strip_express_gateway_query_params(event["rawPath"]) + base_path = os.environ.get("API_GATEWAY_BASE_PATH") + if base_path: + script_name = "/" + base_path + + if path_info.startswith(script_name): + path_info = path_info[len(script_name):] body = event.get("body", "") body = get_body_bytes(event, body) @@ -256,43 +269,33 @@ def handle_payload_v2(app, event, context): headers["Cookie"] = "; ".join(event.get("cookies", [])) environ = { - "CONTENT_LENGTH": str(len(body)), - "CONTENT_TYPE": headers.get(u"Content-Type", ""), - "PATH_INFO": url_unquote(path_info), - "QUERY_STRING": url_encode(event.get(u"queryStringParameters", {})), + "CONTENT_LENGTH": str(len(body or "")), + "CONTENT_TYPE": headers.get("Content-Type", ""), + "PATH_INFO": unquote(path_info), + "QUERY_STRING": event.get("rawQueryString", ""), "REMOTE_ADDR": event.get("requestContext", {}) - .get(u"http", {}) - .get(u"sourceIp", ""), + .get("http", {}) + .get("sourceIp", ""), "REMOTE_USER": event.get("requestContext", {}) - .get(u"authorizer", {}) - .get(u"principalId", ""), + .get("authorizer", {}) + .get("principalId", ""), "REQUEST_METHOD": event.get("requestContext", {}) .get("http", {}) .get("method", ""), "SCRIPT_NAME": script_name, - "SERVER_NAME": headers.get(u"Host", "lambda"), - "SERVER_PORT": headers.get(u"X-Forwarded-Port", "80"), + "SERVER_NAME": headers.get("Host", "lambda"), + "SERVER_PORT": headers.get("X-Forwarded-Port", "443"), "SERVER_PROTOCOL": "HTTP/1.1", "wsgi.errors": sys.stderr, - "wsgi.input": BytesIO(body), + "wsgi.input": io.BytesIO(body), "wsgi.multiprocess": False, "wsgi.multithread": False, "wsgi.run_once": False, - "wsgi.url_scheme": headers.get(u"X-Forwarded-Proto", "http"), + "wsgi.url_scheme": headers.get("X-Forwarded-Proto", "https"), "wsgi.version": (1, 0), - "serverless.authorizer": event.get("requestContext", {}).get(u"authorizer"), + "serverless.authorizer": event.get("requestContext", {}).get("authorizer"), "serverless.event": event, "serverless.context": context, - # TODO: Deprecate the following entries, as they do not comply with the WSGI - # spec. For custom variables, the spec says: - # - # Finally, the environ dictionary may also contain server-defined variables. - # These variables should be named using only lower-case letters, numbers, dots, - # and underscores, and should be prefixed with a name that is unique to the - # defining server or gateway. - "API_GATEWAY_AUTHORIZER": event.get("requestContext", {}).get(u"authorizer"), - "event": event, - "context": context, } environ = setup_environ_items(environ, headers) @@ -305,13 +308,13 @@ def handle_payload_v2(app, event, context): def handle_lambda_integration(app, event, context): - headers = Headers(event[u"headers"]) + headers = Headers(event["headers"]) script_name = get_script_name(headers, event) - path_info = event[u"requestPath"] + path_info = strip_express_gateway_query_params(event["requestPath"]) - for key, value in event.get(u"path", {}).items(): + for key, value in event.get("path", {}).items(): path_info = path_info.replace("{%s}" % key, value) path_info = path_info.replace("{%s+}" % key, value) @@ -320,37 +323,27 @@ def handle_lambda_integration(app, event, context): body = get_body_bytes(event, body) environ = { - "CONTENT_LENGTH": str(len(body)), - "CONTENT_TYPE": headers.get(u"Content-Type", ""), - "PATH_INFO": url_unquote(path_info), - "QUERY_STRING": url_encode(event.get(u"query", {})), - "REMOTE_ADDR": event.get("identity", {}).get(u"sourceIp", ""), + "CONTENT_LENGTH": str(len(body or "")), + "CONTENT_TYPE": headers.get("Content-Type", ""), + "PATH_INFO": unquote(path_info), + "QUERY_STRING": urlencode(event.get("query", {}), doseq=True), + "REMOTE_ADDR": event.get("identity", {}).get("sourceIp", ""), "REMOTE_USER": event.get("principalId", ""), "REQUEST_METHOD": event.get("method", ""), "SCRIPT_NAME": script_name, - "SERVER_NAME": headers.get(u"Host", "lambda"), - "SERVER_PORT": headers.get(u"X-Forwarded-Port", "80"), + "SERVER_NAME": headers.get("Host", "lambda"), + "SERVER_PORT": headers.get("X-Forwarded-Port", "443"), "SERVER_PROTOCOL": "HTTP/1.1", "wsgi.errors": sys.stderr, - "wsgi.input": BytesIO(body), + "wsgi.input": io.BytesIO(body), "wsgi.multiprocess": False, "wsgi.multithread": False, "wsgi.run_once": False, - "wsgi.url_scheme": headers.get(u"X-Forwarded-Proto", "http"), + "wsgi.url_scheme": headers.get("X-Forwarded-Proto", "https"), "wsgi.version": (1, 0), "serverless.authorizer": event.get("enhancedAuthContext"), "serverless.event": event, "serverless.context": context, - # TODO: Deprecate the following entries, as they do not comply with the WSGI - # spec. For custom variables, the spec says: - # - # Finally, the environ dictionary may also contain server-defined variables. - # These variables should be named using only lower-case letters, numbers, dots, - # and underscores, and should be prefixed with a name that is unique to the - # defining server or gateway. - "API_GATEWAY_AUTHORIZER": event.get("enhancedAuthContext"), - "event": event, - "context": context, } environ = setup_environ_items(environ, headers) diff --git a/setup.py b/setup.py index 54ca58f..c450dce 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,8 @@ setuptools.setup( name="serverless-wsgi", - version="1.7.7", + version="3.1.0", + python_requires=">3.6", author="Logan Raarup", author_email="logan@logan.dk", description="Amazon AWS API Gateway WSGI wrapper", @@ -13,10 +14,9 @@ long_description_content_type="text/markdown", url="https://github.com/logandk/serverless-wsgi", py_modules=["serverless_wsgi"], - install_requires=["werkzeug"], + install_requires=["werkzeug>2"], classifiers=( "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", diff --git a/wsgi_handler.py b/wsgi_handler.py index 2b7cfc9..6803d26 100644 --- a/wsgi_handler.py +++ b/wsgi_handler.py @@ -7,10 +7,13 @@ Author: Logan Raarup """ import importlib +import io import json +import logging import os import sys import traceback +from werkzeug.exceptions import InternalServerError # Call decompression helper from `serverless-python-requirements` if # available. See: https://github.com/UnitedIncome/serverless-python-requirements#dealing-with-lambdas-size-limitations @@ -23,16 +26,14 @@ def load_config(): - """ Read the configuration file created during deployment - """ + """Read the configuration file created during deployment""" root = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(root, ".serverless-wsgi"), "r") as f: return json.loads(f.read()) def import_app(config): - """ Load the application WSGI handler - """ + """Load the application WSGI handler""" wsgi_fqn = config["app"].rsplit(".", 1) wsgi_fqn_parts = wsgi_fqn[0].rsplit("/", 1) @@ -44,29 +45,26 @@ def import_app(config): wsgi_module = importlib.import_module(wsgi_fqn_parts[-1]) return getattr(wsgi_module, wsgi_fqn[1]) - except: # noqa - traceback.print_exc() - raise Exception("Unable to import {}".format(config["app"])) + except Exception as err: + logging.exception("Unable to import app: '{}' - {}".format(config["app"], err)) + return InternalServerError("Unable to import app: {}".format(config["app"])) def append_text_mime_types(config): - """ Append additional text (non-base64) mime types from configuration file - """ + """Append additional text (non-base64) mime types from configuration file""" if "text_mime_types" in config and isinstance(config["text_mime_types"], list): serverless_wsgi.TEXT_MIME_TYPES.extend(config["text_mime_types"]) def handler(event, context): - """ Lambda event handler, invokes the WSGI wrapper and handles command invocation - """ + """Lambda event handler, invokes the WSGI wrapper and handles command invocation""" if "_serverless-wsgi" in event: import shlex import subprocess - from werkzeug._compat import StringIO, to_native native_stdout = sys.stdout native_stderr = sys.stderr - output_buffer = StringIO() + output_buffer = io.StringIO() try: sys.stdout = output_buffer @@ -81,7 +79,7 @@ def handler(event, context): result = subprocess.check_output( meta.get("data", ""), shell=True, stderr=subprocess.STDOUT ) - output_buffer.write(to_native(result)) + output_buffer.write(result.decode()) elif meta.get("command") == "manage": # Run Django management commands from django.core import management diff --git a/wsgi_handler_test.py b/wsgi_handler_test.py index a38f977..b1ae43a 100644 --- a/wsgi_handler_test.py +++ b/wsgi_handler_test.py @@ -1,22 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import print_function - -try: - import __builtin__ as builtins -except ImportError: # pragma: no cover - import builtins - +import builtins import importlib import json import os -import sys import pytest -from werkzeug.datastructures import MultiDict +import sys +from urllib.parse import urlencode from werkzeug.wrappers import Request, Response -from werkzeug.urls import url_encode - -PY2 = sys.version_info[0] == 2 # Reference to open() before monkeypatching original_open = open @@ -36,7 +27,7 @@ def __init__(self): def __call__(self, environ, start_response): self.last_environ = environ - response = Response(u"Hello World ☃!", mimetype=self.response_mimetype) + response = Response("Hello World ☃!", mimetype=self.response_mimetype) cookies = [ ("CUSTOMER", "WILE_E_COYOTE"), ("PART_NUMBER", "ROCKET_LAUNCHER_0002"), @@ -134,7 +125,8 @@ def mock_text_mime_wsgi_app_file(monkeypatch): with manager.open("/tmp/.serverless-wsgi", "w") as f: f.write( json.dumps( - {"app": "app.app", "text_mime_types": ["application/custom+json"]} + {"app": "app.app", "text_mime_types": [ + "application/custom+json"]} ) ) monkeypatch.setattr(builtins, "open", manager.open) @@ -199,6 +191,13 @@ def event_v1(): } +@pytest.fixture +def event_v1_offline(event_v1): + event = event_v1 + del event["body"] + return event + + @pytest.fixture def elb_event(): return { @@ -242,7 +241,7 @@ def test_handler(mock_wsgi_app_file, mock_app, event_v1, capsys, wsgi_handler): response = wsgi_handler.handler(event_v1, {"memory_limit_in_mb": "128"}) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "set-cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -277,7 +276,7 @@ def test_handler(mock_wsgi_app_file, mock_app, event_v1, capsys, wsgi_handler): "HTTP_X_FORWARDED_PORT": "443", "HTTP_X_FORWARDED_PROTO": "https", "PATH_INFO": "/some/path", - "QUERY_STRING": url_encode(event_v1["queryStringParameters"]), + "QUERY_STRING": urlencode(event_v1["queryStringParameters"], doseq=True), "REMOTE_ADDR": "76.20.166.147", "REMOTE_USER": "wile_e_coyote", "REQUEST_METHOD": "GET", @@ -292,9 +291,6 @@ def test_handler(mock_wsgi_app_file, mock_app, event_v1, capsys, wsgi_handler): "wsgi.run_once": False, "wsgi.url_scheme": "https", "wsgi.version": (1, 0), - "API_GATEWAY_AUTHORIZER": {"principalId": "wile_e_coyote"}, - "context": {"memory_limit_in_mb": "128"}, - "event": event_v1, "serverless.authorizer": {"principalId": "wile_e_coyote"}, "serverless.context": {"memory_limit_in_mb": "128"}, "serverless.event": event_v1, @@ -305,7 +301,16 @@ def test_handler(mock_wsgi_app_file, mock_app, event_v1, capsys, wsgi_handler): assert err == "application debug #1\n" -def test_handler_multivalue(mock_wsgi_app_file, mock_app, event_v1, capsys, wsgi_handler): +def test_handler_offline(mock_wsgi_app_file, mock_app, event_v1_offline, wsgi_handler): + response = wsgi_handler.handler( + event_v1_offline, {"memory_limit_in_mb": "128"}) + + assert response["body"] == "Hello World ☃!" + + +def test_handler_multivalue( + mock_wsgi_app_file, mock_app, event_v1, capsys, wsgi_handler +): event_v1["multiValueQueryStringParameters"] = { "param1": ["value1"], "param2": ["value2", "value3"], @@ -322,16 +327,18 @@ def test_handler_multivalue(mock_wsgi_app_file, mock_app, event_v1, capsys, wsgi response = wsgi_handler.handler(event_v1, {"memory_limit_in_mb": "128"}) query_string = wsgi_handler.wsgi_app.last_environ["QUERY_STRING"] - assert query_string == url_encode( - MultiDict( + print(query_string) + assert query_string == urlencode( + [ (i, k) for i, j in event_v1["multiValueQueryStringParameters"].items() for k in j - ) + ], + doseq=True ) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "multiValueHeaders": { "Content-Length": ["16"], "Content-Type": ["text/plain; charset=utf-8"], @@ -358,7 +365,7 @@ def test_handler_single_cookie(mock_wsgi_app_file, mock_app, event_v1, wsgi_hand response = wsgi_handler.handler(event_v1, {}) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "Set-Cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -374,7 +381,7 @@ def test_handler_no_cookie(mock_wsgi_app_file, mock_app, event_v1, wsgi_handler) response = wsgi_handler.handler(event_v1, {}) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "Content-Length": "16", "Content-Type": "text/plain; charset=utf-8", @@ -429,7 +436,7 @@ def test_handler_custom_domain(mock_wsgi_app_file, mock_app, event_v1, wsgi_hand "HTTP_X_FORWARDED_PORT": "443", "HTTP_X_FORWARDED_PROTO": "https", "PATH_INFO": "/some/path", - "QUERY_STRING": url_encode(event_v1["queryStringParameters"]), + "QUERY_STRING": urlencode(event_v1["queryStringParameters"], doseq=True), "REMOTE_ADDR": "76.20.166.147", "REMOTE_USER": "wile_e_coyote", "REQUEST_METHOD": "GET", @@ -444,9 +451,6 @@ def test_handler_custom_domain(mock_wsgi_app_file, mock_app, event_v1, wsgi_hand "wsgi.run_once": False, "wsgi.url_scheme": "https", "wsgi.version": (1, 0), - "API_GATEWAY_AUTHORIZER": {"principalId": "wile_e_coyote"}, - "context": {}, - "event": event_v1, "serverless.authorizer": {"principalId": "wile_e_coyote"}, "serverless.context": {}, "serverless.event": event_v1, @@ -487,7 +491,7 @@ def test_handler_api_gateway_base_path( "HTTP_X_FORWARDED_PORT": "443", "HTTP_X_FORWARDED_PROTO": "https", "PATH_INFO": "/some/path", - "QUERY_STRING": url_encode(event_v1["queryStringParameters"]), + "QUERY_STRING": urlencode(event_v1["queryStringParameters"]), "REMOTE_ADDR": "76.20.166.147", "REMOTE_USER": "wile_e_coyote", "REQUEST_METHOD": "GET", @@ -502,9 +506,6 @@ def test_handler_api_gateway_base_path( "wsgi.run_once": False, "wsgi.url_scheme": "https", "wsgi.version": (1, 0), - "API_GATEWAY_AUTHORIZER": {"principalId": "wile_e_coyote"}, - "context": {}, - "event": event_v1, "serverless.authorizer": {"principalId": "wile_e_coyote"}, "serverless.context": {}, "serverless.event": event_v1, @@ -527,7 +528,7 @@ def test_handler_base64(mock_wsgi_app_file, mock_app, event_v1, wsgi_handler): response = wsgi_handler.handler(event_v1, {}) assert response == { - "body": u"SGVsbG8gV29ybGQg4piDIQ==", + "body": "SGVsbG8gV29ybGQg4piDIQ==", "headers": { "Set-Cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -552,7 +553,7 @@ def test_handler_plain(mock_wsgi_app_file, mock_app, event_v1, wsgi_handler): response = wsgi_handler.handler(event_v1, {}) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "Set-Cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -583,19 +584,21 @@ def test_non_package_subdir_app(mock_subdir_wsgi_app_file, mock_app, wsgi_handle assert wsgi_handler.wsgi_app.module == "app" -def test_handler_binary_request_body(mock_wsgi_app_file, mock_app, event_v1, wsgi_handler): +def test_handler_binary_request_body( + mock_wsgi_app_file, mock_app, event_v1, wsgi_handler +): event_v1["body"] = ( - u"LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5VTRDZE5CRWVLQWxIaGRRcQ0KQ29udGVu" - u"dC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ3YXQiDQoNCmhleW9vb3Bw" - u"cHBwDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlVNENkTkJFZUtBbEhoZFFxDQpD" - u"b250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImZpbGVUb1VwbG9h" - u"ZCI7IGZpbGVuYW1lPSJGRjREMDAtMC44LnBuZyINCkNvbnRlbnQtVHlwZTogaW1h" - u"Z2UvcG5nDQoNColQTkcNChoKAAAADUlIRFIAAAABAAAAAQEDAAAAJdtWygAAAANQ" - u"TFRF/00AXDU4fwAAAAF0Uk5TzNI0Vv0AAAAKSURBVHicY2IAAAAGAAM2N3yoAAAA" - u"AElFTkSuQmCCDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlVNENkTkJFZUtBbEho" - u"ZFFxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9InN1Ym1p" - u"dCINCg0KVXBsb2FkIEltYWdlDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlVNENk" - u"TkJFZUtBbEhoZFFxLS0NCg==" + "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5VTRDZE5CRWVLQWxIaGRRcQ0KQ29udGVu" + "dC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ3YXQiDQoNCmhleW9vb3Bw" + "cHBwDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlVNENkTkJFZUtBbEhoZFFxDQpD" + "b250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImZpbGVUb1VwbG9h" + "ZCI7IGZpbGVuYW1lPSJGRjREMDAtMC44LnBuZyINCkNvbnRlbnQtVHlwZTogaW1h" + "Z2UvcG5nDQoNColQTkcNChoKAAAADUlIRFIAAAABAAAAAQEDAAAAJdtWygAAAANQ" + "TFRF/00AXDU4fwAAAAF0Uk5TzNI0Vv0AAAAKSURBVHicY2IAAAAGAAM2N3yoAAAA" + "AElFTkSuQmCCDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlVNENkTkJFZUtBbEho" + "ZFFxDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9InN1Ym1p" + "dCINCg0KVXBsb2FkIEltYWdlDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlVNENk" + "TkJFZUtBbEhoZFFxLS0NCg==" ) event_v1["headers"][ "Content-Type" @@ -608,17 +611,17 @@ def test_handler_binary_request_body(mock_wsgi_app_file, mock_app, event_v1, wsg environ = wsgi_handler.wsgi_app.last_environ assert environ["CONTENT_LENGTH"] == "496" - assert Request(environ).form["submit"] == u"Upload Image" + assert Request(environ).form["submit"] == "Upload Image" def test_handler_request_body_undecodable_with_latin1( mock_wsgi_app_file, mock_app, event_v1, wsgi_handler ): event_v1["body"] = ( - u"------WebKitFormBoundary3vA72kRLuq9D3NdL\r\n" + "------WebKitFormBoundary3vA72kRLuq9D3NdL\r\n" u'Content-Disposition: form-data; name="text"\r\n\r\n' - u"テスト 테스트 测试\r\n" - u"------WebKitFormBoundary3vA72kRLuq9D3NdL--" + "テスト 테스트 测试\r\n" + "------WebKitFormBoundary3vA72kRLuq9D3NdL--" ) event_v1["headers"][ "Content-Type" @@ -628,7 +631,7 @@ def test_handler_request_body_undecodable_with_latin1( wsgi_handler.handler(event_v1, {}) environ = wsgi_handler.wsgi_app.last_environ - assert Request(environ).form["text"] == u"テスト 테스트 测试" + assert Request(environ).form["text"] == "テスト 테스트 测试" def test_handler_custom_text_mime_types( @@ -639,7 +642,7 @@ def test_handler_custom_text_mime_types( response = wsgi_handler.handler(event_v1, {}) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "Set-Cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -654,7 +657,7 @@ def test_handler_alb(mock_wsgi_app_file, mock_app, wsgi_handler, elb_event): response = wsgi_handler.handler(elb_event, {}) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "set-cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -669,15 +672,13 @@ def test_handler_alb(mock_wsgi_app_file, mock_app, wsgi_handler, elb_event): def test_alb_query_params(mock_wsgi_app_file, mock_app, wsgi_handler, elb_event): - elb_event["queryStringParameters"] = { - "test": "test%20test" - } + elb_event["queryStringParameters"] = {"test": "test%20test"} response = wsgi_handler.handler(elb_event, {}) query_string = wsgi_handler.wsgi_app.last_environ["QUERY_STRING"] assert query_string == "test=test+test" assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "set-cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -692,20 +693,18 @@ def test_alb_query_params(mock_wsgi_app_file, mock_app, wsgi_handler, elb_event) def test_alb_multi_query_params(mock_wsgi_app_file, mock_app, wsgi_handler, elb_event): - del(elb_event["queryStringParameters"]) + del elb_event["queryStringParameters"] elb_event["multiValueQueryStringParameters"] = { "%E6%B8%AC%E8%A9%A6": ["%E3%83%86%E3%82%B9%E3%83%88", "test"], - "test": "test%20test" + "test": "test%20test", } response = wsgi_handler.handler(elb_event, {}) query_string = wsgi_handler.wsgi_app.last_environ["QUERY_STRING"] - assert query_string == url_encode({ - "測試": ["テスト", "test"], - "test": "test test" - }) + assert query_string == urlencode( + {"測試": ["テスト", "test"], "test": "test test"}, doseq=True) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "set-cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -754,8 +753,8 @@ def test_command_command(mock_wsgi_app_file, mock_app, wsgi_handler): {}, ) - assert response[0] == 2 - assert "'non-existing-filename': No such file or directory" in response[1] + assert response[0] > 0 + assert "No such file or directory" in response[1] def test_command_manage(mock_wsgi_app_file, mock_app, wsgi_handler): @@ -813,11 +812,17 @@ def test_command_unknown(mock_wsgi_app_file, mock_app, wsgi_handler): assert "Exception: Unknown command: unknown" in response[1] -def test_app_import_error(mock_wsgi_app_file, mock_app_with_import_error, event_v1): - with pytest.raises(Exception, match="Unable to import app.app"): - if "wsgi_handler" in sys.modules: - del sys.modules["wsgi_handler"] - import wsgi_handler # noqa: F401 +def test_app_import_error(mock_wsgi_app_file, mock_app_with_import_error, event_v1, wsgi_handler): + response = wsgi_handler.handler(event_v1, {}) + assert response == { + "statusCode": 500, + "body": "\n\n500 Internal Server Error\n

Internal Server Error

\n

Unable to import app: app.app

\n", + "headers": { + "Content-Type": "text/html; charset=utf-8", + "Content-Length": "140" + }, + "isBase64Encoded": False + } def test_handler_with_encoded_characters_in_path( @@ -856,7 +861,7 @@ def event_v2(): "X-Forwarded-Proto": "https", "cache-control": "no-cache", }, - "queryStringParameters": {"param1": "value1", "param2": "value2, value3"}, + "queryStringParameters": {"param1": "value1", "param2": "value2,value3"}, "requestContext": { "accountId": "16794", "apiId": "3z6kd9fbb1", @@ -868,13 +873,13 @@ def event_v2(): "path": "/some/path", "protocol": "HTTP/1.1", "sourceIp": "76.20.166.147", - "userAgent": "agent" + "userAgent": "agent", }, "requestId": "ad2db740-10a2-11e7-8ced-35048084babb", "stage": "dev", "routeKey": "$default", "time": "12/Mar/2020:19:03:58 +0000", - "timeEpoch": 1583348638390 + "timeEpoch": 1583348638390, }, "pathParameters": {"proxy": "some/path"}, "isBase64Encoded": False, @@ -886,7 +891,7 @@ def test_handler_v2(mock_wsgi_app_file, mock_app, event_v2, capsys, wsgi_handler response = wsgi_handler.handler(event_v2, {"memory_limit_in_mb": "128"}) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "set-cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -921,7 +926,7 @@ def test_handler_v2(mock_wsgi_app_file, mock_app, event_v2, capsys, wsgi_handler "HTTP_X_FORWARDED_PORT": "443", "HTTP_X_FORWARDED_PROTO": "https", "PATH_INFO": "/some/path", - "QUERY_STRING": url_encode(event_v2["queryStringParameters"]), + "QUERY_STRING": "param1=value1¶m2=value2¶m2=value3", "REMOTE_ADDR": "76.20.166.147", "REMOTE_USER": "wile_e_coyote", "REQUEST_METHOD": "GET", @@ -936,9 +941,6 @@ def test_handler_v2(mock_wsgi_app_file, mock_app, event_v2, capsys, wsgi_handler "wsgi.run_once": False, "wsgi.url_scheme": "https", "wsgi.version": (1, 0), - "API_GATEWAY_AUTHORIZER": {"principalId": "wile_e_coyote"}, - "context": {"memory_limit_in_mb": "128"}, - "event": event_v2, "serverless.authorizer": {"principalId": "wile_e_coyote"}, "serverless.context": {"memory_limit_in_mb": "128"}, "serverless.event": event_v2, @@ -950,7 +952,7 @@ def test_handler_v2(mock_wsgi_app_file, mock_app, event_v2, capsys, wsgi_handler def test_handler_with_encoded_characters_in_path_v2( - mock_wsgi_app_file, mock_app, event_v2, capsys, wsgi_handler + mock_wsgi_app_file, mock_app, event_v2, capsys, wsgi_handler ): event_v2["rawPath"] = "/city/new%20york" wsgi_handler.handler(event_v2, {"memory_limit_in_mb": "128"}) @@ -964,13 +966,11 @@ def event_lambda_integration(): "method": "GET", "principalId": "testuser", "stage": "dev", - "cognitoPoolClaims": { - "sub": "" - }, + "cognitoPoolClaims": {"sub": ""}, "enhancedAuthContext": { "principalId": "testuser", "integrationLatency": "1031", - "contextTest": "123" + "contextTest": "123", }, "headers": { "Accept": "*/*", @@ -988,14 +988,10 @@ def event_lambda_integration(): "X-Amzn-Trace-Id": "Root=1-5055b7d3-751afb497f81bab2759b6e7b", "X-Forwarded-For": "83.23.10.243, 130.166.149.164", "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "query": { - "q": "test" - }, - "path": { - "p": "path2" + "X-Forwarded-Proto": "https", }, + "query": {"q": "test"}, + "path": {"p": "path2"}, "identity": { "cognitoIdentityPoolId": "", "accountId": "", @@ -1008,18 +1004,22 @@ def event_lambda_integration(): "cognitoAuthenticationProvider": "", "userArn": "", "userAgent": "curl/7.68.0", - "user": "" + "user": "", }, "stageVariables": {}, - "requestPath": "/some/{p}" + "requestPath": "/some/{p}", } -def test_handler_lambda(mock_wsgi_app_file, mock_app, event_lambda_integration, capsys, wsgi_handler): - response = wsgi_handler.handler(event_lambda_integration, {"memory_limit_in_mb": "128"}) +def test_handler_lambda( + mock_wsgi_app_file, mock_app, event_lambda_integration, capsys, wsgi_handler +): + response = wsgi_handler.handler( + event_lambda_integration, {"memory_limit_in_mb": "128"} + ) assert response == { - "body": u"Hello World ☃!", + "body": "Hello World ☃!", "headers": { "set-cookie": "CUSTOMER=WILE_E_COYOTE; Path=/", "Content-Length": "16", @@ -1066,17 +1066,10 @@ def test_handler_lambda(mock_wsgi_app_file, mock_app, event_lambda_integration, "wsgi.run_once": False, "wsgi.url_scheme": "https", "wsgi.version": (1, 0), - "API_GATEWAY_AUTHORIZER": { - "principalId": "testuser", - "integrationLatency": "1031", - "contextTest": "123" - }, - "context": {"memory_limit_in_mb": "128"}, - "event": event_lambda_integration, "serverless.authorizer": { "principalId": "testuser", "integrationLatency": "1031", - "contextTest": "123" + "contextTest": "123", }, "serverless.context": {"memory_limit_in_mb": "128"}, "serverless.event": event_lambda_integration, @@ -1087,7 +1080,10 @@ def test_handler_lambda(mock_wsgi_app_file, mock_app, event_lambda_integration, assert err == "application debug #1\n" -def test_handler_lambda_error(mock_wsgi_app_file, mock_app, event_lambda_integration, capsys, wsgi_handler): +def test_handler_lambda_error( + mock_wsgi_app_file, mock_app, event_lambda_integration, capsys, wsgi_handler +): mock_app.status_code = 400 with pytest.raises(Exception, match='"statusCode": 400'): - wsgi_handler.handler(event_lambda_integration, {"memory_limit_in_mb": "128"}) + wsgi_handler.handler(event_lambda_integration, { + "memory_limit_in_mb": "128"})