1
0
Fork 0
mirror of synced 2024-06-13 16:05:06 +12:00

Add type hierarchy for importers

This commit is contained in:
Rory Powell 2021-12-01 09:48:52 +00:00
parent d136824898
commit 868a7dace3
13 changed files with 547 additions and 345 deletions

View file

@ -68,6 +68,7 @@
"author": "Budibase",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@apidevtools/swagger-parser": "^10.0.3",
"@budibase/auth": "^0.9.185-alpha.21",
"@budibase/client": "^0.9.185-alpha.21",
"@budibase/string-templates": "^0.9.185-alpha.21",
@ -77,7 +78,6 @@
"@koa/router": "8.0.0",
"@sendgrid/mail": "7.1.1",
"@sentry/node": "^6.0.0",
"@types/swagger-schema-official": "^2.0.22",
"airtable": "0.10.1",
"arangojs": "7.2.0",
"aws-sdk": "^2.767.0",
@ -109,6 +109,7 @@
"mysql2": "^2.3.1",
"node-fetch": "2.6.0",
"open": "7.3.0",
"openapi-types": "^9.3.1",
"pg": "8.5.1",
"pino-pretty": "4.0.0",
"posthog-node": "^1.1.4",

View file

@ -1,334 +0,0 @@
import CouchDB from "../../../db"
import { queryValidation } from "./validation"
import { generateQueryID } from "../../../db/utils"
import { Spec as Swagger2, Operation } from "swagger-schema-official"
const curlconverter = require("curlconverter")
import { URL } from "url"
// {
// "_id": "query_datasource_d62738f2d72a466997ffbf46f4952404_e7258ad382cd4c37961b81730633ff2d",
// "_rev": "1-e702a18eaa96c7cb4be1b402c34eaa59",
// "datasourceId": "datasource_d62738f2d72a466997ffbf46f4952404",
// "parameters": [
// {
// "name": "paramtest",
// "default": "defaultValue"
// }
// ],
// "fields": {
// "headers": {
// "headertest": "test"
// },
// "queryString": "query=test",
// "path": "/path/test"
// },
// "queryVerb": "read",
// "transformer": "return data.test",
// "schema": {},
// "name": "name",
// "readable": true
// }
// return joiValidator.body(Joi.object({
// _id: Joi.string(),
// _rev: Joi.string(),
// name: Joi.string().required(),
// fields: Joi.object().required(),
// datasourceId: Joi.string().required(),
// readable: Joi.boolean(),
// parameters: Joi.array().items(Joi.object({
// name: Joi.string(),
// default: Joi.string().allow(""),
// })),
// queryVerb: Joi.string().allow().required(),
// extra: Joi.object().optional(),
// schema: Joi.object({}).required().unknown(true),
// transformer: Joi.string().optional(),
// }))
interface Parameter {
name: string
default: string
}
interface Query {
_id?: string
datasourceId: string
name: string
parameters: Parameter[]
fields: {
headers: any
queryString: string
path: string
}
transformer: string | null
schema: any
readable: boolean
queryVerb: string
}
enum Strategy {
SWAGGER2,
OPENAPI3,
CURL,
}
enum MethodToVerb {
get = "read",
post = "create",
put = "update",
patch = "patch",
delete = "delete",
}
interface ImportResult {
errorQueries: Query[]
}
interface DatasourceInfo {
url: string
name: string
defaultHeaders: any[]
}
const parseImportStrategy = (data: string): Strategy => {
try {
const json = JSON.parse(data)
if (json.swagger === "2.0") {
return Strategy.CURL
} else if (json.openapi?.includes("3.0")) {
return Strategy.OPENAPI3
}
} catch (jsonError) {
try {
parseCurl(data)
return Strategy.CURL
} catch (curlError) {
// do nothing
}
}
throw new Error(`The import data could not be processed`)
}
const processPath = (path: string): string => {
if (path?.startsWith("/")) {
return path.substring(1)
}
return path
}
// SWAGGER
const parseSwagger2Info = (swagger2: Swagger2): DatasourceInfo => {
const scheme = swagger2.schemes?.includes("https") ? "https" : "http"
const basePath = swagger2.basePath || ""
const host = swagger2.host || "<host>"
const url = `${scheme}://${host}${basePath}`
const name = swagger2.info.title || "Swagger Import"
return {
url: url,
name: name,
defaultHeaders: [],
}
}
const parseSwagger2Queries = (
datasourceId: string,
swagger2: Swagger2
): Query[] => {
const queries = []
for (let [pathName, path] of Object.entries(swagger2.paths)) {
for (let [methodName, op] of Object.entries(path)) {
let operation = op as Operation
const name = operation.operationId || pathName
const queryString = ""
const headers = {}
const parameters: Parameter[] = []
const query = constructQuery(
datasourceId,
name,
methodName,
pathName,
queryString,
headers,
parameters
)
queries.push(query)
}
}
return queries
}
// OPEN API
const parseOpenAPI3Info = (data: any): DatasourceInfo => {
return {
url: "http://localhost:3000",
name: "swagger",
defaultHeaders: [],
}
}
const parseOpenAPI3Queries = (datasourceId: string, data: string): Query[] => {
return []
}
// CURL
const parseCurl = (data: string): any => {
const curlJson = curlconverter.toJsonString(data)
return JSON.parse(curlJson)
}
const parseCurlDatasourceInfo = (data: any): DatasourceInfo => {
const curl = parseCurl(data)
const url = new URL(curl.url)
return {
url: url.origin,
name: url.hostname,
defaultHeaders: [],
}
}
const parseCurlQueries = (datasourceId: string, data: string): Query[] => {
const curl = parseCurl(data)
const url = new URL(curl.url)
const name = url.pathname
const path = url.pathname
const method = curl.method
const queryString = url.search
const headers = curl.headers
const query = constructQuery(
datasourceId,
name,
method,
path,
queryString,
headers
)
return [query]
}
const verbFromMethod = (method: string) => {
const verb = (<any>MethodToVerb)[method]
if (!verb) {
throw new Error(`Unsupported method: ${method}`)
}
return verb
}
const constructQuery = (
datasourceId: string,
name: string,
method: string,
path: string,
queryString: string,
headers: any = {},
parameters: Parameter[] = []
): Query => {
const readable = true
const queryVerb = verbFromMethod(method)
const transformer = "return data"
const schema = {}
path = processPath(path)
const query: Query = {
datasourceId,
name,
parameters,
fields: {
headers,
queryString,
path,
},
transformer,
schema,
readable,
queryVerb,
}
return query
}
export const getDatasourceInfo = (data: string): DatasourceInfo => {
const strategy = parseImportStrategy(data)
let info: DatasourceInfo
switch (strategy) {
case Strategy.SWAGGER2:
info = parseSwagger2Info(JSON.parse(data))
break
case Strategy.OPENAPI3:
info = parseOpenAPI3Info(JSON.parse(data))
break
case Strategy.CURL:
info = parseCurlDatasourceInfo(data)
break
}
return info
}
export const importQueries = async (
appId: string,
datasourceId: string,
data: string
): Promise<ImportResult> => {
const strategy = parseImportStrategy(data)
// constuct the queries
let queries: Query[]
switch (strategy) {
case Strategy.SWAGGER2:
queries = parseSwagger2Queries(datasourceId, JSON.parse(data))
break
case Strategy.OPENAPI3:
queries = parseOpenAPI3Queries(datasourceId, JSON.parse(data))
break
case Strategy.CURL:
queries = parseCurlQueries(datasourceId, data)
break
}
// validate queries
const errorQueries = []
const schema = queryValidation()
queries = queries
.filter(query => {
const validation = schema.validate(query)
if (validation.error) {
errorQueries.push(query)
return false
}
return true
})
.map(query => {
query._id = generateQueryID(query.datasourceId)
return query
})
// persist queries
const db = new CouchDB(appId)
for (const query of queries) {
try {
await db.put(query)
} catch (error) {
errorQueries.push(query)
}
}
return {
errorQueries,
}
}

