diff --git a/.eslintrc b/.eslintrc index f67a2ac..6bbeb5d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { - "extends": ["eslint:recommended", "eslint-config-hapi"], + "extends": ["eslint:recommended", "plugin:@hapi/recommended"], "parserOptions": { - "ecmaVersion": 2018, + "ecmaVersion": 2020, "sourceType": "module" }, "env": { diff --git a/.vscode/launch.json b/.vscode/launch.json index ed31437..ada4412 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,18 +1,18 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "node", - "request": "launch", - "name": "Tape Current File", - "program": "${workspaceFolder}/node_modules/tape/bin/tape", - "args": [ - "${file}" - ], - "console": "internalConsole" - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Tape Current File", + "program": "${workspaceFolder}/node_modules/tape/bin/tape", + "args": [ + "${file}" + ], + "console": "internalConsole" + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f5be58..87f51a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - Don't set undefined parameters on request in `routeExt` (#180) - Update enjoi and @hapi/joi to Joi 17 (#176) -### 2.0.1 +### 2.0.1 - Removed left over console.log file - Fixed `unknown` being used on all joi schemas as opposed to just objects @@ -43,7 +43,8 @@ ### 1.2.0 -- Vendor extensions are now stripped from the API docs end point (option `docs.stripExtensions`). +- Vendor extensions are now stripped from the API docs end point (option + `docs.stripExtensions`). - Bumped to Enjoi 4.x. ### 1.1.0 @@ -56,11 +57,13 @@ ### 1.0.4 -- Resolves https://github.com/krakenjs/hapi-openapi/issues/123, fixing array format support in parameters. +- Resolves https://github.com/krakenjs/hapi-openapi/issues/123, fixing array + format support in parameters. ### 1.0.3 -- Resolves https://github.com/krakenjs/hapi-openapi/issues/125, correcting `date-time` format. +- Resolves https://github.com/krakenjs/hapi-openapi/issues/125, correcting + `date-time` format. ### 1.0.2 @@ -105,7 +108,8 @@ ### 3.3.1 -- Fixed https://github.com/krakenjs/swaggerize-hapi/issues/72 - allow single item arrays. +- Fixed https://github.com/krakenjs/swaggerize-hapi/issues/72 - allow single + item arrays. ### 3.3.0 @@ -127,7 +131,9 @@ - [BREAKING] Migrated to Hapi 17 and Node 8. - [BREAKING] Severed from `swaggerize-routes` - this module is now standalone. -- [BREAKING] `server.plugins.swagger.api` is now `server.plugins.swagger.getApi()`. -- [BREAKING] `handlers` object doesn't namespace http methods using `$` anymore. Assumption is verb is last in object path. +- [BREAKING] `server.plugins.swagger.api` is now + `server.plugins.swagger.getApi()`. +- [BREAKING] `handlers` object doesn't namespace http methods using `$` anymore. + Assumption is verb is last in object path. - [BREAKING] Currently does not work with the `swaggerize-generator`. - Routes will specify what they allow based on api spec. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 91db1a2..8a43cc6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,45 +2,73 @@ ## Our Pledge -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual identity +and orientation. ## Our Standards -Examples of behavior that contributes to creating a positive environment include: +Examples of behavior that contributes to creating a positive environment +include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting ## Our Responsibilities -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. ## Scope -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at nodejs@paypal.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at nodejs@paypal.com. The project team +will review and investigate all complaints, and will respond in a way that it +deems appropriate to the circumstances. The project team is obligated to +maintain confidentiality with regard to the reporter of an incident. Further +details of specific enforcement policies may be posted separately. -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ diff --git a/README.md b/README.md index 66f0bcf..73b55ad 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ ### Note: this project was renamed from 'swaggerize-hapi' to 'hapi-openapi'. -`hapi-openapi` is a design-driven approach to building RESTful services with [OpenAPI (Swagger)](http://openapis.org) and [Hapi](http://hapijs.com). +`hapi-openapi` is a design-driven approach to building RESTful services with +[OpenAPI (Swagger)](http://openapis.org) and [Hapi](http://hapijs.com). `hapi-openapi` provides the following features: @@ -16,17 +17,21 @@ ### Why "Design Driven" -There are already a number of modules that help build RESTful APIs for node with OpenAPI. However, -these modules tend to focus on building the documentation or specification as a side effect of writing -the application business logic. +There are already a number of modules that help build RESTful APIs for node with +OpenAPI. However, these modules tend to focus on building the documentation or +specification as a side effect of writing the application business logic. -`hapi-openapi` begins with the OpenAPI document first. This facilitates writing APIs that are easier to design, review, and test. +`hapi-openapi` begins with the OpenAPI document first. This facilitates writing +APIs that are easier to design, review, and test. -At runtime, `hapi-openapi` uses the API specification to build routes from previously defined paths. This ensures that everything specified is what is implemented. +At runtime, `hapi-openapi` uses the API specification to build routes from +previously defined paths. This ensures that everything specified is what is +implemented. ### Quick Start with a Generator -This guide will let you go from an `api.json` to a service project in no time flat. +This guide will let you go from an `api.json` to a service project in no time +flat. First install `generator-swaggerize` (and `yo` if you haven't already): @@ -44,26 +49,28 @@ $ yo swaggerize Follow the prompts (note: make sure to choose `hapi` as your framework choice). -You now have a working api and can use something like [SwaggerHub](https://swaggerhub.com/?_ga=2.118604234.2143392684.1515431456-1673703125.1481054263) to explore it. +You now have a working api and can use something like +[SwaggerHub](https://swaggerhub.com/?_ga=2.118604234.2143392684.1515431456-1673703125.1481054263) +to explore it. ### Manual Usage ```javascript -const Hapi = require('@hapi/hapi'); +const Hapi = require("@hapi/hapi"); const Path = require("path"); -const server = new Hapi.Server( { port: 3000 } ); +const server = new Hapi.Server({ port: 3000 }); -async function init () { +async function init() { await server.register({ - plugin: require('hapi-openapi'), + plugin: require("hapi-openapi"), options: { - api: Path.join(__dirname, './config/pets.json'), - handlers: Path.join(__dirname, './handlers') - } + api: Path.join(__dirname, "./config/pets.json"), + handlers: Path.join(__dirname, "./handlers"), + }, }); await server.start(); - console.log( server.info.uri ); + console.log(server.info.uri); } init(); @@ -71,33 +78,46 @@ init(); ### Hapi Plugin -The plugin will be registered as `openapi` on `server.plugins` with the following exposed: +The plugin will be registered as `openapi` on `server.plugins` with the +following exposed: - `getApi()` - the resolved Swagger document. -- `setHost(host)` - a helper function for setting the `host` property on the `api`. +- `setHost(host)` - a helper function for setting the `host` property on the + `api`. ### Configuration Options -- `api` - a path to a valid OpenAPI 2.0 document, or a valid document in the form of an object. -- *deprecated* `docspath` - the path to expose api docs for swagger-ui, etc. Defaults to `/api-docs`. +- `api` - a path to a valid OpenAPI 2.0 document, or a valid document in the + form of an object. +- _deprecated_ `docspath` - the path to expose api docs for swagger-ui, etc. + Defaults to `/api-docs`. - `docs` - an object used to configure the api docs route. - - `path` - the path to expose api docs for swagger-ui, etc. Defaults to `/api-docs`. - - `auth` - options auth config for this route. - - `stripExtensions` - strip vendor extensions from docs. Defaults to true. - - `prefixBasePath` - prefix path of docs with he OpenAPI document's `basePath` value. Defaults to true. -- `handlers` - either a string directory structure for route handlers, object, or not set if using `x-hapi-handler`. -- `extensions` - an array of file extension types to use when scanning for handlers. Defaults to `['js']`. -- `vhost` - *optional* domain string (see [hapi route options](http://hapijs.com/api#route-options)). -- `cors` - *optional* cors setting (see [hapi route options](http://hapijs.com/api#route-options)). -- `outputvalidation` - *optional* validate response data. + - `path` - the path to expose api docs for swagger-ui, etc. Defaults to + `/api-docs`. + - `auth` - options auth config for this route. + - `stripExtensions` - strip vendor extensions from docs. Defaults to true. + - `prefixBasePath` - prefix path of docs with he OpenAPI document's `basePath` + value. Defaults to true. +- `handlers` - either a string directory structure for route handlers, object, + or not set if using `x-hapi-handler`. +- `extensions` - an array of file extension types to use when scanning for + handlers. Defaults to `['js']`. +- `vhost` - _optional_ domain string (see + [hapi route options](http://hapi.dev/api#route-options)). +- `cors` - _optional_ cors setting (see + [hapi route options](http://hapi.dev/api#route-options)). +- `outputvalidation` - _optional_ validate response data. ### Mount Path -Api `path` values will be prefixed with the OpenAPI document's `basePath` value. This behavior can be negated if you set the option `docs.prefixBasePath` to `false`. +Api `path` values will be prefixed with the OpenAPI document's `basePath` value. +This behavior can be negated if you set the option `docs.prefixBasePath` to +`false`. ### Handlers Directory -The `options.handlers` option specifies a directory to scan for handlers. These handlers are bound to the api `paths` defined in the OpenAPI document. +The `options.handlers` option specifies a directory to scan for handlers. These +handlers are bound to the api `paths` defined in the OpenAPI document. ``` handlers @@ -117,7 +137,8 @@ baz.js => /baz ### Path Parameters -The file and directory names in the handlers directory can also represent path parameters. +The file and directory names in the handlers directory can also represent path +parameters. For example, to represent the path `/users/{id}`: @@ -141,7 +162,8 @@ To represent `/users/{id}/foo`. ### Handlers File -Each provided javascript file should export an object containing functions with HTTP verbs as keys. +Each provided javascript file should export an object containing functions with +HTTP verbs as keys. Example: @@ -153,7 +175,8 @@ module.exports = { } ``` -Optionally, `pre` handlers can be used by providing an array of handlers for a method: +Optionally, `pre` handlers can be used by providing an array of handlers for a +method: ```javascript module.exports = { @@ -166,7 +189,8 @@ module.exports = { ### Handlers Object -The directory generation will yield this object, but it can be provided directly as `options.handlers`. +The directory generation will yield this object, but it can be provided directly +as `options.handlers`. Example: @@ -185,7 +209,8 @@ Example: ### X-Hapi-Handler -Alternatively the API document can set `x-hapi-handler` attribute on each defined `paths` element if `handlers` is not defined. +Alternatively the API document can set `x-hapi-handler` attribute on each +defined `paths` element if `handlers` is not defined. Example: @@ -201,7 +226,9 @@ This will construct a `handlers` object from the given `x-hapi-handler` files. ### X-Hapi-Options -There is now support at the operations level for `x-hapi-options` which represent individual [Hapi Route Optijons](https://github.com/hapijs/hapi/blob/master/API.md#route-options). +There is now support at the operations level for `x-hapi-options` which +represent individual +[Hapi Route Optijons](https://github.com/hapijs/hapi/blob/master/API.md#route-options). This support is limited to configuration supported by the JSON file type. @@ -220,9 +247,15 @@ Example: ### Authentication -Support for OpenAPI [security schemes](http://swagger.io/specification/#securitySchemeObject) requires that relevant authentication scheme and strategy are registered before the hapi-openapi plugin. See the [hapi docs](http://hapijs.com/tutorials/auth) for information about authentication schemes and strategies. +Support for OpenAPI +[security schemes](http://swagger.io/specification/#securitySchemeObject) +requires that relevant authentication scheme and strategy are registered before +the hapi-openapi plugin. See the [hapi docs](http://hapijs.com/tutorials/auth) +for information about authentication schemes and strategies. -The name of the hapi authentication strategy is expected to match the name field of the OpenAPI [security requirement object](http://swagger.io/specification/#securityRequirementObject). +The name of the hapi authentication strategy is expected to match the name field +of the OpenAPI +[security requirement object](http://swagger.io/specification/#securityRequirementObject). Example: @@ -244,28 +277,30 @@ const server = new Hapi.Server(); await server.register({ plugin: AuthTokenScheme }); -server.auth.strategy('api_key', 'auth-token-scheme', { - validateFunc: async function (token) { - // Implement validation here, return { credentials, artifacts }. - } +server.auth.strategy("api_key", "auth-token-scheme", { + validateFunc: async function (token) { + // Implement validation here, return { credentials, artifacts }. + }, }); await server.register({ - plugin: require('hapi-openapi'), - options: { - api: require('./config/pets.json'), - handlers: Path.join(__dirname, './handlers') - } + plugin: require("hapi-openapi"), + options: { + api: require("./config/pets.json"), + handlers: Path.join(__dirname, "./handlers"), + }, }); ``` ### X-Hapi-Auth -Alternatively it may be easier to automatically register a plugin to handle registering the necessary schemes and strategies. +Alternatively it may be easier to automatically register a plugin to handle +registering the necessary schemes and strategies. **x-hapi-auth-schemes** -The root document can contain an `x-hapi-auth-schemes` object specifying different plugins responsible for registering auth schemes. +The root document can contain an `x-hapi-auth-schemes` object specifying +different plugins responsible for registering auth schemes. Example: @@ -281,7 +316,8 @@ This plugin will be passed the following options: **x-hapi-auth-strategy** -The `securityDefinitions` entries can contain an `x-hapi-auth-strategy` attribute pointing to a plugin responsible for registering auth strategies. +The `securityDefinitions` entries can contain an `x-hapi-auth-strategy` +attribute pointing to a plugin responsible for registering auth strategies. Example: @@ -298,29 +334,37 @@ Example: The plugin will be passed the following options: -- `name` - the `securityDefinitions` entry's key. In this example, `api_key`. This is typically used as the strategy name. -- `scheme` - the `securityDefinitions` `type`. In this example, `apiKey`. This should match a `x-hapi-auth-scheme` name. -- `where` - `securityDefinitions` entry `in` attribute. This is search for the `lookup` value; in this example `header`. -- `lookup` - `securityDefinitions` entry `name` attribute. Used as the name to look up against `where`. +- `name` - the `securityDefinitions` entry's key. In this example, `api_key`. + This is typically used as the strategy name. +- `scheme` - the `securityDefinitions` `type`. In this example, `apiKey`. This + should match a `x-hapi-auth-scheme` name. +- `where` - `securityDefinitions` entry `in` attribute. This is search for the + `lookup` value; in this example `header`. +- `lookup` - `securityDefinitions` entry `name` attribute. Used as the name to + look up against `where`. -The way you can make these play together is that for every `type`, a scheme exists that delegates some lookup or evaluation to the appropriate strategy. +The way you can make these play together is that for every `type`, a scheme +exists that delegates some lookup or evaluation to the appropriate strategy. Example: ```javascript //xauth-scheme.js -const register = function (server, { name }) { - server.auth.scheme(name /*apiKey*/, (server, /* options received from the strategy */ { validate }) => { - return { - authenticate: async function (request, h) { - return h.authenticated(await validate(request)); - } - }; - }); +const register = function (server, { name }) { + server.auth.scheme( + name, /*apiKey*/ + (server, /* options received from the strategy */ { validate }) => { + return { + authenticate: async function (request, h) { + return h.authenticated(await validate(request)); + }, + }; + }, + ); }; -module.exports = { register, name: 'x-hapi-auth-scheme' }; +module.exports = { register, name: "x-hapi-auth-scheme" }; ``` and @@ -328,23 +372,27 @@ and ```javascript //xauth-strategy.js -const Boom = require('@hapi/boom'); +const Boom = require("@hapi/boom"); const register = function (server, { name, scheme, where, lookup }) { - server.auth.strategy(name, /* the scheme to use this strategy with */ scheme, { - //Define a validate function for the scheme above to receive - validate: async function (request) { - const token = request.headers[lookup]; - - //Some arbitrary example - if (token === '12345') { - return { credentials: { scope: ['read'] }, artifacts: { token } }; - } - - throw Boom.unauthorized(); + server.auth.strategy( + name, + /* the scheme to use this strategy with */ scheme, + { + //Define a validate function for the scheme above to receive + validate: async function (request) { + const token = request.headers[lookup]; + + //Some arbitrary example + if (token === "12345") { + return { credentials: { scope: ["read"] }, artifacts: { token } }; } - }); + + throw Boom.unauthorized(); + }, + }, + ); }; -module.exports = { register, name: 'x-hapi-auth-strategy' }; +module.exports = { register, name: "x-hapi-auth-strategy" }; ``` diff --git a/lib/caller.js b/lib/caller.js index 8eae627..166470b 100644 --- a/lib/caller.js +++ b/lib/caller.js @@ -1,5 +1,3 @@ -'use strict'; - /* * Copy of Erik's caller module with node 8 support added. */ @@ -10,28 +8,28 @@ * @blessings: https://twitter.com/eriktoth/statuses/413719312273125377 * @see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi */ -const caller = function (depth) { - const pst = Error.prepareStackTrace; +export const caller = function (depth) { + const pst = Error.prepareStackTrace; - Error.prepareStackTrace = function (_, frames) { - const stack = frames.map((frame) => { - return frame.getFileName(); - }); + Error.prepareStackTrace = function (_, frames) { + const stack = frames.map((frame) => { + return frame.getFileName(); + }); - Error.prepareStackTrace = pst; + Error.prepareStackTrace = pst; - return stack; - }; + return stack; + }; - const stack = (new Error()).stack.slice(2); + const stack = (new Error()).stack.slice(2); - let file; + let file; - do { - file = stack.shift(); - } while (stack.length && file.match(/module\.js?/)); + do { + file = stack.shift(); + } while (stack.length && file.match(/module\.js?/)); - return file; + return file; }; -module.exports = caller; +export default caller; diff --git a/lib/index.js b/lib/index.js index e467278..51cda0e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,198 +1,219 @@ -'use strict'; - -const Package = require('../package.json'); -const Joi = require('joi'); -const Hoek = require('@hapi/hoek'); -const Caller = require('./caller'); -const Path = require('path'); -const Parser = require('swagger-parser'); -const Utils = require('./utils'); -const Routes = require('./routes'); -const Yaml = require('js-yaml'); -const Fs = require('fs'); -const Util = require('util'); +// import Package from '../package.json'; +const Package = { + version: "3.0.5", +}; +import Joi from "joi"; +import Hoek from "@hapi/hoek"; +import Caller from "./caller.js"; +import Path from "path"; +import Parser from "swagger-parser"; +import Utils from "./utils.js"; +import { create } from "./routes.js"; +import Yaml from "js-yaml"; +import Fs from "fs"; const CALLER_DIR = Path.resolve(Path.dirname(Caller())); const optionsSchema = Joi.object({ - api: Joi.alternatives(Joi.string(), Joi.object().unknown(true)), - //deprecated - docspath: Joi.string().default('/api-docs'), - docs: Joi.object({ - path: Joi.string().default('/api-docs'), - auth: Joi.alternatives().try(Joi.object(), Joi.boolean()).allow(null), - stripExtensions: Joi.boolean().default(true), - prefixBasePath: Joi.boolean().default(true) - }).default(), - cors: Joi.alternatives().try(Joi.object(), Joi.boolean()).default(true), - vhost: Joi.string().allow(null), - handlers: Joi.alternatives().try(Joi.string().default(Path.join(CALLER_DIR, 'routes')), Joi.object()).allow(null), - extensions: Joi.array().items(Joi.string()).default(['js']), - outputvalidation: Joi.boolean().default(false) + api: Joi.alternatives(Joi.string(), Joi.object().unknown(true)), + //deprecated + docspath: Joi.string().default("/api-docs"), + docs: Joi.object({ + path: Joi.string().default("/api-docs"), + auth: Joi.alternatives().try(Joi.object(), Joi.boolean()).allow(null), + stripExtensions: Joi.boolean().default(true), + prefixBasePath: Joi.boolean().default(true), + }).default(), + cors: Joi.alternatives().try(Joi.object(), Joi.boolean()).default(true), + vhost: Joi.string().allow(null), + handlers: Joi.alternatives().try( + Joi.string().default(Path.join(CALLER_DIR, "routes")), + Joi.object(), + ).allow(null), + extensions: Joi.array().items(Joi.string()).default(["js"]), + outputvalidation: Joi.boolean().default(false), }).required(); const stripVendorExtensions = function (obj) { - if (Array.isArray(obj)) { - const clean = []; - for (const value of obj) { - clean.push(stripVendorExtensions(value)); - } - - return clean; + if (Array.isArray(obj)) { + const clean = []; + for (const value of obj) { + clean.push(stripVendorExtensions(value)); } - if (Util.isObject(obj)) { - const clean = {}; - for (const [key, value] of Object.entries(obj)) { - if (!key.match(/\x-(.*)/)) { - clean[key] = stripVendorExtensions(value); - } - } + return clean; + } - return clean; + if (typeof obj === "object") { + const clean = {}; + for (const [key, value] of Object.entries(obj)) { + if (!key.match(/\x-(.*)/)) { + clean[key] = stripVendorExtensions(value); + } } - return obj; + return clean; + } + + return obj; }; -const requireApi = function (path) { - let document; +const requireApi = async function (path) { + let document; - if (path.match(/\.ya?ml?/)) { - const file = Fs.readFileSync(path); - document = Yaml.load(file); - } - else { - document = require(path); - } + if (path.match(/\.ya?ml?/)) { + const file = Fs.readFileSync(path); + document = Yaml.load(file); + } else { + document = await import(path); + document = Utils.mergeDefault(document); + } - return document; + return document; }; const register = async function (server, options, next) { - - //Validator needs to be explicitly declared for Hapi v19.* - server.validator(require('joi')); - - const validation = optionsSchema.validate(options); - - Hoek.assert(!validation.error, validation.error); - - const { api, cors, vhost, handlers, extensions, outputvalidation } = validation.value; - let { docs, docspath } = validation.value; - const spec = await Parser.validate(api); - - // Cannot use conflicting url pathnames, so opting to mount the first url pathname - if (spec.openapi) { - spec.basePath = new URL(Hoek.reach(spec, ['servers', 0, 'url'])).pathname; - } - - spec.basePath = Utils.unsuffix(Utils.prefix(spec.basePath || '/', '/'), '/'); - - //Expose plugin api - server.expose({ - getApi() { - return spec; + //Validator needs to be explicitly declared for Hapi v19.* + server.validator(Joi); + + const validation = optionsSchema.validate(options); + + Hoek.assert(!validation.error, validation.error); + + const { api, cors, vhost, handlers, extensions, outputvalidation } = + validation.value; + let { docs, docspath } = validation.value; + const spec = await Parser.validate(api); + + // Cannot use conflicting url pathnames, so opting to mount the first url pathname + if (spec.openapi) { + spec.basePath = new URL(Hoek.reach(spec, ["servers", 0, "url"])).pathname; + } + + spec.basePath = Utils.unsuffix(Utils.prefix(spec.basePath || "/", "/"), "/"); + + //Expose plugin api + server.expose({ + getApi() { + return spec; + }, + setHost: function setHost(host) { + spec.host = host; + }, + }); + + let basedir; + let apiDocument; + + if (typeof api === "string") { + apiDocument = await requireApi(api); + basedir = Path.dirname(Path.resolve(api)); + } else { + apiDocument = api; + basedir = CALLER_DIR; + } + + if (spec["x-hapi-auth-schemes"]) { + for (const [name, path] of Object.entries(spec["x-hapi-auth-schemes"])) { + let scheme = await import(Path.resolve(Path.join(basedir, path))); + scheme = Utils.mergeDefault(scheme); + + await server.register({ + plugin: scheme, + options: { + name, }, - setHost: function setHost(host) { - spec.host = host; - } - }); - - let basedir; - let apiDocument; - - if (typeof api === 'string') { - apiDocument = requireApi(api); - basedir = Path.dirname(Path.resolve(api)); - } - else { - apiDocument = api; - basedir = CALLER_DIR; + }); } - - if (spec['x-hapi-auth-schemes']) { - for (const [name, path] of Object.entries(spec['x-hapi-auth-schemes'])) { - const scheme = require(Path.resolve(Path.join(basedir, path))); - - await server.register({ - plugin: scheme, - options: { - name - } - }); - } - } - - let securitySchemes; - - if (spec.swagger && spec.securityDefinitions) { - securitySchemes = spec.securityDefinitions; - } - - if (spec.openapi && spec.components && spec.components.securitySchemes) { - securitySchemes = spec.components.securitySchemes; - } - - if (securitySchemes) { - for (const [name, security] of Object.entries(securitySchemes)) { - if (security['x-hapi-auth-strategy']) { - const strategy = require(Path.resolve(Path.join(basedir, security['x-hapi-auth-strategy']))); - - await server.register({ - plugin: strategy, - options: { - name, - scheme: security.type, - lookup: security.name, - where: security.in - } - }); - } - } - } - - if (docspath !== '/api-docs' && docs.path === '/api-docs') { - server.log(['warn'], 'docspath is deprecated. Use docs instead.'); - docs = { - path: docspath, - prefixBasePath: docs.prefixBasePath - }; - } - - let apiPath = docs.path; - if (docs.prefixBasePath){ - docs.path = Utils.prefix(docs.path, '/'); - docs.path = Utils.unsuffix(docs.path, '/'); - apiPath = spec.basePath + docs.path; - } - - if (docs.stripExtensions) { - apiDocument = stripVendorExtensions(apiDocument); - } - - //API docs route - server.route({ - method: 'GET', - path: apiPath, - config: { - handler(request, h) { - return apiDocument; - }, - cors, - id: `${apiPath.replace(/\//g, '_')}`, - description: 'The OpenAPI document.', - tags: ['api', 'documentation'], - auth: docs.auth - }, - vhost - }); - - const routes = await Routes.create(server, { api: spec, basedir, cors, vhost, handlers, extensions, outputvalidation }); - - for (const route of routes) { - server.route(route); + } + + let securitySchemes; + + if (spec.swagger && spec.securityDefinitions) { + securitySchemes = spec.securityDefinitions; + } + + if (spec.openapi && spec.components && spec.components.securitySchemes) { + securitySchemes = spec.components.securitySchemes; + } + + if (securitySchemes) { + for (const [name, security] of Object.entries(securitySchemes)) { + if (security["x-hapi-auth-strategy"]) { + let strategy = await import( + Path.resolve(Path.join(basedir, security["x-hapi-auth-strategy"])) + ); + strategy = Utils.mergeDefault(strategy); + + await server.register({ + plugin: strategy, + options: { + name, + scheme: security.type, + lookup: security.name, + where: security.in, + }, + }); + } } + } + + if (docspath !== "/api-docs" && docs.path === "/api-docs") { + server.log(["warn"], "docspath is deprecated. Use docs instead."); + docs = { + path: docspath, + prefixBasePath: docs.prefixBasePath, + }; + } + + let apiPath = docs.path; + if (docs.prefixBasePath) { + docs.path = Utils.prefix(docs.path, "/"); + docs.path = Utils.unsuffix(docs.path, "/"); + apiPath = spec.basePath + docs.path; + } + + if (docs.stripExtensions) { + apiDocument = stripVendorExtensions(apiDocument); + } + + //API docs route + server.route({ + method: "GET", + path: apiPath, + config: { + handler(request, h) { + return apiDocument; + }, + cors, + id: `${apiPath.replace(/\//g, "_")}`, + description: "The OpenAPI document.", + tags: ["api", "documentation"], + auth: docs.auth, + }, + vhost, + }); + + const routes = await create(server, { + api: spec, + basedir, + cors, + vhost, + handlers, + extensions, + outputvalidation, + }); + + for (const route of routes) { + server.route(route); + } }; -module.exports.plugin = { register, name: 'openapi', version: Package.version, multiple: true }; +export default { + plugin: { + register, + name: "openapi", + version: Package.version, + multiple: true, + }, +}; diff --git a/lib/routes.js b/lib/routes.js index e4cb2b0..2405cd2 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -1,142 +1,167 @@ -'use strict'; - -const ObjectFiles = require('merge-object-files'); -const Validators = require('./validators'); -const Hoek = require('@hapi/hoek'); -const Utils = require('./utils'); -const Path = require('path'); -const Props = require('dot-prop'); +import ObjectFiles from "merge-object-files-es6"; +import Validators from "./validators.js"; +import Hoek from "@hapi/hoek"; +import Utils from "./utils.js"; +import Path from "path"; +import Props from "dot-prop"; + +const SEPARATOR = "/"; + +export const create = async function ( + server, + { api, basedir, cors, vhost, handlers, extensions, outputvalidation }, +) { + const routes = []; + const validator = Validators.create({ api }); + + if (typeof handlers === "string") { + handlers = await ObjectFiles.merge(handlers, extensions); + } + + //Support x-hapi-handler when no handlers set. + if (!handlers) { + for (const [path, operations] of Object.entries(api.paths)) { + if (operations["x-hapi-handler"]) { + const pathnames = path.split("/").slice(1).join("."); -const SEPARATOR = '/'; + if (!handlers) { + handlers = {}; + } -const create = async function (server, { api, basedir, cors, vhost, handlers, extensions, outputvalidation }) { - const routes = []; - const validator = Validators.create({ api }); + const xhandler = await import( + Path.resolve(Path.join(basedir, operations["x-hapi-handler"])) + ); - if (typeof handlers === 'string') { - handlers = await ObjectFiles.merge(handlers, extensions); + Props.set(handlers, pathnames, Utils.mergeDefault(xhandler)); + } } - - //Support x-hapi-handler when no handlers set. - if (!handlers) { - for (const [path, operations] of Object.entries(api.paths)) { - if (operations['x-hapi-handler']) { - const pathnames = path.split('/').slice(1).join('.'); - - if (!handlers) { - handlers = {}; - } - - const xhandler = require(Path.resolve(Path.join(basedir, operations['x-hapi-handler']))); - - Props.set(handlers, pathnames, xhandler); - } + } + + for (const [path, operations] of Object.entries(api.paths)) { + const pathnames = Utils.unsuffix(path, "/").split("/").slice(1).join( + SEPARATOR, + ); + + for (const [method, operation] of Object.entries(operations)) { + const pathsearch = `${pathnames}${SEPARATOR}${method}`; + const handler = Hoek.reach(handlers, pathsearch, { + separator: SEPARATOR, + }); + const xoptions = operation["x-hapi-options"] || {}; + + if (!handler) { + continue; + } + + const customTags = operation.tags || []; + const options = Object.assign({ + cors, + id: operation.operationId, + // hapi does not support empty descriptions + description: operation.description !== "" + ? operation.description + : undefined, + tags: ["api", ...customTags], + }, xoptions); + + options.handler = handler; + + if (Utils.canCarry(method)) { + options.payload = options.payload + ? Hoek.applyToDefaults( + { allow: operation.consumes || api.consumes }, + options.payload, + ) + : { allow: operation.consumes || api.consumes }; + } + + if (Array.isArray(handler)) { + options.pre = []; + + for (let i = 0; i < handler.length - 1; ++i) { + options.pre.push({ + assign: handler[i].name || "p" + (i + 1), + method: handler[i], + }); } - } - - for (const [path, operations] of Object.entries(api.paths)) { - const pathnames = Utils.unsuffix(path, '/').split('/').slice(1).join(SEPARATOR); - for (const [method, operation] of Object.entries(operations)) { - const pathsearch = `${pathnames}${SEPARATOR}${method}`; - const handler = Hoek.reach(handlers, pathsearch, { separator: SEPARATOR }); - const xoptions = operation['x-hapi-options'] || {}; - - if (!handler) { - continue; + options.handler = handler[handler.length - 1]; + } + + const skipValidation = options.payload && options.payload.parse === false; + + if ((operation.parameters || operation.requestBody) && !skipValidation) { + const allowUnknownProperties = xoptions.validate && + xoptions.validate.options && + xoptions.validate.options.allowUnknown === true; + const v = validator.makeAll( + operation.parameters, + operation.requestBody, + operation.consumes || api.consumes, + api.openapi, + allowUnknownProperties, + ); + options.validate = v.validate; + options.ext = { + onPreAuth: { method: v.routeExt }, + }; + } + + if (outputvalidation && operation.responses) { + options.response = {}; + options.response.status = validator.makeResponseValidator( + operation.responses, + api.openapi, + ); + } + + if (operation.security === undefined && api.security) { + operation.security = api.security; + } + + if (operation.security && operation.security.length) { + for (const secdef of operation.security) { + const securitySchemes = Object.keys(secdef); + if (!securitySchemes.length) { + options.auth = options.auth || { access: {}, mode: "optional" }; + options.auth.mode = "optional"; + } + + for (const securityDefinitionName of securitySchemes) { + let securityDefinition; + if (api.swagger) { + securityDefinition = + api.securityDefinitions[securityDefinitionName]; } - const customTags = operation.tags || []; - const options = Object.assign({ - cors, - id: operation.operationId, - // hapi does not support empty descriptions - description: operation.description !== '' ? operation.description : undefined, - tags: ['api', ...customTags] - }, xoptions); - - options.handler = handler; - - if (Utils.canCarry(method)) { - options.payload = options.payload ? Hoek.applyToDefaults({ allow: operation.consumes || api.consumes }, options.payload) : { allow: operation.consumes || api.consumes }; + if (api.openapi) { + securityDefinition = + api.components.securitySchemes[securityDefinitionName]; } - if (Array.isArray(handler)) { - options.pre = []; + Hoek.assert(securityDefinition, "Security scheme not defined."); - for (let i = 0; i < handler.length - 1; ++i) { - options.pre.push({ - assign: handler[i].name || 'p' + (i + 1), - method: handler[i] - }); - } - - options.handler = handler[handler.length - 1]; - } - - const skipValidation = options.payload && options.payload.parse === false; - - if ((operation.parameters || operation.requestBody) && !skipValidation) { - const allowUnknownProperties = xoptions.validate && xoptions.validate.options && xoptions.validate.options.allowUnknown === true; - const v = validator.makeAll(operation.parameters, operation.requestBody, operation.consumes || api.consumes, api.openapi, allowUnknownProperties); - options.validate = v.validate; - options.ext = { - onPreAuth: { method: v.routeExt } - }; - } - - if (outputvalidation && operation.responses) { - options.response = {}; - options.response.status = validator.makeResponseValidator(operation.responses, api.openapi); - } - - if (operation.security === undefined && api.security) { - operation.security = api.security; - } - - if (operation.security && operation.security.length) { - for (const secdef of operation.security) { - const securitySchemes = Object.keys(secdef); - if (!securitySchemes.length) { - options.auth = options.auth || { access: {}, mode: 'optional' }; - options.auth.mode = 'optional'; - } - - for (const securityDefinitionName of securitySchemes) { - let securityDefinition; - if (api.swagger) { - securityDefinition = api.securityDefinitions[securityDefinitionName]; - } - - if (api.openapi) { - securityDefinition = api.components.securitySchemes[securityDefinitionName]; - } - - Hoek.assert(securityDefinition, 'Security scheme not defined.'); - - options.auth = options.auth || { access: {}, mode: 'required' }; - options.auth.access.scope = options.auth.access.scope || []; - options.auth.access.scope.push(...secdef[securityDefinitionName]); - options.auth.strategies = options.auth.strategies || []; - options.auth.strategies.push(securityDefinitionName); - } - } - - if (options.auth.access.scope.length === 0) { - options.auth.access.scope = false; - } - } + options.auth = options.auth || { access: {}, mode: "required" }; + options.auth.access.scope = options.auth.access.scope || []; + options.auth.access.scope.push(...secdef[securityDefinitionName]); + options.auth.strategies = options.auth.strategies || []; + options.auth.strategies.push(securityDefinitionName); + } + } - routes.push({ - method, - path: api.basePath + path, - options, - vhost - }); + if (options.auth.access.scope.length === 0) { + options.auth.access.scope = false; } + } + + routes.push({ + method, + path: api.basePath + path, + options, + vhost, + }); } + } - return routes; + return routes; }; - -module.exports = { create }; diff --git a/lib/utils.js b/lib/utils.js index 65535fc..46391bb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,86 +1,101 @@ -'use strict'; - -const utils = { - - verbs: [ - 'get', - 'post', - 'put', - 'delete', - 'head', - 'options', - 'trace', - 'connect', - 'patch' - ], - - canCarry: function (method) { - switch (method) { - case 'post': - case 'put': - case 'patch': - case 'delete': - return true; - default: - return false; - } - }, - - isHttpMethod: function (method) { - return (typeof method === 'string') && !!~utils.verbs.indexOf(method.toLowerCase()); - }, - - endsWith: function (haystack, needle) { - if (!haystack || !needle) { - return false; - } - - if (needle.length === 1) { - return haystack[haystack.length - 1] === needle; - } - - return haystack.slice(haystack.length - needle.length) === needle; - }, - - prefix: function (str, pre) { - str = str || ''; - if (str.indexOf(pre) === 0) { - return str; - } - - str = pre + str; - return str; - }, - - unprefix: function (str, pre) { - str = str || ''; - if (str.indexOf(pre) === 0) { - str = str.substr(pre.length); - return str; - } - - return str; - }, - - suffix: function (str, suff) { - str = str || ''; - if (this.endsWith(str, suff)) { - return str; - } - - str = str + suff; - return str; - }, - - unsuffix: function (str, suff) { - str = str || ''; - if (this.endsWith(str, suff)) { - str = str.substr(0, str.length - suff.length); - return str; - } - - return str; +export const utils = { + verbs: [ + "get", + "post", + "put", + "delete", + "head", + "options", + "trace", + "connect", + "patch", + ], + + canCarry: function (method) { + switch (method) { + case "post": + case "put": + case "patch": + case "delete": + return true; + default: + return false; } + }, + + isHttpMethod: function (method) { + return (typeof method === "string") && + !!~utils.verbs.indexOf(method.toLowerCase()); + }, + + endsWith: function (haystack, needle) { + if (!haystack || !needle) { + return false; + } + + if (needle.length === 1) { + return haystack[haystack.length - 1] === needle; + } + + return haystack.slice(haystack.length - needle.length) === needle; + }, + + prefix: function (str, pre) { + str = str || ""; + if (str.indexOf(pre) === 0) { + return str; + } + + str = pre + str; + return str; + }, + + unprefix: function (str, pre) { + str = str || ""; + if (str.indexOf(pre) === 0) { + str = str.substr(pre.length); + return str; + } + + return str; + }, + + suffix: function (str, suff) { + str = str || ""; + if (this.endsWith(str, suff)) { + return str; + } + + str = str + suff; + return str; + }, + + unsuffix: function (str, suff) { + str = str || ""; + if (this.endsWith(str, suff)) { + str = str.substr(0, str.length - suff.length); + return str; + } + + return str; + }, + + mergeDefault: (obj) => { + const merged_obj = {}; + if (obj.default) { + for (const [k, v] of Object.entries(obj.default)) { + merged_obj[k] = v; + } + } + + for (const [k, v] of Object.entries(obj)) { + if (k !== "default") { + merged_obj[k] = v; + } + } + + return merged_obj; + }, }; -module.exports = utils; +export default utils; diff --git a/lib/validators.js b/lib/validators.js index 8a44f6b..a946364 100644 --- a/lib/validators.js +++ b/lib/validators.js @@ -1,336 +1,368 @@ -'use strict'; - -const Enjoi = require('enjoi'); -const Joi = require('joi'); +import Enjoi from "enjoi"; +import Joi from "joi"; const extensions = [ - { type: 'int64', base: Joi.string().regex(/^\d+$/) }, - { type: 'byte', base: Joi.string().base64() }, - { type: 'date-time', base: Joi.date().iso() }, - { - type: 'file', - base: Joi.object({ - value: Joi.binary().required(true), - consumes: Joi.array().items( - Joi.string().regex(/multipart\/form-data|application\/x-www-form-urlencoded/) - ).required(true), - in: Joi.string().regex(/formData/).required(true) - }) - } + { type: "int64", base: Joi.string().regex(/^\d+$/) }, + { type: "byte", base: Joi.string().base64() }, + { type: "date-time", base: Joi.date().iso() }, + { + type: "file", + base: Joi.object({ + value: Joi.binary().required(true), + consumes: Joi.array().items( + Joi.string().regex( + /multipart\/form-data|application\/x-www-form-urlencoded/, + ), + ).required(true), + in: Joi.string().regex(/formData/).required(true), + }), + }, ]; const refineType = function (type, format) { - if (type === 'integer') { - type = 'number'; - } - - switch (format) { - case 'int64': - case 'byte': - case 'binary': - case 'date': - case 'date-time': - return format; - default: - return type; - } + if (type === "integer") { + type = "number"; + } + + switch (format) { + case "int64": + case "byte": + case "binary": + case "date": + case "date-time": + return format; + default: + return type; + } }; const refineSchema = function (joiSchema, jsonSchema) { - if (jsonSchema.nullable) { - return joiSchema.allow(null); - } + if (jsonSchema.nullable) { + return joiSchema.allow(null); + } - return joiSchema; + return joiSchema; }; const enjoi = Enjoi.defaults({ extensions, refineType, refineSchema }); -const create = function (options = {}) { - const makeValidator = function (parameter, consumes, openapi, allowUnknownProperties = false) { - const coerce = coercion(parameter, consumes); - - let schema; +export const create = function (options = {}) { + const makeValidator = function ( + parameter, + consumes, + openapi, + allowUnknownProperties = false, + ) { + const coerce = coercion(parameter, consumes); + + let schema; + + if ( + (parameter.in === "body" || parameter.in === "formData") && + parameter.schema + ) { + schema = enjoi.schema(parameter.schema); + if (schema.type === "object") { + schema = schema.unknown(allowUnknownProperties); + } + } else { + let template = { + required: parameter.required, + enum: parameter.enum, + type: parameter.type, + schema: undefined, + items: parameter.items, + properties: parameter.properties, + pattern: parameter.pattern, + format: parameter.format, + allowEmptyValue: parameter.allowEmptyValue, + collectionFormat: parameter.collectionFormat, + default: parameter.default, + maximum: parameter.maximum, + minimum: parameter.minimum, + maxLength: parameter.maxLength, + minLength: parameter.minLength, + maxItems: parameter.maxItems, + minItems: parameter.minItems, + uniqueItems: parameter.uniqueItems, + multipleOf: parameter.multipleOf, + }; + + if (openapi) { + template = { ...template, ...parameter.schema }; + } else { + template.schema = parameter.schema; + } + + schema = enjoi.schema(template); + } - if ((parameter.in === 'body' || parameter.in === 'formData') && parameter.schema) { - schema = enjoi.schema(parameter.schema); - if (schema.type === 'object') { - schema = schema.unknown(allowUnknownProperties); - } - } - else { - let template = { - required: parameter.required, - enum: parameter.enum, - type: parameter.type, - schema: undefined, - items: parameter.items, - properties: parameter.properties, - pattern: parameter.pattern, - format: parameter.format, - allowEmptyValue: parameter.allowEmptyValue, - collectionFormat: parameter.collectionFormat, - default: parameter.default, - maximum: parameter.maximum, - minimum: parameter.minimum, - maxLength: parameter.maxLength, - minLength: parameter.minLength, - maxItems: parameter.maxItems, - minItems: parameter.minItems, - uniqueItems: parameter.uniqueItems, - multipleOf: parameter.multipleOf - }; - - if (openapi) { - template = { ...template, ...parameter.schema }; - } - else { - template.schema = parameter.schema; - } + if (parameter.type === "array") { + schema = schema.single(true); + } - schema = enjoi.schema(template); - } + if (parameter.required) { + schema = schema.required(); + } - if (parameter.type === 'array') { - schema = schema.single(true); - } + if (parameter.in !== "body" && parameter.allowEmptyValue) { + schema = schema.allow("").optional(); + } - if (parameter.required) { - schema = schema.required(); + return { + parameter, + schema, + routeExt: function (request, h) { + const p = parameter.in === "query" ? "query" : "params"; + if (request[p][parameter.name] !== undefined) { + request[p][parameter.name] = coerce && request[p][parameter.name] && + coerce(request[p][parameter.name]); } - if (parameter.in !== 'body' && parameter.allowEmptyValue) { - schema = schema.allow('').optional(); - } + return h.continue; + }, + validate: function (value) { + const data = coerce && value && coerce(value); + const result = schema.validate(data); - return { - parameter, - schema, - routeExt: function (request, h) { - const p = parameter.in === 'query' ? 'query' : 'params'; - if (request[p][parameter.name] !== undefined) { - request[p][parameter.name] = coerce && request[p][parameter.name] && coerce(request[p][parameter.name]); - } + if (result && result.error) { + result.error.message = result.error.message.replace( + "value", + parameter.name, + ); - return h.continue; - }, - validate: function (value) { - const data = coerce && value && coerce(value); - const result = schema.validate(data); + result.error.details.forEach((detail) => { + detail.message = detail.message.replace("value", parameter.name); + detail.path = [parameter.name]; + }); - if (result && result.error) { + throw result.error; + } - result.error.message = result.error.message.replace('value', parameter.name); + return result.value; + }, + }; + }; + + const makeResponseValidator = function (responses, openapi) { + const schemas = {}; + + for (const [code, response] of Object.entries(responses)) { + if (!isNaN(code)) { + let schemaDesc; + if (openapi && response.content) { + for (const mediaType of Object.keys(response.content)) { + // Applying first available schema to all media types + if (response.content[mediaType].schema) { + schemaDesc = response.content[mediaType].schema; + break; + } + } + } else { + schemaDesc = response.schema; + } - result.error.details.forEach((detail) => { - detail.message = detail.message.replace('value', parameter.name); - detail.path = [parameter.name]; - }); + if (schemaDesc) { + const schema = enjoi.schema(schemaDesc); + if (schemaDesc === "array") { + schema.single(true); + } - throw result.error; - } + schemas[code] = schema; + } + } + } - return result.value; - } - }; + return schemas; + }; + + const makeAll = function ( + parameters = [], + requestBody, + consumes, + openapi, + allowUnknownProperties = false, + ) { + const routeExt = []; + const validate = {}; + const formValidators = {}; + let headers = {}; + + const formValidator = function (value) { + const result = this.validate(value); + + if (result.error) { + throw result.error; + } + + return this.parameter.type === "file" ? result.value : result; }; - const makeResponseValidator = function (responses, openapi) { - const schemas = {}; - - for (const [code, response] of Object.entries(responses)) { - if (!isNaN(code)) { - let schemaDesc; - if (openapi && response.content) { - for (const mediaType of Object.keys(response.content)) { - // Applying first available schema to all media types - if (response.content[mediaType].schema) { - schemaDesc = response.content[mediaType].schema; - break; - } - } - } - else { - schemaDesc = response.schema; - } - - if (schemaDesc) { - const schema = enjoi.schema(schemaDesc); - if (schemaDesc === 'array') { - schema.single(true); - } - - schemas[code] = schema; - } - } + if (openapi && requestBody && requestBody.content) { + consumes = Object.keys(requestBody.content); + for (const mediaType of consumes) { + // Applying first available schema to all media types + if (requestBody.content[mediaType].schema) { + const parameter = { + in: "body", + schema: requestBody.content[mediaType].schema, + name: "body", + }; + const validator = makeValidator( + parameter, + consumes, + openapi, + allowUnknownProperties, + ); + validate.payload = validator.validate; + break; } + } + } - return schemas; - }; + for (const parameter of parameters) { + const validator = makeValidator( + parameter, + consumes, + openapi, + allowUnknownProperties, + ); + + switch (validator.parameter.in) { + case "header": + headers = headers || {}; + headers[validator.parameter.name] = validator.schema; + break; + case "query": + validate.query = validate.query || {}; + validate.query[validator.parameter.name] = validator.schema; + routeExt.push(validator.routeExt); + break; + case "path": + validate.params = validate.params || {}; + validate + .params[validator.parameter.name.replace(/(\*[0-9]*|\?)$/, "")] = + validator.schema; + routeExt.push(validator.routeExt); + break; + case "body": + validate.payload = validator.validate; + break; + case "formData": + formValidators[validator.parameter.name] = formValidator.bind( + validator, + ); + break; + default: + break; + } - const makeAll = function (parameters = [], requestBody, consumes, openapi, allowUnknownProperties = false) { - const routeExt = []; - const validate = {}; - const formValidators = {}; - let headers = {}; + if (headers && Object.keys(headers).length > 0) { + validate.headers = Joi.object(headers).options({ allowUnknown: true }); + } - const formValidator = function (value) { - const result = this.validate(value); + if (!validate.payload && Object.keys(formValidators).length > 0) { + validate.payload = async function (value) { + const results = {}; - if (result.error) { - throw result.error; - } + for (const [param, data] of Object.entries(value)) { + results[param] = await formValidators[param](data); + } - return this.parameter.type === 'file' ? result.value : result; + return results; }; + } + } - if (openapi && requestBody && requestBody.content) { - consumes = Object.keys(requestBody.content); - for (const mediaType of consumes) { - // Applying first available schema to all media types - if (requestBody.content[mediaType].schema) { - const parameter = { in: 'body', schema: requestBody.content[mediaType].schema, name: 'body' }; - const validator = makeValidator(parameter, consumes, openapi, allowUnknownProperties); - validate.payload = validator.validate; - break; - } - } - } + for (const [key, value] of Object.entries(validate)) { + if (typeof value === "object" && !Joi.isSchema(value)) { + validate[key] = Joi.object(value); + } + } - for (const parameter of parameters) { - const validator = makeValidator(parameter, consumes, openapi, allowUnknownProperties); - - switch (validator.parameter.in) { - case 'header': - headers = headers || {}; - headers[validator.parameter.name] = validator.schema; - break; - case 'query': - validate.query = validate.query || {}; - validate.query[validator.parameter.name] = validator.schema; - routeExt.push(validator.routeExt); - break; - case 'path': - validate.params = validate.params || {}; - validate.params[validator.parameter.name.replace(/(\*[0-9]*|\?)$/, '')] = validator.schema; - routeExt.push(validator.routeExt); - break; - case 'body': - validate.payload = validator.validate; - break; - case 'formData': - formValidators[validator.parameter.name] = formValidator.bind(validator); - break; - default: - break; - } + return { + validate, + routeExt, + }; + }; - if (headers && Object.keys(headers).length > 0) { - validate.headers = Joi.object(headers).options({ allowUnknown: true }); - } + return { + makeAll, + makeValidator, + makeResponseValidator, + }; +}; - if (!validate.payload && Object.keys(formValidators).length > 0) { - validate.payload = async function (value) { - const results = {}; +const pathsep = function (format) { + switch (format) { + case "csv": + return ","; + case "ssv": + return " "; + case "tsv": + return "\t"; + case "pipes": + return "|"; + case "multi": + return "&"; + } +}; - for (const [param, data] of Object.entries(value)) { - results[param] = await formValidators[param](data); - } +const coercion = function (parameter, consumes) { + let fn; - return results; - }; - } + switch (parameter.type) { + case "array": + fn = function (data) { + if (Array.isArray(data)) { + return data; } - for (const [key, value] of Object.entries(validate)) { - if (typeof value === 'object' && !Joi.isSchema(value)) { - validate[key] = Joi.object(value); - } + const sep = pathsep(parameter.collectionFormat || "csv"); + + return data.split(sep); + }; + + break; + case "integer": + case "number": + fn = function (data) { + if (parameter.format === "int64") { + return data; } + return Number(data); + }; + + break; + case "string": + //TODO: handle date, date-time, binary, byte formats. + fn = String; + break; + case "boolean": + fn = function (data) { + return (data === "true") || (data === "1") || (data === true); + }; + + break; + case "file": { + fn = function (data) { return { - validate, - routeExt + value: data, + consumes, + in: parameter.in, }; - }; - - return { - makeAll, - makeValidator, - makeResponseValidator - }; -}; + }; -const pathsep = function (format) { - switch (format) { - case 'csv': - return ','; - case 'ssv': - return ' '; - case 'tsv': - return '\t'; - case 'pipes': - return '|'; - case 'multi': - return '&'; + break; } -}; + } -const coercion = function (parameter, consumes) { - let fn; - - switch (parameter.type) { - case 'array': - fn = function (data) { - if (Array.isArray(data)) { - return data; - } - - const sep = pathsep(parameter.collectionFormat || 'csv'); - - return data.split(sep); - }; - - break; - case 'integer': - case 'number': - fn = function (data) { - if (parameter.format === 'int64') { - return data; - } - - return Number(data); - }; - - break; - case 'string': - //TODO: handle date, date-time, binary, byte formats. - fn = String; - break; - case 'boolean': - fn = function (data) { - return (data === 'true') || (data === '1') || (data === true); - }; - - break; - case 'file': { - fn = function (data) { - return { - value: data, - consumes, - in: parameter.in - }; - }; - - break; - } - } - - if (!fn && parameter.schema) { - fn = (data) => data; - } + if (!fn && parameter.schema) { + fn = (data) => data; + } - return fn; + return fn; }; -module.exports = { create }; +export default { create }; diff --git a/package.json b/package.json index edd9c11..bbad8da 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { - "name": "hapi-openapi", + "name": "hapi-openapi-es6", "description": "Design-driven apis with OpenAPI (formerly Swagger) 2.0 and hapi.", - "version": "3.0.0", + "version": "3.0.5", "author": "Trevor Livingston ", + "type": "module", "repository": { "type": "git", - "url": "git://github.com/krakenjs/hapi-openapi.git" + "url": "git://github.com/jceb/hapi-openapi-es6.git" }, - "bugs": "http://github.com/krakenjs/hapi-openapi/issues", + "bugs": "http://github.com/jceb/hapi-openapi-es6/issues", "publishConfig": { "registry": "https://registry.npmjs.org" }, @@ -16,31 +17,29 @@ }, "dependencies": { "@hapi/hoek": "^9.0.4", - "dot-prop": "^5.2.0", + "dot-prop": "^6.0.1", "enjoi": "^9.0.0", "joi": "^17.1.0", - "js-yaml": "^3.11.0", - "merge-object-files": "^2.0.0", - "swagger-parser": "^9.0.1" + "js-yaml": "^4.1.0", + "merge-object-files-es6": "^1.0.0", + "openapi-types": "^10.0.0", + "swagger-parser": "^10.0.3" }, "devDependencies": { "@hapi/boom": "^9.1.0", - "@hapi/eslint-config-hapi": "^13.0.2", - "@hapi/eslint-plugin-hapi": "^4.3.5", + "@hapi/eslint-plugin": "^5.1.0", "@hapi/hapi": "^20.0.3", - "eslint": "^6.8.0", - "eslint-config-hapi": "^12.0.0", - "eslint-plugin-hapi": "^4.1.0", - "nyc": "^15.0.0", + "eslint": "^7.32.0", + "nyc": "^15.1.0", "tape": "^5.0.0" }, "scripts": { - "test": "tape test/*.js", - "cover": "nyc npm test", + "test": "NODE_OPTIONS='--trace-warnings --stack-trace-limit=100 --experimental-json-modules --experimental-import-meta-resolve' npx tape test/*.js", + "cover": "NODE_OPTIONS='--trace-warnings --stack-trace-limit=100 --experimental-json-modules --experimental-import-meta-resolve' nyc npm test", "lint": "eslint lib" }, "license": "Apache-2.0", - "main": "./lib/index", + "main": "./lib/index.js", "keywords": [ "openapi", "swagger", diff --git a/test/fixtures/defs/form.json b/test/fixtures/defs/form.json index 42b0298..f541c06 100644 --- a/test/fixtures/defs/form.json +++ b/test/fixtures/defs/form.json @@ -1,58 +1,58 @@ { - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Upload Test", - "description": "Form data validation" - }, - "host": "example.com", - "basePath": "/v1/forms", - "schemes": [ - "http" - ], - "consumes": [ - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json" - ], - "paths": { - "/upload": { - "post": { - "consumes": [ - "application/x-www-form-urlencoded" - ], - "operationId": "uploadFile", - "description": "uploads a file", - "parameters": [ - { - "name": "upload", - "in": "formData", - "type": "file", - "required": true - }, - { - "name": "name", - "in": "formData", - "type": "string", - "required": false - } - ], - "responses": { - "200": { - "description": "default response", - "schema": { - "type": "object", - "properties": { - "upload": { - "type": "string", - "format": "binary" - } - } - } - } + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Upload Test", + "description": "Form data validation" + }, + "host": "example.com", + "basePath": "/v1/forms", + "schemes": [ + "http" + ], + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/json" + ], + "paths": { + "/upload": { + "post": { + "consumes": [ + "application/x-www-form-urlencoded" + ], + "operationId": "uploadFile", + "description": "uploads a file", + "parameters": [ + { + "name": "upload", + "in": "formData", + "type": "file", + "required": true + }, + { + "name": "name", + "in": "formData", + "type": "string", + "required": false + } + ], + "responses": { + "200": { + "description": "default response", + "schema": { + "type": "object", + "properties": { + "upload": { + "type": "string", + "format": "binary" } + } } + } } + } } -} \ No newline at end of file + } +} diff --git a/test/fixtures/defs/form_xoptions.json b/test/fixtures/defs/form_xoptions.json index dc58e0b..6a093b7 100644 --- a/test/fixtures/defs/form_xoptions.json +++ b/test/fixtures/defs/form_xoptions.json @@ -1,61 +1,61 @@ { - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Upload Test", - "description": "Form data validation" - }, - "host": "example.com", - "basePath": "/v1/forms", - "schemes": [ - "http" - ], - "consumes": [ - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json" - ], - "paths": { - "/upload": { - "post": { - "consumes": [ - "application/x-www-form-urlencoded" - ], - "operationId": "uploadFile", - "description": "uploads a file", - "parameters": [ - { - "name": "upload", - "in": "formData", - "type": "file", - "required": true - }, - { - "name": "name", - "in": "formData", - "type": "string", - "required": false - } - ], - "x-hapi-options": { - "isInternal": true - }, - "responses": { - "200": { - "description": "default response", - "schema": { - "type": "object", - "properties": { - "upload": { - "type": "string", - "format": "binary" - } - } - } - } + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Upload Test", + "description": "Form data validation" + }, + "host": "example.com", + "basePath": "/v1/forms", + "schemes": [ + "http" + ], + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/json" + ], + "paths": { + "/upload": { + "post": { + "consumes": [ + "application/x-www-form-urlencoded" + ], + "operationId": "uploadFile", + "description": "uploads a file", + "parameters": [ + { + "name": "upload", + "in": "formData", + "type": "file", + "required": true + }, + { + "name": "name", + "in": "formData", + "type": "string", + "required": false + } + ], + "x-hapi-options": { + "isInternal": true + }, + "responses": { + "200": { + "description": "default response", + "schema": { + "type": "object", + "properties": { + "upload": { + "type": "string", + "format": "binary" } + } } + } } + } } -} \ No newline at end of file + } +} diff --git a/test/fixtures/defs/pets.json b/test/fixtures/defs/pets.json index efb617e..616f3f4 100644 --- a/test/fixtures/defs/pets.json +++ b/test/fixtures/defs/pets.json @@ -1,242 +1,242 @@ { - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Swagger Petstore (Simple)", - "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - "termsOfService": "http://helloreverb.com/terms/", - "contact": { - "name": "Swagger API team", - "email": "foo@example.com", - "url": "http://swagger.io" - }, - "license": { - "name": "MIT", - "url": "http://opensource.org/licenses/MIT" - }, - "x-meta" : "test" + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore (Simple)", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Swagger API team", + "email": "foo@example.com", + "url": "http://swagger.io" }, - "host": "petstore.swagger.io", - "basePath": "/v1/petstore", - "schemes": [ - "http" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": { - "/path.with.period": { - "get": { - "description": "Test a path with periods", - "parameters": [], - "responses": { - "204": { - "description": "empty response" - } - } - } - }, - "/pets": { - "get": { - "description": "Returns all pets from the system that the user has access to", - "operationId": "findPets", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "tags", - "in": "query", - "description": "tags to filter by", - "required": false, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi", - "x-meta": "test" - }, - { - "name": "limit", - "in": "query", - "description": "maximum number of results to return", - "required": false, - "type": "integer", - "format": "int32" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/pet" - } - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/MIT" + }, + "x-meta": "test" + }, + "host": "petstore.swagger.io", + "basePath": "/v1/petstore", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/path.with.period": { + "get": { + "description": "Test a path with periods", + "parameters": [], + "responses": { + "204": { + "description": "empty response" + } + } + } + }, + "/pets": { + "get": { + "description": "Returns all pets from the system that the user has access to", + "operationId": "findPets", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "tags to filter by", + "required": false, + "type": "array", + "items": { + "type": "string" }, - "post": { - "description": "Creates a new pet in the store. Duplicates are allowed", - "operationId": "addPet", - "produces": [ - "application/json" - ], - "parameters": [ - { - "name": "pet", - "in": "body", - "description": "Pet to add to the store", - "required": true, - "schema": { - "$ref": "#/definitions/newPet" - } - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "collectionFormat": "multi", + "x-meta": "test" + }, + { + "name": "limit", + "in": "query", + "description": "maximum number of results to return", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/pet" + } } - }, - "/pets/{id}": { - "get": { - "description": "Returns a user based on a single ID, if the user does not have access to the pet", - "operationId": "findPetById", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to fetch", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } - }, - "delete": { - "description": "deletes a single pet based on the ID supplied", - "operationId": "deletePet", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to delete", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "204": { - "description": "pet deleted" - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } } + }, + "post": { + "description": "Creates a new pet in the store. Duplicates are allowed", + "operationId": "addPet", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "pet", + "in": "body", + "description": "Pet to add to the store", + "required": true, + "schema": { + "$ref": "#/definitions/newPet" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } }, - "definitions": { - "pet": { - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + "/pets/{id}": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "findPetById", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to fetch", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" } - }, - "newPet": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } - }, - "errorModel": { - "type": "object", - "required": [ - "code", - "message" - ], - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } + } + } + }, + "delete": { + "description": "deletes a single pet based on the ID supplied", + "operationId": "deletePet", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "204": { + "description": "pet deleted" + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } + } + } + } + }, + "definitions": { + "pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "newPet": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "errorModel": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" } + } } -} \ No newline at end of file + } +} diff --git a/test/fixtures/defs/pets_authed.json b/test/fixtures/defs/pets_authed.json index cd4e7aa..bbc6c5a 100644 --- a/test/fixtures/defs/pets_authed.json +++ b/test/fixtures/defs/pets_authed.json @@ -1,273 +1,273 @@ { - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Swagger Petstore (Simple)", - "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - "termsOfService": "http://helloreverb.com/terms/", - "contact": { - "name": "Swagger API team", - "email": "foo@example.com", - "url": "http://swagger.io" - }, - "license": { - "name": "MIT", - "url": "http://opensource.org/licenses/MIT" - } + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore (Simple)", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Swagger API team", + "email": "foo@example.com", + "url": "http://swagger.io" }, - "host": "petstore.swagger.io", - "basePath": "/v1/petstore", - "schemes": [ - "http" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": { - "/pets": { - "get": { - "security": [ - { - "api_key": [ - "api1:read" - ] - }, - { - "api_key2": [ - "api2:read" - ] - }, - {} - ], - "description": "Returns all pets from the system that the user has access to", - "operationId": "findPets", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "tags", - "in": "query", - "description": "tags to filter by", - "required": false, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - }, - { - "name": "limit", - "in": "query", - "description": "maximum number of results to return", - "required": false, - "type": "integer", - "format": "int32" - }, - { - "name": "custom-header", - "in": "header", - "description": "A custom header", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/pet" - } - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/MIT" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v1/petstore", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/pets": { + "get": { + "security": [ + { + "api_key": [ + "api1:read" + ] + }, + { + "api_key2": [ + "api2:read" + ] + }, + {} + ], + "description": "Returns all pets from the system that the user has access to", + "operationId": "findPets", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "tags to filter by", + "required": false, + "type": "array", + "items": { + "type": "string" }, - "post": { - "security": [ - { - "api_key": [ - "api1:read" - ] - }, - { - "api_key2": [ - "api2:read" - ] - } - ], - "description": "Creates a new pet in the store. Duplicates are allowed", - "operationId": "addPet", - "produces": [ - "application/json" - ], - "parameters": [ - { - "name": "pet", - "in": "body", - "description": "Pet to add to the store", - "required": true, - "schema": { - "$ref": "#/definitions/newPet" - } - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "collectionFormat": "multi" + }, + { + "name": "limit", + "in": "query", + "description": "maximum number of results to return", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "custom-header", + "in": "header", + "description": "A custom header", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/pet" + } } - }, - "/pets/{id}": { - "get": { - "description": "Returns a user based on a single ID, if the user does not have access to the pet", - "operationId": "findPetById", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to fetch", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } - }, - "delete": { - "description": "deletes a single pet based on the ID supplied", - "operationId": "deletePet", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to delete", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "204": { - "description": "pet deleted" - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } } + }, + "post": { + "security": [ + { + "api_key": [ + "api1:read" + ] + }, + { + "api_key2": [ + "api2:read" + ] + } + ], + "description": "Creates a new pet in the store. Duplicates are allowed", + "operationId": "addPet", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "pet", + "in": "body", + "description": "Pet to add to the store", + "required": true, + "schema": { + "$ref": "#/definitions/newPet" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } }, - "definitions": { - "pet": { - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + "/pets/{id}": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "findPetById", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to fetch", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" } - }, - "newPet": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } - }, - "errorModel": { - "type": "object", - "required": [ - "code", - "message" - ], - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } + } + } + }, + "delete": { + "description": "deletes a single pet based on the ID supplied", + "operationId": "deletePet", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "204": { + "description": "pet deleted" + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } + } + } + } + }, + "definitions": { + "pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" } + } }, - "securityDefinitions": { - "api_key": { - "type": "apiKey", - "name": "authorization", - "in": "header" + "newPet": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" }, - "api_key2": { - "type": "apiKey", - "name": "authorization", - "in": "header" + "tag": { + "type": "string" } + } + }, + "errorModel": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "authorization", + "in": "header" + }, + "api_key2": { + "type": "apiKey", + "name": "authorization", + "in": "header" } + } } diff --git a/test/fixtures/defs/pets_root_authed.json b/test/fixtures/defs/pets_root_authed.json index 1329819..f3df3d3 100644 --- a/test/fixtures/defs/pets_root_authed.json +++ b/test/fixtures/defs/pets_root_authed.json @@ -1,115 +1,114 @@ { - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Swagger Petstore (Simple)", - "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - "termsOfService": "http://helloreverb.com/terms/", - "contact": { - "name": "Swagger API team", - "email": "foo@example.com", - "url": "http://swagger.io" - }, - "license": { - "name": "MIT", - "url": "http://opensource.org/licenses/MIT" - } + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore (Simple)", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Swagger API team", + "email": "foo@example.com", + "url": "http://swagger.io" }, - "host": "petstore.swagger.io", - "basePath": "/v1/petstore", - "schemes": [ - "http" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "security": [ - { - "api_key": [ - "api1:read" - ] - }, - { - "api_key2": [ - "api2:read" - ] - } - ], - "paths": { - "/pets": { - "get": { - "parameters": [ - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/pet" - } - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } - } - } + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/MIT" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v1/petstore", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "security": [ + { + "api_key": [ + "api1:read" + ] }, - "definitions": { - "pet": { - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + { + "api_key2": [ + "api2:read" + ] + } + ], + "paths": { + "/pets": { + "get": { + "parameters": [], + "responses": { + "200": { + "description": "pet response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/pet" + } } - }, - "errorModel": { - "type": "object", - "required": [ - "code", - "message" - ], - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } } + } + } + }, + "definitions": { + "pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } }, - "securityDefinitions": { - "api_key": { - "type": "apiKey", - "name": "authorization", - "in": "header" + "errorModel": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" }, - "api_key2": { - "type": "apiKey", - "name": "authorization", - "in": "header" + "message": { + "type": "string" } + } + } + }, + "securityDefinitions": { + "api_key": { + "type": "apiKey", + "name": "authorization", + "in": "header" + }, + "api_key2": { + "type": "apiKey", + "name": "authorization", + "in": "header" } + } } diff --git a/test/fixtures/defs/pets_xauthed.json b/test/fixtures/defs/pets_xauthed.json index f018d96..71a3046 100644 --- a/test/fixtures/defs/pets_xauthed.json +++ b/test/fixtures/defs/pets_xauthed.json @@ -1,254 +1,254 @@ { - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Swagger Petstore (Simple)", - "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - "termsOfService": "http://helloreverb.com/terms/", - "contact": { - "name": "Swagger API team", - "email": "foo@example.com", - "url": "http://swagger.io" - }, - "license": { - "name": "MIT", - "url": "http://opensource.org/licenses/MIT" - } + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore (Simple)", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Swagger API team", + "email": "foo@example.com", + "url": "http://swagger.io" }, - "host": "petstore.swagger.io", - "basePath": "/v1/petstore", - "schemes": [ - "http" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": { - "/pets": { - "get": { - "security": [ - { - "api_key": [ - "read" - ] - } - ], - "description": "Returns all pets from the system that the user has access to", - "operationId": "findPets", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "tags", - "in": "query", - "description": "tags to filter by", - "required": false, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - }, - { - "name": "limit", - "in": "query", - "description": "maximum number of results to return", - "required": false, - "type": "integer", - "format": "int32" - }, - { - "name": "custom-header", - "in": "header", - "description": "A custom header", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/pet" - } - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/MIT" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v1/petstore", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/pets": { + "get": { + "security": [ + { + "api_key": [ + "read" + ] + } + ], + "description": "Returns all pets from the system that the user has access to", + "operationId": "findPets", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "tags to filter by", + "required": false, + "type": "array", + "items": { + "type": "string" }, - "post": { - "description": "Creates a new pet in the store. Duplicates are allowed", - "operationId": "addPet", - "produces": [ - "application/json" - ], - "parameters": [ - { - "name": "pet", - "in": "body", - "description": "Pet to add to the store", - "required": true, - "schema": { - "$ref": "#/definitions/newPet" - } - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "collectionFormat": "multi" + }, + { + "name": "limit", + "in": "query", + "description": "maximum number of results to return", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "custom-header", + "in": "header", + "description": "A custom header", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/pet" + } } - }, - "/pets/{id}": { - "get": { - "description": "Returns a user based on a single ID, if the user does not have access to the pet", - "operationId": "findPetById", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to fetch", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } - }, - "delete": { - "description": "deletes a single pet based on the ID supplied", - "operationId": "deletePet", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to delete", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "204": { - "description": "pet deleted" - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } } + }, + "post": { + "description": "Creates a new pet in the store. Duplicates are allowed", + "operationId": "addPet", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "pet", + "in": "body", + "description": "Pet to add to the store", + "required": true, + "schema": { + "$ref": "#/definitions/newPet" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + } }, - "definitions": { - "pet": { - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + "/pets/{id}": { + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "findPetById", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to fetch", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" } - }, - "newPet": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } - }, - "errorModel": { - "type": "object", - "required": [ - "code", - "message" - ], - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } + } + } + }, + "delete": { + "description": "deletes a single pet based on the ID supplied", + "operationId": "deletePet", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "204": { + "description": "pet deleted" + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } + } + } + } + }, + "definitions": { + "pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" } + } }, - "x-hapi-auth-schemes": { - "apiKey": "../lib/xauth-scheme.js" + "newPet": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } }, - "securityDefinitions": { - "api_key": { - "x-hapi-auth-strategy": "../lib/xauth-strategy.js", - "type": "apiKey", - "name": "authorization", - "in": "header" + "errorModel": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" } + } + } + }, + "x-hapi-auth-schemes": { + "apiKey": "../lib/xauth-scheme.js" + }, + "securityDefinitions": { + "api_key": { + "x-hapi-auth-strategy": "../lib/xauth-strategy.js", + "type": "apiKey", + "name": "authorization", + "in": "header" } -} \ No newline at end of file + } +} diff --git a/test/fixtures/defs/pets_xhandlers.json b/test/fixtures/defs/pets_xhandlers.json index 8db6c2a..6621941 100644 --- a/test/fixtures/defs/pets_xhandlers.json +++ b/test/fixtures/defs/pets_xhandlers.json @@ -1,231 +1,231 @@ { - "swagger": "2.0", - "info": { - "version": "1.0.0", - "title": "Swagger Petstore (Simple)", - "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", - "termsOfService": "http://helloreverb.com/terms/", - "contact": { - "name": "Swagger API team", - "email": "foo@example.com", - "url": "http://swagger.io" - }, - "license": { - "name": "MIT", - "url": "http://opensource.org/licenses/MIT" - } + "swagger": "2.0", + "info": { + "version": "1.0.0", + "title": "Swagger Petstore (Simple)", + "description": "A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification", + "termsOfService": "http://helloreverb.com/terms/", + "contact": { + "name": "Swagger API team", + "email": "foo@example.com", + "url": "http://swagger.io" }, - "host": "petstore.swagger.io", - "basePath": "/v1/petstore", - "schemes": [ - "http" - ], - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "paths": { - "/pets": { - "x-hapi-handler": "../handlers/pets.js", - "get": { - "description": "Returns all pets from the system that the user has access to", - "operationId": "findPets", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "tags", - "in": "query", - "description": "tags to filter by", - "required": false, - "type": "array", - "items": { - "type": "string" - }, - "collectionFormat": "multi" - }, - { - "name": "limit", - "in": "query", - "description": "maximum number of results to return", - "required": false, - "type": "integer", - "format": "int32" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/pet" - } - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "license": { + "name": "MIT", + "url": "http://opensource.org/licenses/MIT" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v1/petstore", + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/pets": { + "x-hapi-handler": "../handlers/pets.js", + "get": { + "description": "Returns all pets from the system that the user has access to", + "operationId": "findPets", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "tags to filter by", + "required": false, + "type": "array", + "items": { + "type": "string" }, - "post": { - "description": "Creates a new pet in the store. Duplicates are allowed", - "operationId": "addPet", - "produces": [ - "application/json" - ], - "parameters": [ - { - "name": "pet", - "in": "body", - "description": "Pet to add to the store", - "required": true, - "schema": { - "$ref": "#/definitions/newPet" - } - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + "collectionFormat": "multi" + }, + { + "name": "limit", + "in": "query", + "description": "maximum number of results to return", + "required": false, + "type": "integer", + "format": "int32" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/pet" + } } - }, - "/pets/{id}": { - "x-hapi-handler": "../handlers/pets/{id}.js", - "get": { - "description": "Returns a user based on a single ID, if the user does not have access to the pet", - "operationId": "findPetById", - "produces": [ - "application/json", - "application/xml", - "text/xml", - "text/html" - ], - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to fetch", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "200": { - "description": "pet response", - "schema": { - "$ref": "#/definitions/pet" - } - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } - }, - "delete": { - "description": "deletes a single pet based on the ID supplied", - "operationId": "deletePet", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of pet to delete", - "required": true, - "type": "integer", - "format": "int64" - } - ], - "responses": { - "204": { - "description": "pet deleted" - }, - "default": { - "description": "unexpected error", - "schema": { - "$ref": "#/definitions/errorModel" - } - } - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" + } + } + } + }, + "post": { + "description": "Creates a new pet in the store. Duplicates are allowed", + "operationId": "addPet", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "pet", + "in": "body", + "description": "Pet to add to the store", + "required": true, + "schema": { + "$ref": "#/definitions/newPet" + } + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" + } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } } + } }, - "definitions": { - "pet": { - "type": "object", - "required": [ - "id", - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + "/pets/{id}": { + "x-hapi-handler": "../handlers/pets/{id}.js", + "get": { + "description": "Returns a user based on a single ID, if the user does not have access to the pet", + "operationId": "findPetById", + "produces": [ + "application/json", + "application/xml", + "text/xml", + "text/html" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to fetch", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "pet response", + "schema": { + "$ref": "#/definitions/pet" } - }, - "newPet": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "tag": { - "type": "string" - } + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } - }, - "errorModel": { - "type": "object", - "required": [ - "code", - "message" - ], - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - } + } + } + }, + "delete": { + "description": "deletes a single pet based on the ID supplied", + "operationId": "deletePet", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of pet to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "204": { + "description": "pet deleted" + }, + "default": { + "description": "unexpected error", + "schema": { + "$ref": "#/definitions/errorModel" } + } + } + } + } + }, + "definitions": { + "pet": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "newPet": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "errorModel": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" } + } } -} \ No newline at end of file + } +} diff --git a/test/fixtures/handlers/path.with.period.js b/test/fixtures/handlers/path.with.period.js index d4d56eb..a3178c5 100644 --- a/test/fixtures/handlers/path.with.period.js +++ b/test/fixtures/handlers/path.with.period.js @@ -1,7 +1,5 @@ -'use strict'; - -module.exports = { - get: function (req, h) { - return null - } +export default { + get: function (req, h) { + return null; + }, }; diff --git a/test/fixtures/handlers/pets.js b/test/fixtures/handlers/pets.js index 346e1b9..c7d057c 100644 --- a/test/fixtures/handlers/pets.js +++ b/test/fixtures/handlers/pets.js @@ -1,12 +1,10 @@ -'use strict'; +import Store from "../lib/store.js"; -const Store = require('../lib/store'); - -module.exports = { - get: function (req, h) { - return Store.all(); - }, - post: function (req, h) { - return Store.get(Store.put(req.payload)); - } +export default { + get: function (req, h) { + return Store.all(); + }, + post: function (req, h) { + return Store.get(Store.put(req.payload)); + }, }; diff --git a/test/fixtures/handlers/pets/{id}.js b/test/fixtures/handlers/pets/{id}.js index 6739946..bbc24cb 100644 --- a/test/fixtures/handlers/pets/{id}.js +++ b/test/fixtures/handlers/pets/{id}.js @@ -1,18 +1,16 @@ -'use strict'; +import Store from "../../lib/store.js"; -const Store = require('../../lib/store'); - -module.exports = { - get: [ - function (req, h) { - return Store.get(req.params.id); - }, - function handler(req, h) { - return req.pre.p1; - } - ], - delete: function (req, h) { - Store.delete(req.params.id); - return Store.all(); - } +export default { + get: [ + function (req, h) { + return Store.get(req.params.id); + }, + function handler(req, h) { + return req.pre.p1; + }, + ], + delete: function (req, h) { + Store.delete(req.params.id); + return Store.all(); + }, }; diff --git a/test/fixtures/lib/store.js b/test/fixtures/lib/store.js index 5b54f3c..d1ba75a 100644 --- a/test/fixtures/lib/store.js +++ b/test/fixtures/lib/store.js @@ -1,19 +1,17 @@ -'use strict'; - var store = []; -module.exports = { - put: function (data) { - store.push(data); - return store.length - 1; - }, - get: function (id) { - return store[id]; - }, - delete: function (id) { - store.splice(id, 1); - }, - all: function () { - return store; - } +export default { + put: function (data) { + store.push(data); + return store.length - 1; + }, + get: function (id) { + return store[id]; + }, + delete: function (id) { + store.splice(id, 1); + }, + all: function () { + return store; + }, }; diff --git a/test/fixtures/lib/stub-auth-token-scheme.js b/test/fixtures/lib/stub-auth-token-scheme.js index 70b52dc..99cad7c 100644 --- a/test/fixtures/lib/stub-auth-token-scheme.js +++ b/test/fixtures/lib/stub-auth-token-scheme.js @@ -1,45 +1,41 @@ -'use strict'; - -var Boom = require('@hapi/boom'); +import Boom from "@hapi/boom"; const register = function (server, options) { - server.auth.scheme('stub-auth-token', function (server, options) { - const scheme = { - authenticate: async function (request, h) { - const token = request.headers.authorization; - - if (!token) { - throw Boom.unauthorized(null, 'stub-auth-token'); - } - - try { - const { credentials, artifacts } = await options.validateFunc(token); - - if (!credentials) { - throw Boom.unauthorized(null, 'stub-auth-token', { credentials }); - } - - return h.authenticated({ credentials, artifacts }); - } - catch (error) { - throw error; - } - } - }; - - return scheme; - }); -}; + server.auth.scheme("stub-auth-token", (server, options) => { + const scheme = { + authenticate: async function (request, h) { + const token = request.headers.authorization; -const buildValidateFunc = function (allowedToken) { - return async function (token) { + if (!token) { + throw Boom.unauthorized(null, "stub-auth-token"); + } + + try { + const { credentials, artifacts } = await options.validateFunc(token); - if (token === allowedToken) { - return { credentials: { scope: [ 'api1:read' ] }, artifacts: { }}; + if (!credentials) { + throw Boom.unauthorized(null, "stub-auth-token", { credentials }); + } + + return h.authenticated({ credentials, artifacts }); + } catch (error) { + throw error; } + }, + }; + + return scheme; + }); +}; - return {}; +const buildValidateFunc = function (allowedToken) { + return async function (token) { + if (token === allowedToken) { + return { credentials: { scope: ["api1:read"] }, artifacts: {} }; } + + return {}; + }; }; -module.exports = { register, name: 'stub-auth-token', buildValidateFunc}; +export default { register, name: "stub-auth-token", buildValidateFunc }; diff --git a/test/fixtures/lib/xauth-scheme.js b/test/fixtures/lib/xauth-scheme.js index ffc90be..73a8b9b 100644 --- a/test/fixtures/lib/xauth-scheme.js +++ b/test/fixtures/lib/xauth-scheme.js @@ -1,15 +1,13 @@ -'use strict'; +import Boom from "@hapi/boom"; -var Boom = require('@hapi/boom'); - -const register = function (server, { name }) { - server.auth.scheme(name /*apiKey*/, (server, { validate }) => { - return { - authenticate: async function (request, h) { - return h.authenticated(await validate(request)); - } - }; - }); +const register = function (server, { name }) { + server.auth.scheme(name, /*apiKey*/ (server, { validate }) => { + return { + authenticate: async function (request, h) { + return h.authenticated(await validate(request)); + }, + }; + }); }; -module.exports = { register, name: 'x-auth-scheme' }; +export default { register, name: "x-auth-scheme" }; diff --git a/test/fixtures/lib/xauth-strategy.js b/test/fixtures/lib/xauth-strategy.js index 814d6e3..cdd92cd 100644 --- a/test/fixtures/lib/xauth-strategy.js +++ b/test/fixtures/lib/xauth-strategy.js @@ -1,19 +1,17 @@ -'use strict'; - -const Boom = require('@hapi/boom'); +import Boom from "@hapi/boom"; const register = function (server, { name, scheme, where, lookup }) { - server.auth.strategy(name, scheme, { - validate: async function (request) { - const token = request.headers[lookup]; + server.auth.strategy(name, scheme, { + validate: async function (request) { + const token = request.headers[lookup]; - if (token === '12345') { - return { credentials: { scope: ['read'] }, artifacts: { token } }; - } + if (token === "12345") { + return { credentials: { scope: ["read"] }, artifacts: { token } }; + } - throw Boom.unauthorized(); - } - }); + throw Boom.unauthorized(); + }, + }); }; -module.exports = { register, name: 'x-auth-strategy' }; +export default { register, name: "x-auth-strategy" }; diff --git a/test/test-auth.js b/test/test-auth.js index 8b5ed5a..38277cb 100644 --- a/test/test-auth.js +++ b/test/test-auth.js @@ -1,189 +1,212 @@ -'use strict'; - -const Test = require('tape'); -const Path = require('path'); -const OpenAPI = require('../lib'); -const Hapi = require('@hapi/hapi'); -const StubAuthTokenScheme = require('./fixtures/lib/stub-auth-token-scheme'); - -Test('authentication', function (t) { - t.test('token authentication', async function (t) { - t.plan(3); - - const server = new Hapi.Server(); - - try { - await server.register({ plugin: StubAuthTokenScheme }); - - server.auth.strategy('api_key', 'stub-auth-token', { - validateFunc: StubAuthTokenScheme.buildValidateFunc('12345') - }); - - server.auth.strategy('api_key2', 'stub-auth-token', { - validateFunc: StubAuthTokenScheme.buildValidateFunc('98765') - }); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets_authed.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} unauthenticated.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat' - } - }); - - t.strictEqual(response.statusCode, 401, `${response.request.path} unauthenticated.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets', - headers: { - authorization: '12345', - 'custom-header': 'Hello' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK when authorized and authenticated.`); - } - catch (error) { - t.fail(error.message); - } - }); - - t.test('unauthorized', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - try { - await server.register({ plugin: StubAuthTokenScheme }); - - server.auth.strategy('api_key', 'stub-auth-token', { - validateFunc: async function (token) { - return { credentials: { scope: [ 'api3:read' ] }, artifacts: { }}; - } - }); - - server.auth.strategy('api_key2', 'stub-auth-token', { - validateFunc: () => ({ isValid: true }) - }); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets_authed.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets', - headers: { - authorization: '12345' - } - }); - - t.strictEqual(response.statusCode, 403, `${response.request.path} unauthorized.`); - } - catch (error) { - t.fail(error.message); - } - }); - - t.test('with root auth', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - try { - await server.register({ plugin: StubAuthTokenScheme }); - - server.auth.strategy('api_key', 'stub-auth-token', { - validateFunc: async function (token) { - return { credentials: { scope: [ 'api3:read' ] }, artifacts: { }}; - } - }); - - server.auth.strategy('api_key2', 'stub-auth-token', { - validateFunc: () => ({ isValid: true }) - }); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets_root_authed.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets', - headers: { - authorization: '12345' - } - }); - - t.strictEqual(response.statusCode, 403, `${response.request.path} unauthorized.`); - } - catch (error) { - t.fail(error.message); - } - }); +import Test from "tape"; +import Path from "path"; +import OpenAPI from "../lib/index.js"; +import Hapi from "@hapi/hapi"; +import StubAuthTokenScheme from "./fixtures/lib/stub-auth-token-scheme.js"; + +import { fileURLToPath } from "url"; +const __dirname = Path.dirname(fileURLToPath(import.meta.url)); + +Test("authentication", (t) => { + t.test("token authentication", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + try { + await server.register({ plugin: StubAuthTokenScheme }); + + server.auth.strategy("api_key", "stub-auth-token", { + validateFunc: StubAuthTokenScheme.buildValidateFunc("12345"), + }); + + server.auth.strategy("api_key2", "stub-auth-token", { + validateFunc: StubAuthTokenScheme.buildValidateFunc("98765"), + }); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets_authed.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); + + t.strictEqual( + response.statusCode, + 200, + `${response.request.path} unauthenticated.`, + ); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + }, + }); + + t.strictEqual( + response.statusCode, + 401, + `${response.request.path} unauthenticated.`, + ); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + headers: { + authorization: "12345", + "custom-header": "Hello", + }, + }); + + t.strictEqual( + response.statusCode, + 200, + `${response.request.path} OK when authorized and authenticated.`, + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("unauthorized", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ plugin: StubAuthTokenScheme }); + + server.auth.strategy("api_key", "stub-auth-token", { + validateFunc: async function (token) { + return { credentials: { scope: ["api3:read"] }, artifacts: {} }; + }, + }); + + server.auth.strategy("api_key2", "stub-auth-token", { + validateFunc: () => ({ isValid: true }), + }); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets_authed.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + headers: { + authorization: "12345", + }, + }); + + t.strictEqual( + response.statusCode, + 403, + `${response.request.path} unauthorized.`, + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("with root auth", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ plugin: StubAuthTokenScheme }); + + server.auth.strategy("api_key", "stub-auth-token", { + validateFunc: async function (token) { + return { credentials: { scope: ["api3:read"] }, artifacts: {} }; + }, + }); + + server.auth.strategy("api_key2", "stub-auth-token", { + validateFunc: () => ({ isValid: true }), + }); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets_root_authed.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + headers: { + authorization: "12345", + }, + }); + + t.strictEqual( + response.statusCode, + 403, + `${response.request.path} unauthorized.`, + ); + } catch (error) { + t.fail(error.message); + } + }); }); -Test('authentication with x-auth', function (t) { - - t.test('authenticated', async function (t) { - t.plan(2); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets_xauthed.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 401, `${response.request.path} unauthenticated.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets', - headers: { - authorization: '12345' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK when authorized and authenticated.`); - } - catch (error) { - t.fail(error.message); - } - }); - +Test("authentication with x-auth", (t) => { + t.test("authenticated", async (t) => { + t.plan(2); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets_xauthed.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); + + t.strictEqual( + response.statusCode, + 401, + `${response.request.path} unauthenticated.`, + ); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + headers: { + authorization: "12345", + }, + }); + + t.strictEqual( + response.statusCode, + 200, + `${response.request.path} OK when authorized and authenticated.`, + ); + } catch (error) { + t.fail(error.message); + } + }); }); diff --git a/test/test-forms.js b/test/test-forms.js index d8b8fa6..89594a0 100644 --- a/test/test-forms.js +++ b/test/test-forms.js @@ -1,123 +1,122 @@ -'use strict'; - -const Test = require('tape'); -const Path = require('path'); -const OpenAPI = require('../lib'); -const Hapi = require('@hapi/hapi'); - - -Test('form data', function (t) { - - t.test('upload', async function (t) { - t.plan(1); - - try { - const server = new Hapi.Server(); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/form.json'), - handlers: { - upload: { - post: function (req, h) { - return { - upload: req.payload.toString() - }; - } - } - }, - outputvalidation: true - } - }); - - const response = await server.inject({ - method: 'POST', - url: '/v1/forms/upload', - headers: { - 'content-type': 'application/x-www-form-urlencoded' - }, - payload: 'name=thing&upload=data' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('bad content type', async function (t) { - t.plan(1); - - try { - const server = new Hapi.Server(); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/form.json'), - handlers: { - upload: { - post: function (req, h) { - return ''; - } - } - } - } - }); - - const response = await server.inject({ - method: 'POST', - url: '/v1/forms/upload', - payload: 'name=thing&upload=data' - }); - - t.strictEqual(response.statusCode, 415, `${response.request.path} unsupported media type.`); - } - catch (error) { - t.fail(error.message); - } - - }); - - - t.test('invalid payload', async function (t) { - t.plan(1); - - try { - const server = new Hapi.Server(); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/form.json'), - handlers: { - upload: { - post: function (req, h) { - return; - } - } - } - } - }); - - const response = await server.inject({ - method: 'POST', - url: '/v1/forms/upload', - headers: { - 'content-type': 'application/x-www-form-urlencoded' - }, - payload: 'name=thing&upload=' - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} validation error.`); - } - catch (error) { - t.fail(error.message); - } - - }); - +import Test from "tape"; +import Path from "path"; +import OpenAPI from "../lib/index.js"; +import Hapi from "@hapi/hapi"; + +import { fileURLToPath } from "url"; +const __dirname = Path.dirname(fileURLToPath(import.meta.url)); + +Test("form data", (t) => { + t.test("upload", async (t) => { + t.plan(1); + + try { + const server = new Hapi.Server(); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/form.json"), + handlers: { + upload: { + post: function (req, h) { + return { + upload: req.payload.toString(), + }; + }, + }, + }, + outputvalidation: true, + }, + }); + + const response = await server.inject({ + method: "POST", + url: "/v1/forms/upload", + headers: { + "content-type": "application/x-www-form-urlencoded", + }, + payload: "name=thing&upload=data", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("bad content type", async (t) => { + t.plan(1); + + try { + const server = new Hapi.Server(); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/form.json"), + handlers: { + upload: { + post: function (req, h) { + return ""; + }, + }, + }, + }, + }); + + const response = await server.inject({ + method: "POST", + url: "/v1/forms/upload", + payload: "name=thing&upload=data", + }); + + t.strictEqual( + response.statusCode, + 415, + `${response.request.path} unsupported media type.`, + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("invalid payload", async (t) => { + t.plan(1); + + try { + const server = new Hapi.Server(); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/form.json"), + handlers: { + upload: { + post: function (req, h) { + return; + }, + }, + }, + }, + }); + + const response = await server.inject({ + method: "POST", + url: "/v1/forms/upload", + headers: { + "content-type": "application/x-www-form-urlencoded", + }, + payload: "name=thing&upload=", + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} validation error.`, + ); + } catch (error) { + t.fail(error.message); + } + }); }); diff --git a/test/test-hapi-openapi.js b/test/test-hapi-openapi.js index 59260c7..97f2c57 100644 --- a/test/test-hapi-openapi.js +++ b/test/test-hapi-openapi.js @@ -1,1449 +1,1455 @@ -'use strict'; - -const Test = require('tape'); -const Path = require('path'); -const OpenAPI = require('../lib'); -const Hapi = require('@hapi/hapi'); - -Test('test plugin', function (t) { - - t.test('register', async function (t) { - t.plan(3); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - t.ok(server.plugins.openapi.getApi, 'server.plugins.openapi.api exists.'); - t.ok(server.plugins.openapi.setHost, 'server.plugins.openapi.setHost exists.'); - - server.plugins.openapi.setHost('api.paypal.com'); - - t.strictEqual(server.plugins.openapi.getApi().host, 'api.paypal.com', 'server.plugins.openapi.setHost set host.'); - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('register with cors options', async function (t) { - t.plan(3); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - cors: { - origin: ["*"], - maxAge: 86400, - headers: ["Accept", "Authorization", "Content-Type", "If-None-Match"], - exposedHeaders: ["x-count", "link"] - } - } - }); - t.ok(server.plugins.openapi.getApi, 'server.plugins.openapi.api exists.'); - t.ok(server.plugins.openapi.setHost, 'server.plugins.openapi.setHost exists.'); - - server.plugins.openapi.setHost('api.paypal.com'); - - t.strictEqual(server.plugins.openapi.getApi().host, 'api.paypal.com', 'server.plugins.openapi.setHost set host.'); - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('register with boolean cors', async function (t) { - t.plan(3); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - cors: true - } - }); - t.ok(server.plugins.openapi.getApi, 'server.plugins.openapi.api exists.'); - t.ok(server.plugins.openapi.setHost, 'server.plugins.openapi.setHost exists.'); - - server.plugins.openapi.setHost('api.paypal.com'); - - t.strictEqual(server.plugins.openapi.getApi().host, 'api.paypal.com', 'server.plugins.openapi.setHost set host.'); - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('register with object api', async function (t) { - t.plan(3); - - const server = new Hapi.Server(); +import Test from "tape"; +import Path from "path"; +import OpenAPI from "../lib/index.js"; +import Hapi from "@hapi/hapi"; + +import { fileURLToPath } from "url"; +const __dirname = Path.dirname(fileURLToPath(import.meta.url)); + +Test("test plugin", (t) => { + t.test("register", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + t.ok(server.plugins.openapi.getApi, "server.plugins.openapi.api exists."); + t.ok( + server.plugins.openapi.setHost, + "server.plugins.openapi.setHost exists.", + ); + + server.plugins.openapi.setHost("api.paypal.com"); + + t.strictEqual( + server.plugins.openapi.getApi().host, + "api.paypal.com", + "server.plugins.openapi.setHost set host.", + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("register with cors options", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + cors: { + origin: ["*"], + maxAge: 86400, + headers: [ + "Accept", + "Authorization", + "Content-Type", + "If-None-Match", + ], + exposedHeaders: ["x-count", "link"], + }, + }, + }); + t.ok(server.plugins.openapi.getApi, "server.plugins.openapi.api exists."); + t.ok( + server.plugins.openapi.setHost, + "server.plugins.openapi.setHost exists.", + ); + + server.plugins.openapi.setHost("api.paypal.com"); + + t.strictEqual( + server.plugins.openapi.getApi().host, + "api.paypal.com", + "server.plugins.openapi.setHost set host.", + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("register with boolean cors", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + cors: true, + }, + }); + t.ok(server.plugins.openapi.getApi, "server.plugins.openapi.api exists."); + t.ok( + server.plugins.openapi.setHost, + "server.plugins.openapi.setHost exists.", + ); + + server.plugins.openapi.setHost("api.paypal.com"); + + t.strictEqual( + server.plugins.openapi.getApi().host, + "api.paypal.com", + "server.plugins.openapi.setHost set host.", + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("register with object api", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Test Object API", + version: "1.0.0", + }, + host: "example.com", + consumes: [ + "application/json", + ], + produces: [ + "application/json", + ], + paths: { + "/test": { + get: { + operationId: "testGet", + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, + }; - const api = { - swagger: '2.0', - info: { - title: 'Test Object API', - version: '1.0.0' + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + get(request, h) { + return; + }, }, - host: 'example.com', - consumes: [ - 'application/json' - ], - produces: [ - 'application/json' + }, + }, + }); + + t.ok(server.plugins.openapi.getApi, "server.plugins.openapi.api exists."); + t.ok( + server.plugins.openapi.setHost, + "server.plugins.openapi.setHost exists.", + ); + + server.plugins.openapi.setHost("api.paypal.com"); + + t.strictEqual( + server.plugins.openapi.getApi().host, + "api.paypal.com", + "server.plugins.openapi.setHost set host.", + ); + } catch (error) { + console.log(error.stack); + t.fail(error.message); + } + }); + + t.test('register with optional query parameters does not change "request.orig"', async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Test Optional Query Params", + version: "1.0.0", + }, + paths: { + "/test": { + get: { + parameters: [ + { + name: "optionalParameter", + in: "query", + required: false, + type: "string", + }, ], - paths: { - '/test': { - get: { - operationId: 'testGet', - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - get(request, h) { - return; - } - } - } - } - }); - - t.ok(server.plugins.openapi.getApi, 'server.plugins.openapi.api exists.'); - t.ok(server.plugins.openapi.setHost, 'server.plugins.openapi.setHost exists.'); - - server.plugins.openapi.setHost('api.paypal.com'); - - t.strictEqual(server.plugins.openapi.getApi().host, 'api.paypal.com', 'server.plugins.openapi.setHost set host.'); - } - catch (error) { - console.log(error.stack) - t.fail(error.message); - } - - }); + responses: { + 200: { + description: "OK", + }, + }, + }, + }, + }, + }; - t.test('register with optional query parameters does not change "request.orig"', async function (t) { - t.plan(1); + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + get(request, h) { + return request.orig; + }, + }, + }, + }, + }); + + const { result } = await server.inject({ + method: "GET", + url: "/test", + }); + t.ok( + Object.entries(result.query).length === 0, + "request.orig was not modified", + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("api docs", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/api-docs", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + const body = JSON.parse(response.payload); + + t.equal(body.info["x-meta"], undefined, "stripped x-"); + t.equal( + body.paths["/pets"].get.parameters[0]["x-meta"], + undefined, + "stripped x- from array.", + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("api docs strip vendor extensions false", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + docs: { + stripExtensions: false, + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/api-docs", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + const body = JSON.parse(response.payload); + + t.equal(body.info["x-meta"], "test"); + t.equal(body.paths["/pets"].get.parameters[0]["x-meta"], "test"); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("api docs auth false", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + docs: { + auth: false, + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/api-docs", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("api docs change path", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + docs: { + path: "/spec", + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/spec", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("api docs change path (with no basepath prefix)", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + docs: { + path: "/spec", + prefixBasePath: false, + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/spec", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("api docs change path old way", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + docspath: "/spec", + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/spec", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("minimal api spec support", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + get: { + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, + }; - const server = new Hapi.Server(); + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + get(request, h) { + return "test"; + }, + }, + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/test", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("trailing slashes", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test/": { + get: { + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, + }; - const api = { - swagger: '2.0', - info: { - title: 'Test Optional Query Params', - version: '1.0.0' + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + get(request, h) { + return "test"; + }, }, - paths: { - '/test': { - get: { - parameters: [ - { - name: 'optionalParameter', - in: 'query', - required: false, - type: 'string', - }, - ], - responses: { - 200: { - description: 'OK' - } - } - } - } - } - }; - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - get(request, h) { - return request.orig; - } - } - } - } - }); - - const { result } = await server.inject({ - method: 'GET', - url: '/test' - }); - t.ok(Object.entries(result.query).length === 0, 'request.orig was not modified'); - } - catch (error) { - t.fail(error.message); - } - }); + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/test/", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("routes", async (t) => { + t.plan(6); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); - t.test('api docs', async function (t) { - t.plan(3); + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); - const server = new Hapi.Server(); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/api-docs' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - const body = JSON.parse(response.payload); - - t.equal(body.info['x-meta'], undefined, 'stripped x-'); - t.equal(body.paths['/pets'].get.parameters[0]['x-meta'], undefined, 'stripped x- from array.'); - } - catch (error) { - t.fail(error.message); - } - }); + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + }, + }); - t.test('api docs strip vendor extensions false', async function (t) { - t.plan(3); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - const server = new Hapi.Server(); + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + name: 123, + }, + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} payload bad.`, + ); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "DELETE", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/path.with.period", + }); + + t.strictEqual(response.statusCode, 204, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("routes with output validation", async (t) => { + t.plan(5); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + outputvalidation: true, + }, + }); - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - docs: { - stripExtensions: false - } - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/api-docs' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - const body = JSON.parse(response.payload); - - t.equal(body.info['x-meta'], 'test'); - t.equal(body.paths['/pets'].get.parameters[0]['x-meta'], 'test'); - } - catch (error) { - t.fail(error.message); - } - }); + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); - t.test('api docs auth false', async function (t) { - t.plan(1); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - const server = new Hapi.Server(); + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + }, + }); - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - docs: { - auth: false - } - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/api-docs' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } - }); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - t.test('api docs change path', async function (t) { - t.plan(1); + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + name: 123, + }, + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} payload bad.`, + ); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "DELETE", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("output validation fails", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: { + pets: { + "{id}": { + get(req, h) { + return "bad response type"; + }, + }, + }, + }, + outputvalidation: true, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual( + response.statusCode, + 500, + `${response.request.path} failed.`, + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("routes x-handler", async (t) => { + t.plan(4); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets_xhandlers.json"), + }, + }); - const server = new Hapi.Server(); + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - docs: { - path: '/spec' - } - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/spec' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } - }); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - t.test('api docs change path (with no basepath prefix)', async function (t) { - t.plan(1); + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + }, + }); - const server = new Hapi.Server(); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - docs: { - path: '/spec', - prefixBasePath: false - } - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/spec' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } - }); + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); - t.test('api docs change path old way', async function (t) { - t.plan(1); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - const server = new Hapi.Server(); + response = await server.inject({ + method: "DELETE", + url: "/v1/petstore/pets/0", + }); - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - docspath: '/spec' - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/spec' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } - }); + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); - t.test('minimal api spec support', async function (t) { - t.plan(1); + t.test("query validation", async (t) => { + const server = new Hapi.Server(); - const server = new Hapi.Server(); + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.json"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + + const queryStringToStatusCode = { + "limit=2": 200, + "tags=some_tag&tags=some_other_tag": 200, + "tags=single_tag": 200, + "limit=2&tags=some_tag&tags=some_other_tag": 200, + "limit=a_string": 400, + }; + + for (const queryString in queryStringToStatusCode) { + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets?" + queryString, + }); - const api = { - swagger: '2.0', + t.strictEqual( + response.statusCode, + queryStringToStatusCode[queryString], + queryString, + ); + } + + t.end(); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("query validation with arrays", async (t) => { + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: { + swagger: "2.0", info: { - title: 'Minimal', - version: '1.0.0' + title: "Minimal", + version: "1.0.0", }, paths: { - '/test': { - get: { - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/test' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('trailing slashes', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - const api = { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' + "/test": { + get: { + description: "", + parameters: [ + { + name: "tags", + in: "query", + required: false, + type: "array", + items: { + type: "string", + }, + collectionFormat: "csv", + }, + ], + responses: { + 200: { + description: "default response", + }, + }, + }, + }, }, - paths: { - '/test/': { - get: { - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/test/' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('routes', async function (t) { - t.plan(6); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - name: 123 - } - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} payload bad.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'DELETE', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/path.with.period' - }); - - t.strictEqual(response.statusCode, 204, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('routes with output validation', async function (t) { - t.plan(5); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers'), - outputvalidation: true - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - name: 123 - } - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} payload bad.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'DELETE', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('output validation fails', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: { - pets: { - '{id}': { - get(req, h) { - return 'bad response type'; - } - } - } + }, + handlers: { + test: { + get(request, h) { + t.ok(request.query.tags, "query exists."); + t.equal(request.query.tags.length, 2, "two array elements."); + t.equal(request.query.tags[0], "some_tag", "values correct."); + return "test"; + }, + }, + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/test?tags=some_tag,some_other_tag", + }); + + t.strictEqual(response.statusCode, 200, "csv format supported."); + + t.end(); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("parse description from api definition", async (t) => { + t.test("do not break with empty descriptions", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + get: { + description: "", + responses: { + 200: { + description: "default response", + }, }, - outputvalidation: true - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 500, `${response.request.path} failed.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('routes x-handler', async function (t) { - t.plan(4); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets_xhandlers.json') - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'DELETE', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - - t.test('query validation', async function (t) { - - const server = new Hapi.Server(); + }, + }, + }, + }, + handlers: { + test: { + get(request, h) { + return "test"; + }, + }, + }, + }, + }); - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.json'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - const queryStringToStatusCode = { - 'limit=2': 200, - 'tags=some_tag&tags=some_other_tag': 200, - 'tags=single_tag': 200, - 'limit=2&tags=some_tag&tags=some_other_tag': 200, - 'limit=a_string': 400 - } - - for (const queryString in queryStringToStatusCode) { - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets?' + queryString - }); - - t.strictEqual(response.statusCode, queryStringToStatusCode[queryString], queryString); - } - - t.end(); - } - catch (error) { - t.fail(error.message); - } + t.pass(); + } catch (error) { + t.fail(error.message); + } }); - t.test('query validation with arrays', async function (t) { - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' - }, - paths: { - '/test': { - get: { - description: '', - parameters: [ - { - name: 'tags', - in: 'query', - required: false, - type: 'array', - items: { - type: 'string' - }, - collectionFormat: 'csv' - } - ], - responses: { - 200: { - description: 'default response' - } - } - } - } - } + t.test("create the right description for the route", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + get: { + description: "A simple description for the route", + responses: { + 200: { + description: "default response", + }, }, - handlers: { - test: { - get(request, h) { - t.ok(request.query.tags, 'query exists.'); - t.equal(request.query.tags.length, 2, 'two array elements.'); - t.equal(request.query.tags[0], 'some_tag', 'values correct.'); - return 'test'; - } - } - } - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/test?tags=some_tag,some_other_tag' - }); - - t.strictEqual(response.statusCode, 200, 'csv format supported.'); - - t.end(); - } - catch (error) { - t.fail(error.message); - } - }); - - t.test('parse description from api definition', async function (t) { - t.test('do not break with empty descriptions', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' - }, - paths: { - '/test': { - get: { - description: '', - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } - }); - - t.pass(); - } catch (error) { - t.fail(error.message); - } + }, + }, + }, + }, + handlers: { + test: { + get(request, h) { + return "test"; + }, + }, + }, + }, }); - t.test('create the right description for the route', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' - }, - paths: { - '/test': { - get: { - description: 'A simple description for the route', - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } - }); - - const response = await server.inject({ method: 'GET', url: '/test' }); - t.strictEqual(response.request.route.settings.description, 'A simple description for the route'); - } catch (error) { - t.fail(error.message); - } - }); + const response = await server.inject({ method: "GET", url: "/test" }); + t.strictEqual( + response.request.route.settings.description, + "A simple description for the route", + ); + } catch (error) { + t.fail(error.message); + } }); - - t.test('hapi payload options (assert via parse:false)', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - const api = { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' + }); + + t.test("hapi payload options (assert via parse:false)", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + post: { + "x-hapi-options": { + payload: { + parse: false, + }, }, - paths: { - '/test': { - post: { - 'x-hapi-options': { - payload: { - parse: false - } - }, - parameters: [ - { - name: 'thing', - in: 'body', - schema: { - type: 'object', - properties: { - id: { - type: 'string' - } - } - } - } - ], - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - post() { - return 'test'; - } - } - } - } - }); - - let response = await server.inject({ - method: 'POST', - url: '/test', - payload: { - id: 1 //won't fail because parse is false - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('hapi allowUnknown request payload properties', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - const api = { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' + parameters: [ + { + name: "thing", + in: "body", + schema: { + type: "object", + properties: { + id: { + type: "string", + }, + }, + }, + }, + ], + responses: { + 200: { + description: "default response", + }, }, - paths: { - '/test': { - post: { - 'x-hapi-options': { - validate: { - options: { - allowUnknown: true - } - } - }, - parameters: [ - { - name: 'thing', - in: 'body', - schema: { - type: 'object', - properties: { - id: { - type: 'string' - } - } - } - } - ], - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - post() { - return 'test'; - } - } - } - } - }); - - let response = await server.inject({ - method: 'POST', - url: '/test', - payload: { - id: 'string-id', - excessive: 42 - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('hapi array parameters', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); + }, + }, + }, + }; - const api = { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + post() { + return "test"; + }, }, - paths: { - '/test': { - post: { - parameters: [ - { - name: 'body', - in: 'body', - schema: { - type: "array", - items: { - type: "object", - properties: { - name: { - type: "string" - }, - breed: { - type: "string" - } - } - } - } - } - ], - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - - try { - await server.register({ - plugin: OpenAPI, + }, + }, + }); + + const response = await server.inject({ + method: "POST", + url: "/test", + payload: { + id: 1, //won't fail because parse is false + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("hapi allowUnknown request payload properties", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + post: { + "x-hapi-options": { + validate: { options: { - api, - handlers: { - test: { - post() { - return 'test'; - } - } - } - } - }); - - let response = await server.inject({ - method: 'POST', - url: '/test', - payload: [ - { - name: 'Fido', - breed: 'Pointer' + allowUnknown: true, + }, + }, + }, + parameters: [ + { + name: "thing", + in: "body", + schema: { + type: "object", + properties: { + id: { + type: "string", }, - { - name: 'Frodo', - breed: 'Beagle' - } - ] - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('hapi operation tags', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - const api = { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' + }, + }, + }, + ], + responses: { + 200: { + description: "default response", + }, }, - paths: { - '/test': { - get: { - tags: [ - 'sample1', - 'sample2' - ], - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - const expectedTags = ['api', 'sample1', 'sample2'] - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - get() { - return 'test'; - } - } - } - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/test' - }); - const responsteTags = response.request.route.settings.tags - - t.deepEqual(responsteTags, expectedTags, 'additional tags successfully configured'); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('hapi operation tags omitted', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); + }, + }, + }, + }; - const api = { - swagger: '2.0', - info: { - title: 'Minimal', - version: '1.0.0' + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + post() { + return "test"; + }, }, - paths: { - '/test': { - get: { - responses: { - 200: { - description: 'default response' - } - } - } - } - } - }; - const expectedDefaultTags = ['api'] - - try { - await server.register({ - plugin: OpenAPI, - options: { - api, - handlers: { - test: { - get() { - return 'test'; - } - } - } - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/test' - }); - const responsteTags = response.request.route.settings.tags - - t.deepEqual(responsteTags, expectedDefaultTags, 'returned default tags'); - - } - catch (error) { - t.fail(error.message); - } - - }); - -}); - -Test('multi-register', function (t) { - - const api1 = { - swagger: '2.0', - info: { - title: 'API 1', - version: '1.0.0' + }, }, - basePath: '/api1', - paths: { - '/test': { - get: { - responses: { - 200: { - description: 'default response' - } - } - } - } - } + }); + + const response = await server.inject({ + method: "POST", + url: "/test", + payload: { + id: "string-id", + excessive: 42, + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("hapi array parameters", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + post: { + parameters: [ + { + name: "body", + in: "body", + schema: { + type: "array", + items: { + type: "object", + properties: { + name: { + type: "string", + }, + breed: { + type: "string", + }, + }, + }, + }, + }, + ], + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, }; - const api2 = { - swagger: '2.0', - info: { - title: 'API 2', - version: '1.0.0' + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + post() { + return "test"; + }, + }, + }, }, - basePath: '/api2', - paths: { - '/test': { - get: { - responses: { - 200: { - description: 'default response' - } - } - } - } - } + }); + + const response = await server.inject({ + method: "POST", + url: "/test", + payload: [ + { + name: "Fido", + breed: "Pointer", + }, + { + name: "Frodo", + breed: "Beagle", + }, + ], + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("hapi operation tags", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + get: { + tags: [ + "sample1", + "sample2", + ], + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, }; + const expectedTags = ["api", "sample1", "sample2"]; + + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + get() { + return "test"; + }, + }, + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/test", + }); + const responsteTags = response.request.route.settings.tags; + + t.deepEqual( + responsteTags, + expectedTags, + "additional tags successfully configured", + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("hapi operation tags omitted", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + const api = { + swagger: "2.0", + info: { + title: "Minimal", + version: "1.0.0", + }, + paths: { + "/test": { + get: { + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, + }; + const expectedDefaultTags = ["api"]; + + try { + await server.register({ + plugin: OpenAPI, + options: { + api, + handlers: { + test: { + get() { + return "test"; + }, + }, + }, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/test", + }); + const responsteTags = response.request.route.settings.tags; + + t.deepEqual(responsteTags, expectedDefaultTags, "returned default tags"); + } catch (error) { + t.fail(error.message); + } + }); +}); - t.test('support register multiple', async function (t) { - t.plan(2); - - const server = new Hapi.Server(); - - try { - await server.register([ - { - plugin: OpenAPI, - options: { - api: api1, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } +Test("multi-register", (t) => { + const api1 = { + swagger: "2.0", + info: { + title: "API 1", + version: "1.0.0", + }, + basePath: "/api1", + paths: { + "/test": { + get: { + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, + }; + + const api2 = { + swagger: "2.0", + info: { + title: "API 2", + version: "1.0.0", + }, + basePath: "/api2", + paths: { + "/test": { + get: { + responses: { + 200: { + description: "default response", + }, + }, + }, + }, + }, + }; + + t.test("support register multiple", async (t) => { + t.plan(2); + + const server = new Hapi.Server(); + + try { + await server.register([ + { + plugin: OpenAPI, + options: { + api: api1, + handlers: { + test: { + get(request, h) { + return "test"; }, - { - plugin: OpenAPI, - options: { - api: api2, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } - } - ]); - - let response = await server.inject({ - method: 'GET', - url: '/api1/test' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'GET', - url: '/api2/test' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - - }); - - t.test('support fail on conflicts', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - try { - await server.register([ - { - plugin: OpenAPI, - options: { - api: api1, - docs: { - path: 'docs1' - }, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } + }, + }, + }, + }, + { + plugin: OpenAPI, + options: { + api: api2, + handlers: { + test: { + get(request, h) { + return "test"; }, - { - plugin: OpenAPI, - options: { - api: api1, - docs: { - path: 'docs2' - }, - handlers: { - test: { - get(request, h) { - return 'test'; - } - } - } - } - } - ]); - - t.fail('should have errored'); - } - catch (error) { - t.pass('expected failure'); - } - - }); + }, + }, + }, + }, + ]); + + let response = await server.inject({ + method: "GET", + url: "/api1/test", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "GET", + url: "/api2/test", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("support fail on conflicts", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register([ + { + plugin: OpenAPI, + options: { + api: api1, + docs: { + path: "docs1", + }, + handlers: { + test: { + get(request, h) { + return "test"; + }, + }, + }, + }, + }, + { + plugin: OpenAPI, + options: { + api: api1, + docs: { + path: "docs2", + }, + handlers: { + test: { + get(request, h) { + return "test"; + }, + }, + }, + }, + }, + ]); + t.fail("should have errored"); + } catch (error) { + t.pass("expected failure"); + } + }); }); -Test('yaml support', function (t) { - t.test('register', async function (t) { - t.plan(3); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/pets.yaml'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - - t.ok(server.plugins.openapi.getApi, 'server.plugins.openapi.getApi exists.'); - t.ok(server.plugins.openapi.setHost, 'server.plugins.openapi.setHost exists.'); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); +Test("yaml support", (t) => { + t.test("register", async (t) => { + t.plan(3); - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } + const server = new Hapi.Server(); - }); + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/pets.yaml"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + + t.ok( + server.plugins.openapi.getApi, + "server.plugins.openapi.getApi exists.", + ); + t.ok( + server.plugins.openapi.setHost, + "server.plugins.openapi.setHost exists.", + ); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); }); diff --git a/test/test-hapi-openapi3.js b/test/test-hapi-openapi3.js index afa90f2..49db5a5 100644 --- a/test/test-hapi-openapi3.js +++ b/test/test-hapi-openapi3.js @@ -1,341 +1,377 @@ -'use strict'; - -const Test = require('tape'); -const Path = require('path'); -const OpenAPI = require('../lib'); -const Hapi = require('@hapi/hapi'); -const StubAuthTokenScheme = require('./fixtures/lib/stub-auth-token-scheme'); - -Test('test plugin', function (t) { - t.test('basic API', async function (t) { - t.plan(8); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/openapi3/defs/pets.yaml'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - t.ok(server.plugins.openapi.getApi, 'server.plugins.openapi.api exists.'); - t.ok(server.plugins.openapi.setHost, 'server.plugins.openapi.setHost exists.'); - - server.plugins.openapi.setHost('api.paypal.com'); - - t.strictEqual(server.plugins.openapi.getApi().host, 'api.paypal.com', 'server.plugins.openapi.setHost set host.'); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - name: 123 - } - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} payload bad.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'DELETE', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } - }); - - t.test('routes with output validation', async function (t) { - t.plan(5); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/openapi3/defs/pets.yaml'), - handlers: Path.join(__dirname, './fixtures/handlers'), - outputvalidation: true - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - name: 123 - } - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} payload bad.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'DELETE', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - } - catch (error) { - t.fail(error.message); - } - }); - - t.test('output validation fails', async function (t) { - t.plan(1); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/openapi3/defs/pets.yaml'), - handlers: { - pets: { - '{id}': { - get(req, h) { - return 'bad response type'; - } - } - } - }, - outputvalidation: true - } - }); - - const response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 500, `${response.request.path} failed.`); - } - catch (error) { - t.fail(error.message); - } - }); - - t.test('additional type properties', async function (t) { - t.plan(11); - - const server = new Hapi.Server(); - - try { - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/openapi3/defs/pets-types.yaml'), - handlers: Path.join(__dirname, './fixtures/handlers'), - outputvalidation: true - } - }); - - let response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat', - coupon: 97 - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat', - coupon: 'Welcome' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Dog', - coupon: false - } - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} payload bad.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Cat', - birthday: '2006-01-02' - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Dog', - birthday: null - } - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Dog', - birthday: '' - } - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} payload bad.`); - - response = await server.inject({ - method: 'POST', - url: '/v1/petstore/pets', - payload: { - id: '0', - name: 'Dog', - birthday: 'yesterday' - } - }); - - t.strictEqual(response.statusCode, 400, `${response.request.path} payload bad.`); - - response = await server.inject({ - method: 'GET', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - - response = await server.inject({ - method: 'DELETE', - url: '/v1/petstore/pets/0' - }); - - t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); - } - catch (error) { - t.fail(error.message); - } - }); - - t.test('security object', async function (t) { - t.plan(3); - - const server = new Hapi.Server(); - - try { - await server.register({ plugin: StubAuthTokenScheme }); - - server.auth.strategy('api_key', 'stub-auth-token', { - validateFunc: StubAuthTokenScheme.buildValidateFunc('12345') - }); - - server.auth.strategy('api_key2', 'stub-auth-token', { - validateFunc: StubAuthTokenScheme.buildValidateFunc('98765') - }); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/openapi3/defs/pets_authed.yaml'), - handlers: Path.join(__dirname, './fixtures/handlers') - } - }); - t.ok(server.plugins.openapi.getApi, 'server.plugins.openapi.api exists.'); - t.ok(server.plugins.openapi.setHost, 'server.plugins.openapi.setHost exists.'); - - server.plugins.openapi.setHost('api.paypal.com'); - - t.strictEqual(server.plugins.openapi.getApi().host, 'api.paypal.com', 'server.plugins.openapi.setHost set host.'); - } - catch (error) { - t.fail(error.message); - } - }); -}); \ No newline at end of file +import Test from "tape"; +import Path from "path"; +import OpenAPI from "../lib/index.js"; +import Hapi from "@hapi/hapi"; +import StubAuthTokenScheme from "./fixtures/lib/stub-auth-token-scheme.js"; + +import { fileURLToPath } from "url"; +const __dirname = Path.dirname(fileURLToPath(import.meta.url)); + +Test("test plugin", (t) => { + t.test("basic API", async (t) => { + t.plan(8); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/openapi3/defs/pets.yaml"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + t.ok(server.plugins.openapi.getApi, "server.plugins.openapi.api exists."); + t.ok( + server.plugins.openapi.setHost, + "server.plugins.openapi.setHost exists.", + ); + + server.plugins.openapi.setHost("api.paypal.com"); + + t.strictEqual( + server.plugins.openapi.getApi().host, + "api.paypal.com", + "server.plugins.openapi.setHost set host.", + ); + + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + name: 123, + }, + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} payload bad.`, + ); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "DELETE", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("routes with output validation", async (t) => { + t.plan(5); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/openapi3/defs/pets.yaml"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + outputvalidation: true, + }, + }); + + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + name: 123, + }, + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} payload bad.`, + ); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "DELETE", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("output validation fails", async (t) => { + t.plan(1); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/openapi3/defs/pets.yaml"), + handlers: { + pets: { + "{id}": { + get(req, h) { + return "bad response type"; + }, + }, + }, + }, + outputvalidation: true, + }, + }); + + const response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual( + response.statusCode, + 500, + `${response.request.path} failed.`, + ); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("additional type properties", async (t) => { + t.plan(11); + + const server = new Hapi.Server(); + + try { + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/openapi3/defs/pets-types.yaml"), + handlers: Path.join(__dirname, "./fixtures/handlers"), + outputvalidation: true, + }, + }); + + let response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + coupon: 97, + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + coupon: "Welcome", + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Dog", + coupon: false, + }, + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} payload bad.`, + ); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Cat", + birthday: "2006-01-02", + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Dog", + birthday: null, + }, + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Dog", + birthday: "", + }, + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} payload bad.`, + ); + + response = await server.inject({ + method: "POST", + url: "/v1/petstore/pets", + payload: { + id: "0", + name: "Dog", + birthday: "yesterday", + }, + }); + + t.strictEqual( + response.statusCode, + 400, + `${response.request.path} payload bad.`, + ); + + response = await server.inject({ + method: "GET", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + + response = await server.inject({ + method: "DELETE", + url: "/v1/petstore/pets/0", + }); + + t.strictEqual(response.statusCode, 200, `${response.request.path} OK.`); + } catch (error) { + t.fail(error.message); + } + }); + + t.test("security object", async (t) => { + t.plan(3); + + const server = new Hapi.Server(); + + try { + await server.register({ plugin: StubAuthTokenScheme }); + + server.auth.strategy("api_key", "stub-auth-token", { + validateFunc: StubAuthTokenScheme.buildValidateFunc("12345"), + }); + + server.auth.strategy("api_key2", "stub-auth-token", { + validateFunc: StubAuthTokenScheme.buildValidateFunc("98765"), + }); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join( + __dirname, + "./fixtures/openapi3/defs/pets_authed.yaml", + ), + handlers: Path.join(__dirname, "./fixtures/handlers"), + }, + }); + t.ok(server.plugins.openapi.getApi, "server.plugins.openapi.api exists."); + t.ok( + server.plugins.openapi.setHost, + "server.plugins.openapi.setHost exists.", + ); + + server.plugins.openapi.setHost("api.paypal.com"); + + t.strictEqual( + server.plugins.openapi.getApi().host, + "api.paypal.com", + "server.plugins.openapi.setHost set host.", + ); + } catch (error) { + t.fail(error.message); + } + }); +}); diff --git a/test/test-utils.js b/test/test-utils.js index e69a2f1..be5b635 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -1,98 +1,94 @@ -'use strict'; +import Test from "tape"; +import Utils from "../lib/utils.js"; -const Test = require('tape'); -const Utils = require('../lib/utils'); +Test("utils", (t) => { + t.test("prefix", (t) => { + t.plan(3); -Test('utils', function (t) { + var str = "foobar"; - t.test('prefix', function (t) { - t.plan(3); + str = Utils.prefix(str, "foo"); - var str = 'foobar'; + t.equal(str, "foobar", "string had prefix so is the same."); - str = Utils.prefix(str, 'foo'); + str = "bar"; - t.equal(str, 'foobar', 'string had prefix so is the same.'); + str = Utils.prefix(str, "foo"); - str = 'bar'; + t.equal(str, "foobar", "string did not have prefix so was changed."); - str = Utils.prefix(str, 'foo'); + t.equal(Utils.prefix(undefined, "foo"), "foo", "handled undefined."); + }); - t.equal(str, 'foobar', 'string did not have prefix so was changed.'); + t.test("unprefix", (t) => { + t.plan(3); - t.equal(Utils.prefix(undefined, 'foo'), 'foo', 'handled undefined.'); - }); + var str = "foobar"; - t.test('unprefix', function (t) { - t.plan(3); + str = Utils.unprefix(str, "foo"); - var str = 'foobar'; + t.equal(str, "bar", "string had prefix so is changed."); - str = Utils.unprefix(str, 'foo'); + str = "bar"; - t.equal(str, 'bar', 'string had prefix so is changed.'); + str = Utils.unprefix(str, "foo"); - str = 'bar'; + t.equal(str, "bar", "string did not have prefix so was not changed."); - str = Utils.unprefix(str, 'foo'); + t.equal(Utils.unprefix(undefined, "foo"), "", "handled undefined."); + }); - t.equal(str, 'bar', 'string did not have prefix so was not changed.'); + t.test("suffix", (t) => { + t.plan(3); - t.equal(Utils.unprefix(undefined, 'foo'), '', 'handled undefined.'); - }); + var str = "foobar"; - t.test('suffix', function (t) { - t.plan(3); + str = Utils.suffix(str, "bar"); - var str = 'foobar'; + t.equal(str, "foobar", "string had suffix so is the same."); - str = Utils.suffix(str, 'bar'); + str = "foo"; - t.equal(str, 'foobar', 'string had suffix so is the same.'); + str = Utils.suffix(str, "bar"); - str = 'foo'; + t.equal(str, "foobar", "string did not have suffix so was changed."); - str = Utils.suffix(str, 'bar'); + t.equal(Utils.suffix(undefined, "foo"), "foo", "handled undefined."); + }); - t.equal(str, 'foobar', 'string did not have suffix so was changed.'); + t.test("unsuffix", (t) => { + t.plan(3); - t.equal(Utils.suffix(undefined, 'foo'), 'foo', 'handled undefined.'); - }); + var str = "foobar"; - t.test('unsuffix', function (t) { - t.plan(3); + str = Utils.unsuffix(str, "bar"); - var str = 'foobar'; + t.equal(str, "foo", "string had suffix so is changed."); - str = Utils.unsuffix(str, 'bar'); + str = "foo"; - t.equal(str, 'foo', 'string had suffix so is changed.'); + str = Utils.unsuffix(str, "bar"); - str = 'foo'; + t.equal(str, "foo", "string did not have suffix so was not changed."); - str = Utils.unsuffix(str, 'bar'); + t.equal(Utils.unsuffix(undefined, "foo"), "", "handled undefined."); + }); - t.equal(str, 'foo', 'string did not have suffix so was not changed.'); + t.test("ends with", (t) => { + t.plan(2); + t.ok(Utils.endsWith("foobar", "bar"), "foobar ends with bar"); + t.ok(!Utils.endsWith("foobar", "x"), "foobar doesn't end with x"); + }); - t.equal(Utils.unsuffix(undefined, 'foo'), '', 'handled undefined.'); - }); + t.test("is httpMethod", (t) => { + const verbs = Utils.verbs; - t.test('ends with', function (t) { - t.plan(2); - t.ok(Utils.endsWith('foobar', 'bar'), 'foobar ends with bar'); - t.ok(!Utils.endsWith('foobar', 'x'), 'foobar doesn\'t end with x'); - }); + t.plan(verbs.length + 1); - t.test('is httpMethod', function (t) { - const verbs = Utils.verbs; - - t.plan(verbs.length + 1); - - for (const verb of verbs) { - t.ok(Utils.isHttpMethod(verb), `${verb} is an http method.`); - } - - t.ok(!Utils.isHttpMethod('Blerg'), 'Blerg is not an http method.'); - }); + for (const verb of verbs) { + t.ok(Utils.isHttpMethod(verb), `${verb} is an http method.`); + } + t.ok(!Utils.isHttpMethod("Blerg"), "Blerg is not an http method."); + }); }); diff --git a/test/test-validators.js b/test/test-validators.js index 581b5d8..65de6db 100644 --- a/test/test-validators.js +++ b/test/test-validators.js @@ -1,95 +1,95 @@ -const Test = require('tape'); -const Validators = require('../lib/validators'); +import Test from "tape"; +import Validators from "../lib/validators.js"; -Test('validator special types', function(t) { +Test("validator special types", (t) => { const api = { - swagger: '2.0', + swagger: "2.0", info: { - title: 'Minimal', - version: '1.0.0' + title: "Minimal", + version: "1.0.0", }, paths: { - '/test': { + "/test": { get: { - description: '', + description: "", parameters: [ { - name: 'dateTime', - in: 'query', + name: "dateTime", + in: "query", required: false, - type: 'string', - format: 'date-time' - } + type: "string", + format: "date-time", + }, ], responses: { 200: { - description: 'default response' - } - } + description: "default response", + }, + }, }, post: { - description: '', + description: "", parameters: [ { - name: 'payload', - in: 'body', + name: "payload", + in: "body", required: true, schema: { - type: 'object', - required: ['requiredProperty'], + type: "object", + required: ["requiredProperty"], properties: { requiredProperty: { - type: 'string' - } - } - } - } - ] - } + type: "string", + }, + }, + }, + }, + ], + }, }, - '/test/{foo*}': { + "/test/{foo*}": { get: { - description: '', + description: "", parameters: [ { - name: 'foo*', - in: 'path', + name: "foo*", + in: "path", required: true, - type: 'string' - } + type: "string", + }, ], responses: { 200: { - description: 'default response' - } - } - } - } - } + description: "default response", + }, + }, + }, + }, + }, }; const validator = Validators.create(api); - t.test('valid date-time', async function(t) { + t.test("valid date-time", async (t) => { t.plan(1); const { validate } = validator.makeValidator( - api.paths['/test'].get.parameters[0] + api.paths["/test"].get.parameters[0], ); try { - validate('1995-09-07T10:40:52Z'); - t.pass('valid date-time'); + validate("1995-09-07T10:40:52Z"); + t.pass("valid date-time"); } catch (error) { t.fail(error.message); } }); - t.test('invalid date-time', async function(t) { + t.test("invalid date-time", async (t) => { t.plan(1); const { validate } = validator.makeValidator( - api.paths['/test'].get.parameters[0] + api.paths["/test"].get.parameters[0], ); const timestamp = Date.now(); @@ -102,42 +102,55 @@ Test('validator special types', function(t) { } }); - t.test('validate multi-segment paths', async function(t) { + t.test("validate multi-segment paths", async (t) => { t.plan(1); - const v = validator.makeAll(api.paths['/test/{foo*}'].get.parameters); + const v = validator.makeAll(api.paths["/test/{foo*}"].get.parameters); const keys = Object.keys(v.validate.params.describe().keys); - if (keys.length === 1 && keys[0] === 'foo') { - return t.pass(`${keys.join(', ')} are valid.`); + if (keys.length === 1 && keys[0] === "foo") { + return t.pass(`${keys.join(", ")} are valid.`); } - t.fail(`${keys.join(', ')} are invalid.`); + + t.fail(`${keys.join(", ")} are invalid.`); }); - t.test('validate missing body parameter', async function(t) { - t.plan(1); + t.test("validate missing body parameter", async (t) => { + t.plan(1); - const { validate } = validator.makeValidator(api.paths['/test'].post.parameters[0]); + const { validate } = validator.makeValidator( + api.paths["/test"].post.parameters[0], + ); - try { - validate(); - t.fail('"undefined" should be invalid'); - } catch (error) { - t.equal(error.message, '"payload" is required', "received expected payload error message"); - } + try { + validate(); + t.fail('"undefined" should be invalid'); + } catch (error) { + t.equal( + error.message, + '"payload" is required', + "received expected payload error message", + ); + } }); - t.test('validate empty object with required property', async function(t) { - t.plan(1); + t.test("validate empty object with required property", async (t) => { + t.plan(1); - const { validate } = validator.makeValidator(api.paths['/test'].post.parameters[0]); + const { validate } = validator.makeValidator( + api.paths["/test"].post.parameters[0], + ); - try { - validate({}); - t.fail('"undefined" should be invalid'); - } catch (error) { - t.match(error.message, /"requiredProperty" is required/, "received expected property error message"); - } - }) + try { + validate({}); + t.fail('"undefined" should be invalid'); + } catch (error) { + t.match( + error.message, + /"requiredProperty" is required/, + "received expected property error message", + ); + } + }); }); diff --git a/test/test_xoptions.js b/test/test_xoptions.js index 2872bb5..4438fb6 100644 --- a/test/test_xoptions.js +++ b/test/test_xoptions.js @@ -1,50 +1,50 @@ -'use strict'; - -const Test = require('tape'); -const Path = require('path'); -const OpenAPI = require('../lib'); -const Hapi = require('@hapi/hapi'); - - -Test('x-hapi-options', function (t) { - - t.test('overrides', async function (t) { - t.plan(1); - - try { - const server = new Hapi.Server(); - - await server.register({ - plugin: OpenAPI, - options: { - api: Path.join(__dirname, './fixtures/defs/form_xoptions.json'), - handlers: { - upload: { - post: function (req, h) { - return { - upload: req.payload.toString() - }; - } - } - } - } - }); - - const response = await server.inject({ - method: 'POST', - url: '/v1/forms/upload', - headers: { - 'content-type': 'application/x-www-form-urlencoded' - }, - payload: 'name=thing&upload=data' - }); - - t.strictEqual(response.statusCode, 404, `${response.request.path} not found due to isInternal.`); - } - catch (error) { - t.fail(error.message); - } - - }); - +import Test from "tape"; +import Path from "path"; +import OpenAPI from "../lib/index.js"; +import Hapi from "@hapi/hapi"; + +import { fileURLToPath } from "url"; +const __dirname = Path.dirname(fileURLToPath(import.meta.url)); + +Test("x-hapi-options", (t) => { + t.test("overrides", async (t) => { + t.plan(1); + + try { + const server = new Hapi.Server(); + + await server.register({ + plugin: OpenAPI, + options: { + api: Path.join(__dirname, "./fixtures/defs/form_xoptions.json"), + handlers: { + upload: { + post: function (req, h) { + return { + upload: req.payload.toString(), + }; + }, + }, + }, + }, + }); + + const response = await server.inject({ + method: "POST", + url: "/v1/forms/upload", + headers: { + "content-type": "application/x-www-form-urlencoded", + }, + payload: "name=thing&upload=data", + }); + + t.strictEqual( + response.statusCode, + 404, + `${response.request.path} not found due to isInternal.`, + ); + } catch (error) { + t.fail(error.message); + } + }); }); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..edf4a06 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2381 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@apidevtools/json-schema-ref-parser@^9.0.6": + version "9.0.9" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#d720f9256e3609621280584f2b47ae165359268b" + integrity sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + +"@apidevtools/openapi-schemas@^2.0.4": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17" + integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== + +"@apidevtools/swagger-methods@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" + integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== + +"@apidevtools/swagger-parser@10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz#32057ae99487872c4dd96b314a1ab4b95d89eaf5" + integrity sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g== + dependencies: + "@apidevtools/json-schema-ref-parser" "^9.0.6" + "@apidevtools/openapi-schemas" "^2.0.4" + "@apidevtools/swagger-methods" "^3.0.2" + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + z-schema "^5.0.1" + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.0.tgz#0dfc80309beec8411e65e706461c408b0bb9b431" + integrity sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA== + dependencies: + "@babel/highlight" "^7.16.0" + +"@babel/compat-data@^7.16.0": + version "7.16.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== + +"@babel/core@^7.7.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.5.tgz#924aa9e1ae56e1e55f7184c8bf073a50d8677f5c" + integrity sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.5" + "@babel/helper-compilation-targets" "^7.16.3" + "@babel/helper-module-transforms" "^7.16.5" + "@babel/helpers" "^7.16.5" + "@babel/parser" "^7.16.5" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.5" + "@babel/types" "^7.16.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/generator@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.5.tgz#26e1192eb8f78e0a3acaf3eede3c6fc96d22bedf" + integrity sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA== + dependencies: + "@babel/types" "^7.16.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-compilation-targets@^7.16.3": + version "7.16.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz#5b480cd13f68363df6ec4dc8ac8e2da11363cbf0" + integrity sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA== + dependencies: + "@babel/compat-data" "^7.16.0" + "@babel/helper-validator-option" "^7.14.5" + browserslist "^4.17.5" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8" + integrity sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-function-name@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz#b7dd0797d00bbfee4f07e9c4ea5b0e30c8bb1481" + integrity sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog== + dependencies: + "@babel/helper-get-function-arity" "^7.16.0" + "@babel/template" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/helper-get-function-arity@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz#0088c7486b29a9cb5d948b1a1de46db66e089cfa" + integrity sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-hoist-variables@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz#4c9023c2f1def7e28ff46fc1dbcd36a39beaa81a" + integrity sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-imports@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz#90538e60b672ecf1b448f5f4f5433d37e79a3ec3" + integrity sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-module-transforms@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz#530ebf6ea87b500f60840578515adda2af470a29" + integrity sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ== + dependencies: + "@babel/helper-environment-visitor" "^7.16.5" + "@babel/helper-module-imports" "^7.16.0" + "@babel/helper-simple-access" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/helper-validator-identifier" "^7.15.7" + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.5" + "@babel/types" "^7.16.0" + +"@babel/helper-simple-access@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz#21d6a27620e383e37534cf6c10bba019a6f90517" + integrity sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-split-export-declaration@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz#29672f43663e936df370aaeb22beddb3baec7438" + integrity sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw== + dependencies: + "@babel/types" "^7.16.0" + +"@babel/helper-validator-identifier@^7.15.7": + version "7.15.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" + integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== + +"@babel/helper-validator-option@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" + integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== + +"@babel/helpers@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.5.tgz#29a052d4b827846dd76ece16f565b9634c554ebd" + integrity sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw== + dependencies: + "@babel/template" "^7.16.0" + "@babel/traverse" "^7.16.5" + "@babel/types" "^7.16.0" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.0.tgz#6ceb32b2ca4b8f5f361fb7fd821e3fddf4a1725a" + integrity sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.16.0", "@babel/parser@^7.16.5": + version "7.16.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" + integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ== + +"@babel/template@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.0.tgz#d16a35ebf4cd74e202083356fab21dd89363ddd6" + integrity sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/parser" "^7.16.0" + "@babel/types" "^7.16.0" + +"@babel/traverse@^7.16.5": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.5.tgz#d7d400a8229c714a59b87624fc67b0f1fbd4b2b3" + integrity sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.5" + "@babel/helper-environment-visitor" "^7.16.5" + "@babel/helper-function-name" "^7.16.0" + "@babel/helper-hoist-variables" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/parser" "^7.16.5" + "@babel/types" "^7.16.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" + to-fast-properties "^2.0.0" + +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@hapi/accept@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" + integrity sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/ammo@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-5.0.1.tgz#9d34560f5c214eda563d838c01297387efaab490" + integrity sha512-FbCNwcTbnQP4VYYhLNGZmA76xb2aHg9AMPiy18NZyWMG310P5KdFGyA9v2rm5ujrIny77dEEIkMOwl0Xv+fSSA== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/b64@5.x.x": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" + integrity sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/boom@9.x.x", "@hapi/boom@^9.1.0": + version "9.1.4" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" + integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/bounce@2.x.x", "@hapi/bounce@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-2.0.0.tgz#e6ef56991c366b1e2738b2cd83b01354d938cf3d" + integrity sha512-JesW92uyzOOyuzJKjoLHM1ThiOvHPOLDHw01YV8yh5nCso7sDwJho1h0Ad2N+E62bZyz46TG3xhAi/78Gsct6A== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/bourne@2.x.x", "@hapi/bourne@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.0.0.tgz#5bb2193eb685c0007540ca61d166d4e1edaf918d" + integrity sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg== + +"@hapi/call@^8.0.0": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@hapi/call/-/call-8.0.1.tgz#9e64cd8ba6128eb5be6e432caaa572b1ed8cd7c0" + integrity sha512-bOff6GTdOnoe5b8oXRV3lwkQSb/LAWylvDMae6RgEWWntd0SHtkYbQukDHKlfaYtVnSAgIavJ0kqszF/AIBb6g== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/catbox-memory@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-5.0.1.tgz#cb63fca0ded01d445a2573b38eb2688df67f70ac" + integrity sha512-QWw9nOYJq5PlvChLWV8i6hQHJYfvdqiXdvTupJFh0eqLZ64Xir7mKNi96d5/ZMUAqXPursfNDIDxjFgoEDUqeQ== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/catbox@^11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-11.1.1.tgz#d277e2d5023fd69cddb33d05b224ea03065fec0c" + integrity sha512-u/8HvB7dD/6X8hsZIpskSDo4yMKpHxFd7NluoylhGrL6cUfYxdQPnvUp9YU2C6F9hsyBVLGulBd9vBN1ebfXOQ== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/podium" "4.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/content@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/content/-/content-5.0.2.tgz#ae57954761de570392763e64cdd75f074176a804" + integrity sha512-mre4dl1ygd4ZyOH3tiYBrOUBzV7Pu/EOs8VLGf58vtOEECWed8Uuw6B4iR9AN/8uQt42tB04qpVaMyoMQh0oMw== + dependencies: + "@hapi/boom" "9.x.x" + +"@hapi/cryptiles@5.x.x": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" + integrity sha512-fo9+d1Ba5/FIoMySfMqPBR/7Pa29J2RsiPrl7bkwo5W5o+AN1dAYQRi4SPrPwwVxVGKjgLOEWrsvt1BonJSfLA== + dependencies: + "@hapi/boom" "9.x.x" + +"@hapi/eslint-plugin@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/eslint-plugin/-/eslint-plugin-5.1.0.tgz#644e4f0d03afb186350ca6735ba66f13a6c544a5" + integrity sha512-D0OvhsjbWW4lhuw0LqERl8vqCIRMnePy9XGYhkf7krzwqzYNEAcBFCafiFsd0gIF6QiQj3O1vYmshRVFZMXdwQ== + +"@hapi/file@2.x.x": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@hapi/file/-/file-2.0.0.tgz#2ecda37d1ae9d3078a67c13b7da86e8c3237dfb9" + integrity sha512-WSrlgpvEqgPWkI18kkGELEZfXr0bYLtr16iIN4Krh9sRnzBZN6nnWxHFxtsnP684wueEySBbXPDg/WfA9xJdBQ== + +"@hapi/hapi@^20.0.3": + version "20.2.1" + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-20.2.1.tgz#7482bc28757cb4671623a61bdb5ce920bffc8a2f" + integrity sha512-OXAU+yWLwkMfPFic+KITo+XPp6Oxpgc9WUH+pxXWcTIuvWbgco5TC/jS8UDvz+NFF5IzRgF2CL6UV/KLdQYUSQ== + dependencies: + "@hapi/accept" "^5.0.1" + "@hapi/ammo" "^5.0.1" + "@hapi/boom" "^9.1.0" + "@hapi/bounce" "^2.0.0" + "@hapi/call" "^8.0.0" + "@hapi/catbox" "^11.1.1" + "@hapi/catbox-memory" "^5.0.0" + "@hapi/heavy" "^7.0.1" + "@hapi/hoek" "^9.0.4" + "@hapi/mimos" "^6.0.0" + "@hapi/podium" "^4.1.1" + "@hapi/shot" "^5.0.5" + "@hapi/somever" "^3.0.0" + "@hapi/statehood" "^7.0.3" + "@hapi/subtext" "^7.0.3" + "@hapi/teamwork" "^5.1.0" + "@hapi/topo" "^5.0.0" + "@hapi/validate" "^1.1.1" + +"@hapi/heavy@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-7.0.1.tgz#73315ae33b6e7682a0906b7a11e8ca70e3045874" + integrity sha512-vJ/vzRQ13MtRzz6Qd4zRHWS3FaUc/5uivV2TIuExGTM9Qk+7Zzqj0e2G7EpE6KztO9SalTbiIkTh7qFKj/33cA== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/hoek@9.x.x", "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.0.3", "@hapi/hoek@^9.0.4": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" + integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw== + +"@hapi/iron@6.x.x": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-6.0.0.tgz#ca3f9136cda655bdd6028de0045da0de3d14436f" + integrity sha512-zvGvWDufiTGpTJPG1Y/McN8UqWBu0k/xs/7l++HVU535NLHXsHhy54cfEMdW7EjwKfbBfM9Xy25FmTiobb7Hvw== + dependencies: + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/mimos@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-6.0.0.tgz#daa523d9c07222c7e8860cb7c9c5501fd6506484" + integrity sha512-Op/67tr1I+JafN3R3XN5DucVSxKRT/Tc+tUszDwENoNpolxeXkhrJ2Czt6B6AAqrespHoivhgZBWYSuANN9QXg== + dependencies: + "@hapi/hoek" "9.x.x" + mime-db "1.x.x" + +"@hapi/nigel@4.x.x": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-4.0.2.tgz#8f84ef4bca4fb03b2376463578f253b0b8e863c4" + integrity sha512-ht2KoEsDW22BxQOEkLEJaqfpoKPXxi7tvabXy7B/77eFtOyG5ZEstfZwxHQcqAiZhp58Ae5vkhEqI03kawkYNw== + dependencies: + "@hapi/hoek" "^9.0.4" + "@hapi/vise" "^4.0.0" + +"@hapi/pez@^5.0.1": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-5.0.3.tgz#b75446e6fef8cbb16816573ab7da1b0522e7a2a1" + integrity sha512-mpikYRJjtrbJgdDHG/H9ySqYqwJ+QU/D7FXsYciS9P7NYBXE2ayKDAy3H0ou6CohOCaxPuTV4SZ0D936+VomHA== + dependencies: + "@hapi/b64" "5.x.x" + "@hapi/boom" "9.x.x" + "@hapi/content" "^5.0.2" + "@hapi/hoek" "9.x.x" + "@hapi/nigel" "4.x.x" + +"@hapi/podium@4.x.x", "@hapi/podium@^4.1.1": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" + integrity sha512-ljsKGQzLkFqnQxE7qeanvgGj4dejnciErYd30dbrYzUOF/FyS/DOF97qcrT3bhoVwCYmxa6PEMhxfCPlnUcD2g== + dependencies: + "@hapi/hoek" "9.x.x" + "@hapi/teamwork" "5.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/shot@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-5.0.5.tgz#a25c23d18973bec93c7969c51bf9579632a5bebd" + integrity sha512-x5AMSZ5+j+Paa8KdfCoKh+klB78otxF+vcJR/IoN91Vo2e5ulXIW6HUsFTCU+4W6P/Etaip9nmdAx2zWDimB2A== + dependencies: + "@hapi/hoek" "9.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/somever@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-3.0.1.tgz#9961cd5bdbeb5bb1edc0b2acdd0bb424066aadcc" + integrity sha512-4ZTSN3YAHtgpY/M4GOtHUXgi6uZtG9nEZfNI6QrArhK0XN/RDVgijlb9kOmXwCR5VclDSkBul9FBvhSuKXx9+w== + dependencies: + "@hapi/bounce" "2.x.x" + "@hapi/hoek" "9.x.x" + +"@hapi/statehood@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-7.0.3.tgz#655166f3768344ed3c3b50375a303cdeca8040d9" + integrity sha512-pYB+pyCHkf2Amh67QAXz7e/DN9jcMplIL7Z6N8h0K+ZTy0b404JKPEYkbWHSnDtxLjJB/OtgElxocr2fMH4G7w== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bounce" "2.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/cryptiles" "5.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/iron" "6.x.x" + "@hapi/validate" "1.x.x" + +"@hapi/subtext@^7.0.3": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-7.0.3.tgz#f7440fc7c966858e1f39681e99eb6171c71e7abd" + integrity sha512-CekDizZkDGERJ01C0+TzHlKtqdXZxzSWTOaH6THBrbOHnsr3GY+yiMZC+AfNCypfE17RaIakGIAbpL2Tk1z2+A== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/content" "^5.0.2" + "@hapi/file" "2.x.x" + "@hapi/hoek" "9.x.x" + "@hapi/pez" "^5.0.1" + "@hapi/wreck" "17.x.x" + +"@hapi/teamwork@5.x.x", "@hapi/teamwork@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.0.tgz#7801a61fc727f702fd2196ef7625eb4e389f4124" + integrity sha512-llqoQTrAJDTXxG3c4Kz/uzhBS1TsmSBa/XG5SPcVXgmffHE1nFtyLIK0hNJHCB3EuBKT84adzd1hZNY9GJLWtg== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/validate@1.x.x", "@hapi/validate@^1.1.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" + integrity sha512-/XMR0N0wjw0Twzq2pQOzPBZlDzkekGcoCtzO314BpIEsbXdYGthQUbxgkGDf4nhk1+IPDAsXqWjMohRQYO06UA== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + +"@hapi/vise@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-4.0.0.tgz#c6a94fe121b94a53bf99e7489f7fcc74c104db02" + integrity sha512-eYyLkuUiFZTer59h+SGy7hUm+qE9p+UemePTHLlIWppEd+wExn3Df5jO04bFQTm7nleF5V8CtuYQYb+VFpZ6Sg== + dependencies: + "@hapi/hoek" "9.x.x" + +"@hapi/wreck@17.x.x": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-17.1.0.tgz#fbdc380c6f3fa1f8052dc612b2d3b6ce3e88dbec" + integrity sha512-nx6sFyfqOpJ+EFrHX+XWwJAxs3ju4iHdbB/bwR8yTNZOiYmuhA8eCe7lYPtYmb4j7vyK/SlbaQsmTtUrMvPEBw== + dependencies: + "@hapi/boom" "9.x.x" + "@hapi/bourne" "2.x.x" + "@hapi/hoek" "9.x.x" + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + +"@sideway/address@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" + integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" + integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@types/json-schema@^7.0.6": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + +ajv@^8.0.1: + version "8.8.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.8.2.tgz#01b4fef2007a28bf75f0b7fc009f62679de4abbb" + integrity sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array.prototype.every@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/array.prototype.every/-/array.prototype.every-1.1.3.tgz#31f01b48e1160bc4b49ecab246bf7f765c6686f9" + integrity sha512-vWnriJI//SOMOWtXbU/VXhJ/InfnNHPF6BLKn5WfY8xXy+NWql0fUy20GO3sdqBhCAO+qw8S/E5nJiZX+QFdCA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + is-string "^1.0.7" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browserslist@^4.17.5: + version "4.19.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + dependencies: + caniuse-lite "^1.0.30001286" + electron-to-chromium "^1.4.17" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001286: + version "1.0.30001287" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001287.tgz#5fab6a46ab9e47146d5dd35abfe47beaf8073c71" + integrity sha512-4udbs9bc0hfNrcje++AxBuc6PfLNHwh3PO9kbwnfCQWyqtlzg3py0YgFu8jyRTTo85VAz4U+VLxSlID09vNtWA== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^2.7.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cross-spawn@^7.0.0, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +deep-equal@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9" + integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw== + dependencies: + call-bind "^1.0.0" + es-get-iterator "^1.1.1" + get-intrinsic "^1.0.1" + is-arguments "^1.0.4" + is-date-object "^1.0.2" + is-regex "^1.1.1" + isarray "^2.0.5" + object-is "^1.1.4" + object-keys "^1.1.1" + object.assign "^4.1.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.3" + which-boxed-primitive "^1.0.1" + which-collection "^1.0.1" + which-typed-array "^1.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +default-require-extensions@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.0.tgz#e03f93aac9b2b6443fc52e5e4a37b3ad9ad8df96" + integrity sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg== + dependencies: + strip-bom "^4.0.0" + +define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + +dotignore@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dotignore/-/dotignore-0.1.2.tgz#f942f2200d28c3a76fbdd6f0ee9f3257c8a2e905" + integrity sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw== + dependencies: + minimatch "^3.0.4" + +electron-to-chromium@^1.4.17: + version "1.4.20" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.20.tgz#8fbf9677ccac19b4249c0a6204e0943d9d66ce30" + integrity sha512-N7ZVNrdzX8NE90OXEFBMsBf3fp8P/vVDUER3WCUZjzC7OkNTXHVoF6W9qVhq8+dA8tGnbDajzUpj2ISNVVyj+Q== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +enjoi@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/enjoi/-/enjoi-9.0.1.tgz#b940c3e4bf243658c380783c014fe4e9bf91ff5d" + integrity sha512-yD2NGiD54egxlGkTWbtZST+FAdJ2XOQC6cWIblyL4zRFdCZ6YEhWHaMxKrQQCfUwUZ/xvdqyCVIltzH8n+U86Q== + dependencies: + "@hapi/bourne" "^2.0.0" + "@hapi/hoek" "^9.0.3" + +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + +es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.1" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.1" + is-string "^1.0.7" + is-weakref "^1.0.1" + object-inspect "^1.11.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-get-iterator@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" + integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.0" + has-symbols "^1.0.1" + is-arguments "^1.1.0" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.5" + isarray "^2.0.5" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7.32.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + 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.1.2" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + 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.9" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" + integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== + +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.6.0, globals@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" + integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== + dependencies: + type-fest "^0.20.2" + +graceful-fs@^4.1.15: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== + +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + +has-dynamic-import@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-dynamic-import/-/has-dynamic-import-2.0.1.tgz#9bca87846aa264f2ad224fcd014946f5e5182f52" + integrity sha512-X3fbtsZmwb6W7fJGR9o7x65fZoodygCrZ3TVycvghP62yYQfS0t4RS0Qcz+j5tQYUKeSWS09tHkWW6WhFV3XhQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-arguments@^1.0.4, is-arguments@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-core-module@^2.2.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== + dependencies: + has "^1.0.3" + +is-date-object@^1.0.1, is-date-object@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + +is-negative-zero@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-regex@^1.1.1, is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== + +is-shared-array-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typed-array@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" + integrity sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== + +is-weakref@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz#e1426514662244b2f25df728e8fd1ba35fe53b9c" + integrity sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.0" + istanbul-lib-coverage "^3.0.0-alpha.1" + make-dir "^3.0.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^3.3.3" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.1.tgz#7085857f17d2441053c6ce5c3b8fdf6882289397" + integrity sha512-q1kvhAXWSsXfMjCdNHNPKZZv94OlspKnoGv+R9RGbnqOOQ0VbNfLFgQDVgi7hHenKsndGq3/o0OBdzDXthWcNw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +joi@^17.1.0: + version "17.6.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.6.0.tgz#0bb54f2f006c09a96e75ce687957bd04290054b2" + integrity sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json5@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== + dependencies: + minimist "^1.2.5" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +merge-object-files-es6@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/merge-object-files-es6/-/merge-object-files-es6-1.0.5.tgz#ae6d75af67ab62a8e328b063601d8b8ce2da2ff2" + integrity sha512-B7KX3WzcqZukIY9kJePDNNGjoLpq2um8V03yaNmxF13x+VbCzRL8fd9ccSoorBwn2HK65I3hGF2iRu/4Sg6saw== + +mime-db@1.x.x: + version "1.51.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== + +nyc@^15.1.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" + integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^2.0.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + +object-inspect@^1.11.0, object-inspect@^1.12.0, object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + +object-is@^1.1.4, object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +openapi-types@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-10.0.0.tgz#0debbf663b2feed0322030b5b7c9080804076934" + integrity sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ== + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + 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.3" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +process-on-spawn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.0.0.tgz#95b05a23073d30a17acfdc92a440efd2baefdc93" + integrity sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg== + dependencies: + fromentries "^1.2.0" + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +regexp.prototype.flags@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" + integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA= + dependencies: + es6-error "^4.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + +resumer@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" + integrity sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k= + dependencies: + through "~2.3.4" + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.2.1: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.3, side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.2: + version "3.0.6" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" + integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +source-map@^0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trim@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz#a587bcc8bfad8cb9829a577f5de30dd170c1682c" + integrity sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +swagger-parser@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/swagger-parser/-/swagger-parser-10.0.3.tgz#04cb01c18c3ac192b41161c77f81e79309135d03" + integrity sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg== + dependencies: + "@apidevtools/swagger-parser" "10.0.3" + +table@^6.0.9: + version "6.7.5" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.5.tgz#f04478c351ef3d8c7904f0e8be90a1b62417d238" + integrity sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +tape@^5.0.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/tape/-/tape-5.5.2.tgz#3750c415e6ddfbcd425945f02f1a907d2ea8171f" + integrity sha512-N9Ss672dFE3QlppiXGh2ieux8Ophau/HSAQguW5cXQworKxV0QvnZCYI35W1OYySTJk0OC9OPuS+0xNO6lhiTQ== + dependencies: + array.prototype.every "^1.1.3" + call-bind "^1.0.2" + deep-equal "^2.0.5" + defined "^1.0.0" + dotignore "^0.1.2" + for-each "^0.3.3" + get-package-type "^0.1.0" + glob "^7.2.0" + has "^1.0.3" + has-dynamic-import "^2.0.1" + inherits "^2.0.4" + is-regex "^1.1.4" + minimist "^1.2.5" + object-inspect "^1.12.0" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.2" + resolve "^2.0.0-next.3" + resumer "^0.0.0" + string.prototype.trim "^1.2.5" + through "^2.3.8" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.8, through@~2.3.4: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +uuid@^3.3.3: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +validator@^13.7.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + +which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which-collection@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== + dependencies: + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + +which-typed-array@^1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" + integrity sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.7" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + +z-schema@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.2.tgz#f410394b2c9fcb9edaf6a7511491c0bb4e89a504" + integrity sha512-40TH47ukMHq5HrzkeVE40Ad7eIDKaRV2b+Qpi2prLc9X9eFJFzV7tMe5aH12e6avaSS/u5l653EQOv+J9PirPw== + dependencies: + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + validator "^13.7.0" + optionalDependencies: + commander "^2.7.1"