diff --git a/packages/cactus-cmd-socketio-server/package.json b/packages/cactus-cmd-socketio-server/package.json index 7cf35a09e74..0e40a0d23df 100644 --- a/packages/cactus-cmd-socketio-server/package.json +++ b/packages/cactus-cmd-socketio-server/package.json @@ -42,7 +42,8 @@ "@types/escape-html": "1.0.1", "@types/http-errors": "1.6.3", "@types/morgan": "1.9.1", - "@types/shelljs": "^0.8.11", + "@types/shelljs": "0.8.11", + "http-terminator": "3.2.0", "ts-node": "8.9.1" }, "publishConfig": { diff --git a/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BLP_config.ts b/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BLP_config.ts index c44e36e4a67..2eb3dbf3fb3 100644 --- a/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BLP_config.ts +++ b/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BLP_config.ts @@ -7,17 +7,24 @@ import { BusinessLogicPlugin } from "./BusinessLogicPlugin"; -let _blp: BusinessLogicPlugin | null = null; +// Singleton of BLPs +let _blpMapping = new Map(); export function getTargetBLPInstance( businessLogicID: string, ): BusinessLogicPlugin | null { - return _blp; + return _blpMapping.get(businessLogicID) ?? null; } export function setTargetBLPInstance( businessLogicID: string, blp: BusinessLogicPlugin, ) { - _blp = blp; -} \ No newline at end of file + _blpMapping.set(businessLogicID, blp); +} + +export function deleteTargetBLPInstance( + businessLogicID: string, +) { + return _blpMapping.delete(businessLogicID); +} diff --git a/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BusinessLogicBase.ts b/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BusinessLogicBase.ts index 4e3a21210da..1727958610e 100644 --- a/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BusinessLogicBase.ts +++ b/packages/cactus-cmd-socketio-server/src/main/typescript/business-logic-plugin/BusinessLogicBase.ts @@ -83,8 +83,10 @@ export class BusinessLogicBase implements BusinessLogicPlugin { return null; } + // This function is optional in setup with single BLP, + // that's why value true is returned (to indicate that given Tx always belong to this BLP) hasTxIDInTransactions(txID: string): boolean { // NOTE: This method implements the BisinessLogcPlugin operation(* Override by subclass) - return false; + return true; } } diff --git a/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/CactusSocketIOServer.ts b/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/CactusSocketIOServer.ts index 2fed089ecda..f4f3166f930 100644 --- a/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/CactusSocketIOServer.ts +++ b/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/CactusSocketIOServer.ts @@ -29,10 +29,17 @@ export type BLPConfig = { plugin: BusinessLogicPlugin; }; -export function startCactusSocketIOServer(blp?: BLPConfig) { +export function startCactusSocketIOServer(blp?: BLPConfig | BLPConfig[], onListening?: () => void) { if (blp) { - logger.info("Using BLP with id =", blp.id); - setTargetBLPInstance(blp.id, blp.plugin); + // Support both single and multiple BLPs + if(!Array.isArray(blp)) { + blp = [blp]; + } + + blp.forEach((cfg) => { + logger.info("Using BLP with id =", cfg.id); + setTargetBLPInstance(cfg.id, cfg.plugin); + }); } runExpressApp(); @@ -85,15 +92,16 @@ export function startCactusSocketIOServer(blp?: BLPConfig) { /** * Event listener for HTTP server "listening" event. */ - - function onListening(): void { - const addr = server.address(); - if (addr) { - const bind = - typeof addr === "string" ? "pipe " + addr : "port " + addr.port; - debugModule("Listening on " + bind); - } else { - throw new Error("Could not get server address!"); + if (!onListening) { + onListening = () => { + const addr = server.address(); + if (addr) { + const bind = + typeof addr === "string" ? "pipe " + addr : "port " + addr.port; + debugModule("Listening on " + bind); + } else { + throw new Error("Could not get server address!"); + } } } diff --git a/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/TransactionManagement.ts b/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/TransactionManagement.ts index e879ed6d83c..ced36df6226 100644 --- a/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/TransactionManagement.ts +++ b/packages/cactus-cmd-socketio-server/src/main/typescript/routing-interface/TransactionManagement.ts @@ -8,15 +8,10 @@ import { Request } from "express"; import { BusinessLogicPlugin } from "../business-logic-plugin/BusinessLogicPlugin"; import { BLPRegistry } from "./util/BLPRegistry"; -import { LPInfoHolder } from "./util/LPInfoHolder"; -import { json2str } from "../verifier/DriverCommon"; -import { Verifier } from "../verifier/Verifier"; import { IVerifierEventListener, LedgerEvent } from "../verifier/LedgerPlugin"; import { getTargetBLPInstance } from "../business-logic-plugin/BLP_config"; import { ConfigUtil } from "./util/ConfigUtil"; -const fs = require("fs"); -const path = require("path"); const config: any = ConfigUtil.getConfig(); import { getLogger } from "log4js"; const moduleName = "TransactionManagement"; @@ -117,22 +112,20 @@ export class TransactionManagement implements IVerifierEventListener { // object judgment let result: {} = {}; - if (businessLogicID === "h40Q9eMD") { - const blp = getTargetBLPInstance(businessLogicID); - if (blp === null) { - logger.warn( - `##startBusinessLogic(): not found BusinessLogicPlugin. businessLogicID: ${businessLogicID}`, - ); - return undefined; - } + const blp = getTargetBLPInstance(businessLogicID); + if (blp === null) { + logger.warn( + `##startBusinessLogic(): not found BusinessLogicPlugin. businessLogicID: ${businessLogicID}`, + ); + return undefined; + } - logger.debug("created instance"); + logger.debug("created instance"); - // Set business logic config - logger.debug(`meterParams = ${req.body.meterParams}`); - result = blp.setConfig(req.body.meterParams); - logger.debug("set business logic config"); - } + // Set business logic config + logger.debug(`meterParams = ${req.body.meterParams}`); + result = blp.setConfig(req.body.meterParams); + logger.debug("set business logic config"); return result; } @@ -271,12 +264,12 @@ export class TransactionManagement implements IVerifierEventListener { continue; } - // if (blp.hasTxIDInTransactions(txID)) { - logger.debug( - `####getBLPInstanceFromTxID(): found!, businessLogicID: ${businessLogicID}`, - ); - return blp; - // } + if (blp.hasTxIDInTransactions(txID)) { + logger.debug( + `####getBLPInstanceFromTxID(): found!, businessLogicID: ${businessLogicID}`, + ); + return blp; + } } // not found. diff --git a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/Verifier.test.ts b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/Verifier.test.ts index 5cdc1d9d7ca..655472285e8 100644 --- a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/Verifier.test.ts +++ b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/Verifier.test.ts @@ -63,7 +63,7 @@ import { LogLevelDesc, } from "@hyperledger/cactus-common"; -const logLevel: LogLevelDesc = "error"; +const logLevel: LogLevelDesc = "info"; const log: Logger = LoggerProvider.getOrCreate({ label: "test-cmd-socketio-verifier", level: logLevel, diff --git a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierAuthentication.test.ts b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierAuthentication.test.ts index 2273f267945..20c438b5a39 100644 --- a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierAuthentication.test.ts +++ b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierAuthentication.test.ts @@ -20,7 +20,7 @@ import { LogLevelDesc, } from "@hyperledger/cactus-common"; -const logLevel: LogLevelDesc = "error"; +const logLevel: LogLevelDesc = "info"; const log: Logger = LoggerProvider.getOrCreate({ label: "test-cmd-socketio-verifier", level: logLevel, diff --git a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierFactory.test.ts b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierFactory.test.ts index 178f65c1c1d..641588289e0 100644 --- a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierFactory.test.ts +++ b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/VerifierFactory.test.ts @@ -19,7 +19,7 @@ import { LogLevelDesc, } from "@hyperledger/cactus-common"; -const logLevel: LogLevelDesc = "error"; +const logLevel: LogLevelDesc = "info"; const log: Logger = LoggerProvider.getOrCreate({ label: "test-cmd-socketio-verifier", level: logLevel, diff --git a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/cmd-socketio-blp-plugin.test.ts b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/cmd-socketio-blp-plugin.test.ts new file mode 100644 index 00000000000..fd600748c87 --- /dev/null +++ b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/cmd-socketio-blp-plugin.test.ts @@ -0,0 +1,399 @@ +/** + * Tests to check the BLP framework functions included in this package. + * Client applications can use this framework when developing own client side apps (BLP). + */ + +// Contants +const testLogLevel: LogLevelDesc = "info"; +const sutLogLevel: LogLevelDesc = "info"; +const setupTimeout = 1000 * 60; // 1 minute timeout for setup +const firstServiceName = "firstBLPServiceID"; +const secondServiceName = "secondBLPServiceID"; + +/** + * Mock cmd-socketio-server configuration that will be used by the test. + * Normally, this would come from parsing config files in `/etc/cactus/` + */ +const mockAppConfig = { + blpRegistry: [ + { + businessLogicID: firstServiceName, + validatorID: [], + }, + { + businessLogicID: secondServiceName, + validatorID: [], + }, + ], + logLevel: sutLogLevel, + applicationHostInfo: { hostName: "0.0.0.0", hostPort: 0 }, + socketOptions: { + rejectUnauthorized: false, + reconnection: false, + timeout: 20000, + }, + verifier: { maxCounterRequestID: 100, syncFunctionTimeoutMillisecond: 5000 }, + appRouters: [], +}; + +// Must be mocked before loading cactus-cmd-socketio-server +import * as ConfigUtil from "../../../main/typescript/routing-interface/util/ConfigUtil"; +jest.mock("../../../main/typescript/routing-interface/util/ConfigUtil"); +(ConfigUtil as any)["__configMock"] = mockAppConfig; + +jest.mock("fs"); + +import { BusinessLogicBase } from "../../../main/typescript/business-logic-plugin/BusinessLogicBase"; +import { LedgerEvent } from "../../../main/typescript/verifier/LedgerPlugin"; +import { TransactionManagement } from "../../../main/typescript/routing-interface/TransactionManagement"; +import { startCactusSocketIOServer } from "../../../main/typescript/routing-interface/CactusSocketIOServer"; + +import { + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; + +import { createHttpTerminator } from "http-terminator"; +import { Request } from "express"; +import http from "http"; +import "jest-extended"; + +// Unit Test logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "cmd-socketio-blp-plugin.test", + level: testLogLevel, +}); + +////////////////////////////// +// Mock BLP Class +////////////////////////////// + +/** + * Returns predefined responses for certain operations that can be then validated in by the test suite. + * Performs some validations of the input parameters as well. + */ +export class TestBLPService extends BusinessLogicBase { + blpClassName: string; + + /** + * Mocked value of ledger transaction ID that belongs to this BLP logic. + * Assume it was received from another call to the ledger (`sendTransaction()` or something similar). + */ + txName: string; + + /** + * List of tradeID generated by the TransactionManagement component. + * Should correspond to each `startBusinessLogic()` call. + */ + transactions: string[] = []; + + /** + * List of received event IDs by `onEvent()` function. + */ + receivedEvents: string[] = []; + + constructor(public businessLogicID: string) { + super(); + this.blpClassName = this.constructor.name; + this.txName = `${businessLogicID}_TxID1`; + log.debug(`${this.blpClassName} of ${businessLogicID} created.`); + } + + /** + * Called after receiving startBusinessLogic request in TransactionManagement. + * Should register new trade start. + * + * @param req HTTP request that triggered this operation. + * @param businessLogicID should be equal to this plugin ID. + * @param tradeID New trade ID generated by TransactionManagement. + */ + startTransaction(req: Request, businessLogicID: string, tradeID: string) { + log.info( + `${this.blpClassName}: [startTransaction] businessLogicID: ${businessLogicID}, tradeID: ${tradeID}`, + ); + expect(req).toBeTruthy(); + expect(businessLogicID).toBeTruthy(); + expect(businessLogicID).toEqual(this.businessLogicID); + expect(tradeID).toBeTruthy(); + this.transactions.push(tradeID); + log.debug( + "Added new transaction (trade). Current transactions:", + this.transactions, + ); + } + + /** + * Should return status of the trade given in argument. + * + * @param tradeID Status we want to get. + * @returns response: true if routed to correct BLP, businessLogicID: this BLP id + */ + getOperationStatus(tradeID: string) { + log.info( + `${this.blpClassName}: [getOperationStatus] businessLogicID: ${this.businessLogicID}, tradeID: ${tradeID}`, + ); + expect(tradeID).toBeTruthy(); + const isIncluded = this.transactions.includes(tradeID); + expect(isIncluded).toBeTrue(); + return { response: isIncluded, businessLogicID: this.businessLogicID }; + } + + /** + * Should be used for BLP logic setup by the client. + * @param meterParams some BLP options. + * @returns meterParams: received in input, businessLogicID: this BLP id + */ + setConfig(meterParams: string[]): any { + log.info( + `${this.blpClassName}: [setConfig] businessLogicID: ${this.businessLogicID}, meterParams: ${meterParams}`, + ); + expect(meterParams).toBeTruthy(); + return { meterParams, businessLogicID: this.businessLogicID }; + } + + /** + * Used to receive events from the verifier. Events should be routed correctly based on responses + * from other methods in this BLP. Pushes new event to `receivedEvents`. + * + * Current flow of the event while being routed to the BLP (implemented in TransactionManagement, + * but described here for test setup clarity): + * 1. `getEventDataNum(ledgerEvent)` is called for each registered BLP, until one of them returns event count different from `0`. + * 2. `getTxIDFromEvent(ledgerEvent, targetIndex)` is called for each registered BLP, until one of them returns + * transaction id different from `null`. + * 3. `hasTxIDInTransactions(txID)` is called for each registered BLP, until one of them confirms it's + * an owner of given transaction. For single-BLP, this function can be ommited and will return true (for backward compatibility). + * 4. `onEvent(ledgerEvent, targetIndex)` - this function is called. + * + * @param ledgerEvent Event that belong to this BLP. + * @param targetIndex Index of the event (starting from `0`). + */ + onEvent(ledgerEvent: LedgerEvent, targetIndex: number): void { + log.info( + `${this.blpClassName}: [onEvent] businessLogicID: ${this.businessLogicID}, ledgerEvent: ${ledgerEvent}, targetIndex: ${targetIndex}`, + ); + expect(ledgerEvent).toBeTruthy(); + expect(targetIndex).toEqual(0); + expect(ledgerEvent.data).toEqual(this.businessLogicID); + this.receivedEvents.push(ledgerEvent.id); + log.debug("Added new event. Current event IDs:", this.receivedEvents); + } + + /** + * Should return number of events from given LedgerEvent. + * This test implementation always returns 1. + * + * @returns 1 + */ + getEventDataNum() { + log.info( + `${this.blpClassName}: [getEventDataNum] businessLogicID: ${this.businessLogicID}`, + ); + return 1; + } + + /** + * Should read ledger transaction id from given LedgerEvent. + * It returns either predefined `this.txName` if event matches this BLP, or `null` if not. + * @param ledgerEvent + * @returns + */ + getTxIDFromEvent(ledgerEvent: LedgerEvent) { + log.info( + `${this.blpClassName}: [getTxIDFromEvent] businessLogicID: ${this.businessLogicID}, ledgerEvent: ${ledgerEvent}`, + ); + + if (ledgerEvent.data === this.businessLogicID) { + log.debug("Event matches, return txName"); + return this.txName; + } else { + return null; + } + } + + /** + * Should return true if given ledger transaction belongs to this BLP. + * + * @param txID ledger transaction ID. + * @returns `true` if given txID matches predefined `this.txName`, `false` otherwise. + */ + hasTxIDInTransactions(txID: string) { + return txID === this.txName; + } +} + +////////////////////////////// +// Test Suite +////////////////////////////// + +describe("Test multiple Bussiness Logic Plugins (BLP) running as a single service", () => { + const firstService = new TestBLPService(firstServiceName); + const secondService = new TestBLPService(secondServiceName); + let transactionManagement: TransactionManagement; + let cmdServer: http.Server; + + ////////////////////////////// + // Setup + ////////////////////////////// + + beforeAll((done) => { + transactionManagement = new TransactionManagement(); + + const blpConfig = [ + { + id: firstServiceName, + plugin: firstService, + }, + { + id: secondServiceName, + plugin: secondService, + }, + ]; + log.info( + "Create CMD SocketIO Server with the following BLP config:", + blpConfig, + ); + + cmdServer = startCactusSocketIOServer(blpConfig, () => { + expect(cmdServer).toBeTruthy(); + const addr = cmdServer.address(); + expect(addr).toBeTruthy(); + const bind = + typeof addr === "string" ? "pipe " + addr : "port " + addr!.port; + log.info("Test CMD SocketIO Server started at", bind); + done(); + }); + }, setupTimeout); + + afterAll(async () => { + if (cmdServer) { + log.info("Stop CMD SocketIO Server"); + const httpTerminator = createHttpTerminator({ + server: cmdServer, + }); + await httpTerminator.terminate(); + } + + log.info("Cleanup done."); + }, setupTimeout); + + ////////////////////////////// + // Tests + ////////////////////////////// + + test("Start bussiness logic creates new trade and calls correct BLP", () => { + // First BLP + const reqMockFirst = { + body: { + businessLogicID: firstServiceName, + }, + }; + const firstTradeID = transactionManagement.startBusinessLogic( + reqMockFirst as any, + ); + expect(firstTradeID).toBeTruthy(); + expect(firstService.transactions).toInclude(firstTradeID as string); + + // Second BLP + const reqMockSecond = { + body: { + businessLogicID: secondServiceName, + }, + }; + const secondTradeID = transactionManagement.startBusinessLogic( + reqMockSecond as any, + ); + expect(secondTradeID).toBeTruthy(); + expect(secondService.transactions).toInclude(secondTradeID as string); + }); + + test("Calling getOperationStatus gets status from correct BLP", () => { + // First BLP + const reqMockFirst = { + body: { + businessLogicID: firstServiceName, + }, + }; + const firstTradeID = transactionManagement.startBusinessLogic( + reqMockFirst as any, + ) as any; + const firstTradeStatus = transactionManagement.getOperationStatus( + firstTradeID, + ) as any; + expect(firstTradeStatus).toBeTruthy(); + expect(firstTradeStatus.businessLogicID).toEqual(firstServiceName); + expect(firstTradeStatus.response).toBeTrue(); + + // Second BLP + const reqMockSecond = { + body: { + businessLogicID: secondServiceName, + }, + }; + const secondTradeID = transactionManagement.startBusinessLogic( + reqMockSecond as any, + ) as any; + const secondTradeStatus = transactionManagement.getOperationStatus( + secondTradeID, + ) as any; + expect(secondTradeStatus).toBeTruthy(); + expect(secondTradeStatus.businessLogicID).toEqual(secondServiceName); + expect(secondTradeStatus.response).toBeTrue(); + }); + + test("Calling setBusinessLogicConfig sends request to correct BLP", () => { + // First BLP + const firstMeterParams = ["a", "b", "c"]; + const reqMockFirst = { + body: { + businessLogicID: firstServiceName, + meterParams: firstMeterParams, + }, + }; + const firstSetResponse = transactionManagement.setBusinessLogicConfig( + reqMockFirst as any, + ) as any; + expect(firstSetResponse).toBeTruthy(); + expect(firstSetResponse.businessLogicID).toEqual(firstServiceName); + expect(firstSetResponse.meterParams).toEqual(firstMeterParams); + + // Second BLP + const secondMeterParams = ["x", "y", "z"]; + const reqMockSecond = { + body: { + businessLogicID: secondServiceName, + meterParams: secondMeterParams, + }, + }; + const secondSetResponse = transactionManagement.setBusinessLogicConfig( + reqMockSecond as any, + ) as any; + expect(secondSetResponse).toBeTruthy(); + expect(secondSetResponse.businessLogicID).toEqual(secondServiceName); + expect(secondSetResponse.meterParams).toEqual(secondMeterParams); + }); + + test("Events are routed to the correct BLP", () => { + // First BLP + const firstEventId = "someEventForFirstService"; + const firstLedgerEvent = { + id: firstEventId, + verifierId: "someVerifier", + data: firstServiceName, + }; + transactionManagement.onEvent(firstLedgerEvent); + expect(firstService.receivedEvents.length).toBeGreaterThan(0); + expect(firstService.receivedEvents[0]).toEqual(firstEventId); + + // Second BLP + const secondEventId = "someEventForSecondService"; + const secondLedgerEvent = { + id: secondEventId, + verifierId: "someVerifier", + data: secondServiceName, + }; + transactionManagement.onEvent(secondLedgerEvent); + expect(secondService.receivedEvents.length).toBeGreaterThan(0); + expect(secondService.receivedEvents[0]).toEqual(secondEventId); + }); +}); diff --git a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/validator-authentication.test.ts b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/validator-authentication.test.ts index 51265161c11..59f44de13b2 100644 --- a/packages/cactus-cmd-socketio-server/src/test/typescript/unit/validator-authentication.test.ts +++ b/packages/cactus-cmd-socketio-server/src/test/typescript/unit/validator-authentication.test.ts @@ -17,7 +17,7 @@ import { LogLevelDesc, } from "@hyperledger/cactus-common"; -const logLevel: LogLevelDesc = "error"; +const logLevel: LogLevelDesc = "info"; const log: Logger = LoggerProvider.getOrCreate({ label: "test-cmd-socketio-verifier", level: logLevel, @@ -36,7 +36,7 @@ const mockConfig = { certValue: publicKey, jwtAlgo: "ES256", }, - logLevel: "info", + logLevel: logLevel, }; jest.mock("config"); diff --git a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json index 5128820086c..e287feb7f33 100644 --- a/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json +++ b/packages/cactus-plugin-ledger-connector-go-ethereum-socketio/package.json @@ -28,7 +28,8 @@ "serve-favicon": "2.4.5", "shelljs": "0.8.5", "socket.io": "4.4.1", - "web3": "1.8.1" + "web3": "1.8.1", + "http-errors": "1.6.3" }, "devDependencies": { "@hyperledger/cactus-test-tooling": "1.1.2", diff --git a/yarn.lock b/yarn.lock index 8a612766d07..57677348463 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5617,7 +5617,7 @@ ajv@8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6: +ajv@^6.10.0, ajv@^6.11.0, ajv@^6.12.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.12.6: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -6820,7 +6820,7 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boolean@^3.0.1: +boolean@^3.0.1, boolean@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== @@ -9206,6 +9206,11 @@ del@^6.0.0: rimraf "^3.0.2" slash "^3.0.0" +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -11135,12 +11140,29 @@ fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@2.x, fast-json-stab resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-json-stringify@^2.7.10: + version "2.7.13" + resolved "https://registry.yarnpkg.com/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz#277aa86c2acba4d9851bd6108ed657aa327ed8c0" + integrity sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA== + dependencies: + ajv "^6.11.0" + deepmerge "^4.2.2" + rfdc "^1.2.0" + string-similarity "^4.0.1" + fast-levenshtein@^2.0.6, 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= -fast-safe-stringify@2.1.1: +fast-printf@^1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/fast-printf/-/fast-printf-1.6.9.tgz#212f56570d2dc8ccdd057ee93d50dd414d07d676" + integrity sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg== + dependencies: + boolean "^3.1.4" + +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -11977,6 +11999,13 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" +globalthis@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + globby@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.0.tgz#abfcd0630037ae174a88590132c2f6804e291072" @@ -12729,6 +12758,16 @@ http-status-codes@2.1.4: resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.1.4.tgz#453d99b4bd9424254c4f6a9a3a03715923052798" integrity sha512-MZVIsLKGVOVE1KEnldppe6Ij+vmemMuApDfjhVSLzyYP+td0bREEYyAoIw9yFePoBXManCuBqmiNP5FqJS5Xkg== +http-terminator@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/http-terminator/-/http-terminator-3.2.0.tgz#bc158d2694b733ca4fbf22a35065a81a609fb3e9" + integrity sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g== + dependencies: + delay "^5.0.0" + p-wait-for "^3.2.0" + roarr "^7.0.4" + type-fest "^2.3.3" + http2-wrapper@^1.0.0-beta.5.2: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" @@ -17732,7 +17771,7 @@ p-timeout@^1.1.1: dependencies: p-finally "^1.0.0" -p-timeout@^3.1.0, p-timeout@^3.2.0: +p-timeout@^3.0.0, p-timeout@^3.1.0, p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== @@ -17749,6 +17788,13 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +p-wait-for@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-wait-for/-/p-wait-for-3.2.0.tgz#640429bcabf3b0dd9f492c31539c5718cb6a3f1f" + integrity sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA== + dependencies: + p-timeout "^3.0.0" + package-hash@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" @@ -19622,7 +19668,7 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rfdc@^1.3.0: +rfdc@^1.2.0, rfdc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== @@ -19682,6 +19728,18 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" +roarr@^7.0.4: + version "7.11.0" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-7.11.0.tgz#010a0ef39bbb8317fd28fcfb337cb8a3e56f3de2" + integrity sha512-DKiMaEYHoOZ0JyD4Ohr5KRnqybQ162s3ZL/WNO9oy6EUszYvpp0eLYJErc/U4NI96HYnHsbROhFaH4LYuJPnDg== + dependencies: + boolean "^3.1.4" + fast-json-stringify "^2.7.10" + fast-printf "^1.6.9" + fast-safe-stringify "^2.1.1" + globalthis "^1.0.2" + semver-compare "^1.0.0" + run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -21027,6 +21085,11 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-similarity@^4.0.1: + version "4.0.4" + resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" + integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -22157,6 +22220,11 @@ type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +type-fest@^2.3.3: + version "2.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.13.1.tgz#621c84220df0e01a8469002594fc005714f0cfba" + integrity sha512-hXYyrPFwETT2swFLHeoKtJrvSF/ftG/sA15/8nGaLuaDGfVAaq8DYFpu4yOyV4tzp082WqnTEoMsm3flKMI2FQ== + type-is@^1.6.4, type-is@~1.6.15, type-is@~1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"