View file

@ -0,0 +1,78 @@
import CouchDB from "../../../../db"
import { queryValidation } from "../validation"
import { generateQueryID } from "../../../../db/utils"
import { Query, ImportInfo, ImportSource } from "./sources/base"
import { OpenAPI2 } from "./sources/openapi2"
import { OpenAPI3 } from "./sources/openapi3"
import { Curl } from "./sources/curl"
interface ImportResult {
errorQueries: Query[]
}
export class RestImporter {
data: string
sources: ImportSource[]
source!: ImportSource
constructor(data: string) {
this.data = data
this.sources = [new OpenAPI2(), new OpenAPI3(), new Curl()]
}
init = async () => {
for (let source of this.sources) {
if (await source.isSupported(this.data)){
this.source = source
break
}
}
}
getInfo = async (): Promise<ImportInfo> => {
return this.source.getInfo()
}
importQueries = async (
appId: string,
datasourceId: string,
): Promise<ImportResult> => {
// constuct the queries
let queries = await this.source.getQueries(datasourceId)
// validate queries
const errorQueries = []
const schema = queryValidation()
queries = queries
.filter(query => {
const validation = schema.validate(query)
if (validation.error) {
errorQueries.push(query)
return false
}
return true
})
.map(query => {
query._id = generateQueryID(query.datasourceId)
return query
})
// persist queries
const db = new CouchDB(appId)
for (const query of queries) {
try {
await db.put(query)
} catch (error) {
errorQueries.push(query)
}
}
return {
errorQueries,
}
}
}

View file

@ -0,0 +1,92 @@
export interface ImportInfo {
url: string
name: string
}
export interface QueryParameter {
name: string
default: string
}
export interface Query {
_id?: string
datasourceId: string
name: string
parameters: QueryParameter[]
fields: {
headers: object
queryString: string | null
path: string
requestBody?: object
}
transformer: string | null
schema: any
readable: boolean
queryVerb: string
}
enum MethodToVerb {
get = "read",
post = "create",
put = "update",
patch = "patch",
delete = "delete",
}
export abstract class ImportSource {
abstract isSupported(data: string): Promise<boolean>
abstract getInfo(): Promise<ImportInfo>
abstract getQueries(datasourceId: string): Promise<Query[]>
constructQuery = (
datasourceId: string,
name: string,
method: string,
path: string,
queryString: string,
headers: object = {},
parameters: QueryParameter[] = [],
requestBody: object | undefined = undefined,
): Query => {
const readable = true
const queryVerb = this.verbFromMethod(method)
const transformer = "return data"
const schema = {}
path = this.processPath(path)
const query: Query = {
datasourceId,
name,
parameters,
fields: {
headers,
queryString,
path,
requestBody
},
transformer,
schema,
readable,
queryVerb,
}
return query
}
verbFromMethod = (method: string) => {
const verb = (<any>MethodToVerb)[method]
if (!verb) {
throw new Error(`Unsupported method: ${method}`)
}
return verb
}
processPath = (path: string): string => {
if (path?.startsWith("/")) {
return path.substring(1)
}
return path
}
}

View file

@ -0,0 +1,13 @@
import { ImportSource } from "."
import SwaggerParser from "@apidevtools/swagger-parser";
import { OpenAPI } from "openapi-types";
export abstract class OpenAPISource extends ImportSource {
parseData = async (data: string): Promise<OpenAPI.Document> => {
const json = JSON.parse(data)
return SwaggerParser.validate(json, {})
}
}

View file

@ -0,0 +1,54 @@
import { ImportSource, ImportInfo, Query } from "./base"
import { URL } from 'url'
const curlconverter = require("curlconverter")
const parseCurl = (data: string): any => {
const curlJson = curlconverter.toJsonString(data)
return JSON.parse(curlJson)
}
/**
* Curl
* https://curl.se/docs/manpage.html
*/
export class Curl extends ImportSource {
curl: any
isSupported = async (data: string): Promise<boolean> => {
try {
const curl = parseCurl(data)
this.curl = curl
} catch (err) {
return false
}
return true
}
getInfo = async (): Promise<ImportInfo> => {
const url = new URL(this.curl.url)
return {
url: url.origin,
name: url.hostname,
}
}
getQueries = async (datasourceId: string): Promise<Query[]> => {
const url = new URL(this.curl.url)
const name = url.pathname
const path = url.pathname
const method = this.curl.method
const queryString = url.search
const headers = this.curl.headers
const query = this.constructQuery(
datasourceId,
name,
method,
path,
queryString,
headers
)
return [query]
}
}

View file

@ -0,0 +1,104 @@
import { ImportInfo, QueryParameter, Query } from "./base"
import { OpenAPIV2 } from "openapi-types"
import { OpenAPISource } from "./base/openapi";
const isBodyParameter = (param: OpenAPIV2.Parameter): param is OpenAPIV2.InBodyParameterObject => {
return param.in === "body"
}
const isParameter = (param: OpenAPIV2.Parameter | OpenAPIV2.ReferenceObject): param is OpenAPIV2.Parameter => {
// we can guarantee this is not a reference object
// due to the deferencing done by the parser library
return true
}
const isOpenAPI2 = (document: any): document is OpenAPIV2.Document => {
if (document.swagger === "2.0") {
return true
} else {
return false
}
}
/**
* OpenAPI Version 2.0 - aka "Swagger"
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/2.0.md
*/
export class OpenAPI2 extends OpenAPISource {
document!: OpenAPIV2.Document
isSupported = async (data: string): Promise<boolean> => {
try {
const document: any = await this.parseData(data)
if (isOpenAPI2(document)) {
this.document = document
return true
} else {
return false
}
} catch (err) {
return false
}
}
getInfo = async (): Promise<ImportInfo> => {
const scheme = this.document.schemes?.includes("https") ? "https" : "http"
const basePath = this.document.basePath || ""
const host = this.document.host || "<host>"
const url = `${scheme}://${host}${basePath}`
const name = this.document.info.title || "Swagger Import"
return {
url: url,
name: name,
}
}
getQueries = async (datasourceId: string): Promise<Query[]> => {
const queries = []
let pathName: string
let path: OpenAPIV2.PathItemObject
for ([pathName, path] of Object.entries(this.document.paths)) {
for (let [methodName, op] of Object.entries(path)) {
let operation = op as OpenAPIV2.OperationObject
const name = operation.operationId || pathName
const queryString = ""
const headers = {}
let requestBody = undefined
const parameters: QueryParameter[] = []
if (operation.parameters) {
for (let param of operation.parameters) {
if (isParameter(param)) {
if (isBodyParameter(param)) {
requestBody = {}
} else {
parameters.push({
name: param.name,
default: "",
})
}
}
}
}
const query = this.constructQuery(
datasourceId,
name,
methodName,
pathName,
queryString,
headers,
parameters,
requestBody
)
queries.push(query)
}
}
return queries
}
}

View file

@ -0,0 +1,40 @@
import { ImportInfo, Query } from "./base"
import { OpenAPISource } from "./base/openapi"
import { OpenAPIV3 } from "openapi-types"
const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => {
return document.openapi === "3.0.0"
}
/**
* OpenAPI Version 3.0.0
* https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md
*/
export class OpenAPI3 extends OpenAPISource {
document!: OpenAPIV3.Document
isSupported = async (data: string): Promise<boolean> => {
try {
const document: any = await this.parseData(data)
if (isOpenAPI3(document)) {
this.document = document
return true
} else {
return false
}
} catch (err) {
return false
}
}
getInfo = async (): Promise<ImportInfo> => {
return {
url: "http://localhost:3000",
name: "swagger",
}
}
getQueries = async (datasourceId: string): Promise<Query[]> => {
return []
}
}

View file

@ -0,0 +1,71 @@
// const Airtable = require("airtable")
// const AirtableIntegration = require("../airtable")
jest.mock("airtable")
// class TestConfiguration {
// constructor(config = {}) {
// this.integration = new AirtableIntegration.integration(config)
// this.client = {
// create: jest.fn(),
// select: jest.fn(),
// update: jest.fn(),
// destroy: jest.fn(),
// }
// this.integration.client = () => this.client
// }
// }
describe("Airtable Integration", () => {
let config
beforeEach(() => {
config = new TestConfiguration()
})
it("calls the create method with the correct params", async () => {
const response = await config.integration.create({
table: "test",
json: {}
})
expect(config.client.create).toHaveBeenCalledWith([
{
fields: {}
}
])
})
it("calls the read method with the correct params", async () => {
const response = await config.integration.read({
table: "test",
view: "Grid view"
})
expect(config.client.select).toHaveBeenCalledWith({
maxRecords: 10, view: "Grid view"
})
})
it("calls the update method with the correct params", async () => {
const response = await config.integration.update({
table: "test",
id: "123",
json: {
name: "test"
}
})
expect(config.client.update).toHaveBeenCalledWith([
{
id: "123",
fields: { name: "test" }
}
])
})
it("calls the delete method with the correct params", async () => {
const ids = [1,2,3,4]
const response = await config.integration.delete({
ids
})
expect(config.client.destroy).toHaveBeenCalledWith(ids)
})
})

View file

@ -4,8 +4,8 @@ const { generateQueryID, getQueryParams } = require("../../../db/utils")
const { BaseQueryVerbs } = require("../../../constants")
const env = require("../../../environment")
const { Thread, ThreadType } = require("../../../threads")
const { importQueries, getDatasourceInfo } = require("./import")
const { save: saveDatasource } = require("../datasource")
const { RestImporter } = require("./import")
const Runner = new Thread(ThreadType.QUERY, { timeoutMs: 10000 })
@ -37,16 +37,19 @@ exports.import = async ctx => {
const body = ctx.request.body
const data = body.data
const importer = new RestImporter(data)
await importer.init()
let datasourceId
if (!body.datasourceId) {
// construct new datasource
const info = getDatasourceInfo(data)
const info = await importer.getInfo()
let datasource = {
type: "datasource",
source: "REST",
config: {
url: info.url,
defaultHeaders: info.defaultHeaders,
defaultHeaders: [],
},
name: info.name,
}
@ -60,7 +63,7 @@ exports.import = async ctx => {
datasourceId = body.datasourceId
}
const importResult = await importQueries(ctx.appId, datasourceId, data)
const importResult = await importer.importQueries(ctx.appId, datasourceId)
ctx.body = {
...importResult,

View file

@ -7,6 +7,38 @@
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.2.1.tgz#7e2cb3fcfb5c8b12d7275afafbb6ec44913551b4"
integrity sha512-uVgekyBXnOVkxp+CUssjN/gefARtudZC8duEn1vm0lBQFwGRZFlDEzU1QC+aIRWCrD1Z8OgRpmBYlSZ7QS003w==
"@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"
"@azure/abort-controller@^1.0.0":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.0.4.tgz#fd3c4d46c8ed67aace42498c8e2270960250eafd"
@ -1826,6 +1858,11 @@
"@babel/runtime" "^7.7.2"
regenerator-runtime "^0.13.3"
"@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==
"@koa/router@8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@koa/router/-/router-8.0.0.tgz#fd4ffa6f03d8293a04c023cb4a22b612401fbe70"
@ -2393,6 +2430,11 @@
jest-diff "^26.0.0"
pretty-format "^26.0.0"
"@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==
"@types/keygrip@*":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
@ -2500,11 +2542,6 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
"@types/swagger-schema-official@^2.0.22":
version "2.0.22"
resolved "https://registry.yarnpkg.com/@types/swagger-schema-official/-/swagger-schema-official-2.0.22.tgz#f7e06168e6994574dfd86928ac04b196870ab043"
integrity sha512-7yQiX6MWSFSvc/1wW5smJMZTZ4fHOd+hqLr3qr/HONDxHEa2bnYAsOcGBOEqFIjd4yetwMOdEDdeW+udRAQnHA==
"@types/yargs-parser@*":
version "20.2.1"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
@ -2865,6 +2902,11 @@ argparse@^1.0.10, argparse@^1.0.7:
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==
args@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761"
@ -3535,6 +3577,11 @@ call-bind@^1.0.0, call-bind@^1.0.2:
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"
@ -3787,7 +3834,7 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.19.0, commander@^2.5.0, commander@^2.8.1:
commander@^2.19.0, commander@^2.5.0, commander@^2.7.1, commander@^2.8.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==
@ -7418,6 +7465,13 @@ js-yaml@^3.13.1:
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"
jsbi@^3.1.1:
version "3.2.5"
resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-3.2.5.tgz#b37bb90e0e5c2814c1c2a1bcd8c729888a2e37d6"
@ -8096,6 +8150,11 @@ lodash.flatten@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
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.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
@ -8913,6 +8972,11 @@ open@7.3.0:
is-docker "^2.0.0"
is-wsl "^2.1.1"
openapi-types@^9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-9.3.1.tgz#617ae6db3efd3e3f68e849f65ced58801d01d3cf"
integrity sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==
opencollective-postinstall@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259"
@ -11887,6 +11951,11 @@ validate.js@0.13.1:
resolved "https://registry.yarnpkg.com/validate.js/-/validate.js-0.13.1.tgz#b58bfac04a0f600a340f62e5227e70d95971e92a"
integrity sha512-PnFM3xiZ+kYmLyTiMgTYmU7ZHkjBZz2/+F0DaALc/uUtVzdCt1wAosvYJ5hFQi/hz8O4zb52FQhHZRC+uVkJ+g==
validator@^13.7.0:
version "13.7.0"
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
vary@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@ -12320,6 +12389,17 @@ yn@3.1.1:
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
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"
zlib@1.0.5, zlib@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0"