From 2176577bebda3b6e3a3f6027f4f0ebe2974af86a Mon Sep 17 00:00:00 2001 From: Artem Zakharchenko Date: Mon, 30 May 2022 16:47:32 +0200 Subject: [PATCH] feat: upgrade to @mswjs/interceptors 0.16 (#1262) fix: set "response.url" on the mocked responses fix: prevent "EAI_AGAIN" from halting requests --- package.json | 4 +- src/context/fetch.ts | 2 +- src/native/index.ts | 4 +- src/node/createSetupServer.ts | 57 ++++++---- src/node/glossary.ts | 4 +- src/node/setupServer.ts | 8 +- src/setupWorker/glossary.ts | 6 +- src/setupWorker/stop/createFallbackStop.ts | 2 +- src/utils/request/parseIsomorphicRequest.ts | 2 +- .../worker/createFallbackRequestListener.ts | 91 +++++++++------- test/graphql-api/mutation.test.ts | 81 +++++++++----- test/graphql-api/query.test.ts | 102 +++++++++++------- test/jest.config.js | 2 +- .../scenarios/response-patching.test.ts | 89 ++++++++------- tsup.config.ts | 1 + yarn.lock | 17 ++- 16 files changed, 280 insertions(+), 192 deletions(-) diff --git a/package.json b/package.json index 16185c3e5..3e580aee5 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "sideEffects": false, "dependencies": { "@mswjs/cookies": "^0.2.0", - "@mswjs/interceptors": "^0.15.1", + "@mswjs/interceptors": "^0.16.3", "@open-draft/until": "^1.0.3", "@types/cookie": "^0.4.1", "@types/js-levenshtein": "^1.1.1", @@ -147,4 +147,4 @@ "path": "./node_modules/cz-conventional-changelog" } } -} \ No newline at end of file +} diff --git a/src/context/fetch.ts b/src/context/fetch.ts index 8597fb146..0e5bb6c70 100644 --- a/src/context/fetch.ts +++ b/src/context/fetch.ts @@ -1,6 +1,6 @@ import { isNodeProcess } from 'is-node-process' import { Headers } from 'headers-polyfill' -import { MockedRequest } from '../handlers/RequestHandler' +import type { MockedRequest } from '../handlers/RequestHandler' const useFetch: (input: RequestInfo, init?: RequestInit) => Promise = isNodeProcess() ? require('node-fetch') : window.fetch diff --git a/src/native/index.ts b/src/native/index.ts index e9b73bf63..d6504aae7 100644 --- a/src/native/index.ts +++ b/src/native/index.ts @@ -1,6 +1,6 @@ -import { interceptXMLHttpRequest } from '@mswjs/interceptors/lib/interceptors/XMLHttpRequest' +import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/lib/interceptors/XMLHttpRequest' import { createSetupServer } from '../node/createSetupServer' // Provision request interception via patching the `XMLHttpRequest` class only // in React Native. There is no `http`/`https` modules in that environment. -export const setupServer = createSetupServer(interceptXMLHttpRequest) +export const setupServer = createSetupServer(new XMLHttpRequestInterceptor()) diff --git a/src/node/createSetupServer.ts b/src/node/createSetupServer.ts index a7a7fe172..4b376a5cc 100644 --- a/src/node/createSetupServer.ts +++ b/src/node/createSetupServer.ts @@ -2,9 +2,10 @@ import { bold } from 'chalk' import { isNodeProcess } from 'is-node-process' import { StrictEventEmitter } from 'strict-event-emitter' import { - createInterceptor, + BatchInterceptor, MockedResponse as MockedInterceptedResponse, Interceptor, + HttpRequestEventMap, } from '@mswjs/interceptors' import * as requestHandlerUtils from '../utils/internal/requestHandlerUtils' import { ServerLifecycleEventsMap, SetupServerApi } from './glossary' @@ -25,7 +26,9 @@ const DEFAULT_LISTEN_OPTIONS: RequiredDeep = { * Creates a `setupServer` API using given request interceptors. * Useful to generate identical API using different patches to request issuing modules. */ -export function createSetupServer(...interceptors: Interceptor[]) { +export function createSetupServer( + ...interceptors: Interceptor[] +) { const emitter = new StrictEventEmitter() const publicEmitter = new StrictEventEmitter() pipeEvents(emitter, publicEmitter) @@ -57,27 +60,35 @@ export function createSetupServer(...interceptors: Interceptor[]) { let resolvedOptions = {} as RequiredDeep - const interceptor = createInterceptor({ - modules: interceptors, - async resolver(request) { - const mockedRequest = parseIsomorphicRequest(request) - return handleRequest( - mockedRequest, - currentHandlers, - resolvedOptions, - emitter, - { - transformResponse(response) { - return { - status: response.status, - statusText: response.statusText, - headers: response.headers.all(), - body: response.body, - } - }, + const interceptor = new BatchInterceptor({ + name: 'setup-server', + interceptors, + }) + + interceptor.on('request', async function setupServerListener(request) { + const mockedRequest = parseIsomorphicRequest(request) + const response = await handleRequest( + mockedRequest, + currentHandlers, + resolvedOptions, + emitter, + { + transformResponse(response) { + return { + status: response.status, + statusText: response.statusText, + headers: response.headers.all(), + body: response.body, + } }, - ) - }, + }, + ) + + if (response) { + request.respondWith(response) + } + + return }) interceptor.on('response', (request, response) => { @@ -146,7 +157,7 @@ ${bold(`${pragma} ${header}`)} close() { emitter.removeAllListeners() publicEmitter.removeAllListeners() - interceptor.restore() + interceptor.dispose() }, } } diff --git a/src/node/glossary.ts b/src/node/glossary.ts index f6df57c94..ad4ea99cb 100644 --- a/src/node/glossary.ts +++ b/src/node/glossary.ts @@ -1,5 +1,5 @@ -import { PartialDeep } from 'type-fest' -import { IsomorphicResponse } from '@mswjs/interceptors' +import type { PartialDeep } from 'type-fest' +import type { IsomorphicResponse } from '@mswjs/interceptors' import { RequestHandler } from '../handlers/RequestHandler' import { LifeCycleEventEmitter, diff --git a/src/node/setupServer.ts b/src/node/setupServer.ts index 5cdd72b44..5185cbb30 100644 --- a/src/node/setupServer.ts +++ b/src/node/setupServer.ts @@ -1,5 +1,5 @@ -import { interceptClientRequest } from '@mswjs/interceptors/lib/interceptors/ClientRequest' -import { interceptXMLHttpRequest } from '@mswjs/interceptors/lib/interceptors/XMLHttpRequest' +import { ClientRequestInterceptor } from '@mswjs/interceptors/lib/interceptors/ClientRequest' +import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/lib/interceptors/XMLHttpRequest' import { createSetupServer } from './createSetupServer' /** @@ -10,6 +10,6 @@ import { createSetupServer } from './createSetupServer' export const setupServer = createSetupServer( // List each interceptor separately instead of using the "node" preset // so that MSW wouldn't bundle the unnecessary classes (i.e. "SocketPolyfill"). - interceptClientRequest, - interceptXMLHttpRequest, + new ClientRequestInterceptor(), + new XMLHttpRequestInterceptor(), ) diff --git a/src/setupWorker/glossary.ts b/src/setupWorker/glossary.ts index 8b33a4a98..848b3ca24 100644 --- a/src/setupWorker/glossary.ts +++ b/src/setupWorker/glossary.ts @@ -7,7 +7,7 @@ import { } from '../sharedOptions' import { ServiceWorkerMessage } from '../utils/createBroadcastChannel' import { DefaultBodyType, RequestHandler } from '../handlers/RequestHandler' -import { InterceptorApi } from '@mswjs/interceptors' +import type { HttpRequestEventMap, Interceptor } from '@mswjs/interceptors' import { Path } from '../utils/matching/matchRequestUrl' import { RequiredDeep } from '../typeUtils' @@ -40,7 +40,7 @@ export interface ServiceWorkerIncomingRequest extends RequestWithoutMethods { /** * Text response body. */ - body: string | undefined + body?: string } export type ServiceWorkerIncomingResponse = Pick< @@ -134,7 +134,7 @@ export interface SetupWorkerInternalContext { > } useFallbackMode: boolean - fallbackInterceptor?: InterceptorApi + fallbackInterceptor?: Interceptor } export type ServiceWorkerInstanceTuple = [ diff --git a/src/setupWorker/stop/createFallbackStop.ts b/src/setupWorker/stop/createFallbackStop.ts index c066ccddb..aecfb6225 100644 --- a/src/setupWorker/stop/createFallbackStop.ts +++ b/src/setupWorker/stop/createFallbackStop.ts @@ -5,7 +5,7 @@ export function createFallbackStop( context: SetupWorkerInternalContext, ): StopHandler { return function stop() { - context.fallbackInterceptor?.restore() + context.fallbackInterceptor?.dispose() printStopMessage({ quiet: context.startOptions?.quiet }) } } diff --git a/src/utils/request/parseIsomorphicRequest.ts b/src/utils/request/parseIsomorphicRequest.ts index d088082ab..780afb13e 100644 --- a/src/utils/request/parseIsomorphicRequest.ts +++ b/src/utils/request/parseIsomorphicRequest.ts @@ -1,4 +1,4 @@ -import { IsomorphicRequest } from '@mswjs/interceptors' +import type { IsomorphicRequest } from '@mswjs/interceptors' import { MockedRequest, passthrough } from '../../handlers/RequestHandler' import { parseBody } from './parseBody' import { setRequestCookies } from './setRequestCookies' diff --git a/src/utils/worker/createFallbackRequestListener.ts b/src/utils/worker/createFallbackRequestListener.ts index baacd545d..da7838e80 100644 --- a/src/utils/worker/createFallbackRequestListener.ts +++ b/src/utils/worker/createFallbackRequestListener.ts @@ -1,55 +1,66 @@ -import { createInterceptor, InterceptorApi } from '@mswjs/interceptors' -import { interceptFetch } from '@mswjs/interceptors/lib/interceptors/fetch' -import { interceptXMLHttpRequest } from '@mswjs/interceptors/lib/interceptors/XMLHttpRequest' -import { RequestHandler } from '../../handlers/RequestHandler' +import { + Interceptor, + BatchInterceptor, + HttpRequestEventMap, +} from '@mswjs/interceptors' +import { FetchInterceptor } from '@mswjs/interceptors/lib/interceptors/fetch' +import { XMLHttpRequestInterceptor } from '@mswjs/interceptors/lib/interceptors/XMLHttpRequest' +import type { RequestHandler } from '../../handlers/RequestHandler' import { SerializedResponse, SetupWorkerInternalContext, StartOptions, } from '../../setupWorker/glossary' -import { RequiredDeep } from '../../typeUtils' +import type { RequiredDeep } from '../../typeUtils' import { handleRequest } from '../handleRequest' import { parseIsomorphicRequest } from '../request/parseIsomorphicRequest' export function createFallbackRequestListener( context: SetupWorkerInternalContext, options: RequiredDeep, -): InterceptorApi { - const interceptor = createInterceptor({ - modules: [interceptFetch, interceptXMLHttpRequest], - async resolver(request) { - const mockedRequest = parseIsomorphicRequest(request) - return handleRequest( - mockedRequest, - context.requestHandlers, - options, - context.emitter, - { - transformResponse(response) { - return { - status: response.status, - statusText: response.statusText, - headers: response.headers.all(), - body: response.body, - delay: response.delay, - } - }, - onMockedResponseSent( - response, - { handler, publicRequest, parsedRequest }, - ) { - if (!options.quiet) { - handler.log( - publicRequest, - response, - handler as RequestHandler, - parsedRequest, - ) - } - }, +): Interceptor { + const interceptor = new BatchInterceptor({ + name: 'fallback', + interceptors: [new FetchInterceptor(), new XMLHttpRequestInterceptor()], + }) + + interceptor.on('request', async (request) => { + const mockedRequest = parseIsomorphicRequest(request) + + const response = await handleRequest( + mockedRequest, + context.requestHandlers, + options, + context.emitter, + { + transformResponse(response) { + return { + status: response.status, + statusText: response.statusText, + headers: response.headers.all(), + body: response.body, + delay: response.delay, + } + }, + onMockedResponseSent( + response, + { handler, publicRequest, parsedRequest }, + ) { + if (!options.quiet) { + handler.log( + publicRequest, + response, + handler as RequestHandler, + parsedRequest, + ) + } }, - ) - }, + }, + ) + + if (response) { + request.respondWith(response) + } }) interceptor.apply() diff --git a/test/graphql-api/mutation.test.ts b/test/graphql-api/mutation.test.ts index 9f11b7bf2..8ec7adde9 100644 --- a/test/graphql-api/mutation.test.ts +++ b/test/graphql-api/mutation.test.ts @@ -1,28 +1,49 @@ import * as path from 'path' +import { createServer, ServerApi } from '@open-draft/test-server' import { pageWith } from 'page-with' -import { - GRAPHQL_TEST_URL, - executeGraphQLQuery, -} from './utils/executeGraphQLQuery' +import { executeGraphQLQuery } from './utils/executeGraphQLQuery' +import { gql } from '../support/graphql' -function createRuntime() { +function prepareRuntime() { return pageWith({ example: path.resolve(__dirname, 'mutation.mocks.ts'), }) } +let server: ServerApi + +function getEndpoint(): string { + return server.http.makeUrl('/graphql') +} + +beforeAll(async () => { + server = await createServer((app) => { + app.use('*', (req, res) => res.status(405).end()) + }) +}) + +afterAll(async () => { + await server.close() +}) + test('sends a mocked response to a GraphQL mutation', async () => { - const runtime = await createRuntime() + const runtime = await prepareRuntime() - const res = await executeGraphQLQuery(runtime.page, { - query: ` - mutation Logout { - logout { - userSession + const res = await executeGraphQLQuery( + runtime.page, + { + query: gql` + mutation Logout { + logout { + userSession + } } - } - `, - }) + `, + }, + { + uri: getEndpoint(), + }, + ) const headers = await res.allHeaders() const body = await res.json() @@ -38,23 +59,29 @@ test('sends a mocked response to a GraphQL mutation', async () => { }) test('prints a warning when captured an anonymous GraphQL mutation', async () => { - const runtime = await createRuntime() + const runtime = await prepareRuntime() - const res = await executeGraphQLQuery(runtime.page, { - query: ` - mutation { - logout { - userSession + const res = await executeGraphQLQuery( + runtime.page, + { + query: gql` + mutation { + logout { + userSession + } } - } - `, - }) + `, + }, + { + uri: getEndpoint(), + }, + ) expect(runtime.consoleSpy.get('warning')).toEqual( expect.arrayContaining([ expect.stringContaining( `\ -[MSW] Failed to intercept a GraphQL request at "POST ${GRAPHQL_TEST_URL}": anonymous GraphQL operations are not supported. +[MSW] Failed to intercept a GraphQL request at "POST ${getEndpoint()}": anonymous GraphQL operations are not supported. Consider naming this operation or using "graphql.operation" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation\ `, @@ -62,8 +89,6 @@ Consider naming this operation or using "graphql.operation" request handler to i ]), ) - expect(res.status).toBeUndefined() - expect(runtime.consoleSpy.get('error')).toEqual([ - 'Failed to load resource: net::ERR_FAILED', - ]) + // The actual GraphQL server is hit. + expect(res.status()).toBe(405) }) diff --git a/test/graphql-api/query.test.ts b/test/graphql-api/query.test.ts index 7e388d631..96e978225 100644 --- a/test/graphql-api/query.test.ts +++ b/test/graphql-api/query.test.ts @@ -1,32 +1,48 @@ import * as path from 'path' +import { createServer, ServerApi } from '@open-draft/test-server' import { pageWith } from 'page-with' -import { - GRAPHQL_TEST_URL, - executeGraphQLQuery, -} from './utils/executeGraphQLQuery' +import { executeGraphQLQuery } from './utils/executeGraphQLQuery' +import { gql } from '../support/graphql' -function createRuntime() { +function prepareRuntime() { return pageWith({ example: path.resolve(__dirname, 'query.mocks.ts'), }) } +let server: ServerApi + +function getEndpoint(): string { + return server.http.makeUrl('/graphql') +} + +beforeAll(async () => { + server = await createServer((app) => { + app.use('*', (req, res) => res.status(405).end()) + }) +}) + +afterAll(async () => { + await server.close() +}) + test('mocks a GraphQL query issued with a GET request', async () => { - const runtime = await createRuntime() + const runtime = await prepareRuntime() const res = await executeGraphQLQuery( runtime.page, { - query: ` - query GetUserDetail { - user { - firstName - lastName + query: gql` + query GetUserDetail { + user { + firstName + lastName + } } - } - `, + `, }, { + uri: getEndpoint(), method: 'GET', }, ) @@ -47,18 +63,24 @@ test('mocks a GraphQL query issued with a GET request', async () => { }) test('mocks a GraphQL query issued with a POST request', async () => { - const runtime = await createRuntime() - - const res = await executeGraphQLQuery(runtime.page, { - query: ` - query GetUserDetail { - user { - firstName - lastName + const runtime = await prepareRuntime() + + const res = await executeGraphQLQuery( + runtime.page, + { + query: gql` + query GetUserDetail { + user { + firstName + lastName + } } - } - `, - }) + `, + }, + { + uri: getEndpoint(), + }, + ) const headers = await res.allHeaders() const body = await res.json() @@ -75,23 +97,29 @@ test('mocks a GraphQL query issued with a POST request', async () => { }) test('prints a warning when captured an anonymous GraphQL query', async () => { - const runtime = await createRuntime() + const runtime = await prepareRuntime() - const res = await executeGraphQLQuery(runtime.page, { - query: ` - query { - user { - firstName + const res = await executeGraphQLQuery( + runtime.page, + { + query: gql` + query { + user { + firstName + } } - } - `, - }) + `, + }, + { + uri: getEndpoint(), + }, + ) expect(runtime.consoleSpy.get('warning')).toEqual( expect.arrayContaining([ expect.stringContaining( `\ -[MSW] Failed to intercept a GraphQL request at "POST ${GRAPHQL_TEST_URL}": anonymous GraphQL operations are not supported. +[MSW] Failed to intercept a GraphQL request at "POST ${getEndpoint()}": anonymous GraphQL operations are not supported. Consider naming this operation or using "graphql.operation" request handler to intercept GraphQL requests regardless of their operation name/type. Read more: https://mswjs.io/docs/api/graphql/operation\ `, @@ -99,8 +127,6 @@ Consider naming this operation or using "graphql.operation" request handler to i ]), ) - expect(res.status).toBeUndefined() - expect(runtime.consoleSpy.get('error')).toEqual([ - 'Failed to load resource: net::ERR_FAILED', - ]) + // The actual GraphQL server is hit. + expect(res.status()).toBe(405) }) diff --git a/test/jest.config.js b/test/jest.config.js index 8560c1dcf..df67b023f 100644 --- a/test/jest.config.js +++ b/test/jest.config.js @@ -1,6 +1,6 @@ module.exports = { preset: 'ts-jest', - testTimeout: 60000, + testTimeout: 15000, moduleNameMapper: { '^msw(.*)': '/..$1', }, diff --git a/test/msw-api/setup-server/scenarios/response-patching.test.ts b/test/msw-api/setup-server/scenarios/response-patching.test.ts index 25445791f..be13c8196 100644 --- a/test/msw-api/setup-server/scenarios/response-patching.test.ts +++ b/test/msw-api/setup-server/scenarios/response-patching.test.ts @@ -8,38 +8,49 @@ import { setupServer } from 'msw/node' let httpServer: ServerApi +interface ResponseBody { + id: number + mocked: boolean +} + const server = setupServer( - rest.get('https://test.mswjs.io/user', async (req, res, ctx) => { - const originalResponse = await ctx.fetch(httpServer.http.makeUrl('/user')) - const body = await originalResponse.json() - return res( - ctx.json({ - id: body.id, - mocked: true, - }), - ) - }), - rest.get('https://test.mswjs.io/complex-request', async (req, res, ctx) => { - const bypass = req.url.searchParams.get('bypass') - const shouldBypass = bypass === 'true' - const performRequest = shouldBypass - ? () => - ctx - .fetch(httpServer.http.makeUrl('/user'), { method: 'POST' }) - .then((response) => response.json()) - : () => - fetch('https://httpbin.org/post', { method: 'POST' }).then((res) => - res.json(), - ) - const originalResponse = await performRequest() + rest.get( + 'https://test.mswjs.io/user', + async (req, res, ctx) => { + const originalResponse = await ctx.fetch(httpServer.http.makeUrl('/user')) + const body = await originalResponse.json() - return res( - ctx.json({ - id: originalResponse.id, - mocked: true, - }), - ) - }), + return res( + ctx.json({ + id: body.id, + mocked: true, + }), + ) + }, + ), + rest.get( + 'https://test.mswjs.io/complex-request', + async (req, res, ctx) => { + const shouldBypass = req.url.searchParams.get('bypass') === 'true' + const performRequest = shouldBypass + ? () => + ctx + .fetch(httpServer.http.makeUrl('/user'), { method: 'POST' }) + .then((res) => res.json()) + : () => + fetch('https://httpbin.org/post', { method: 'POST' }).then((res) => + res.json(), + ) + const originalResponse = await performRequest() + + return res( + ctx.json({ + id: originalResponse.id, + mocked: true, + }), + ) + }, + ), rest.post('https://httpbin.org/post', (req, res, ctx) => { return res(ctx.json({ id: 303 })) }), @@ -74,7 +85,7 @@ test('returns a combination of mocked and original responses', async () => { expect(status).toBe(200) expect(headers.get('x-powered-by')).toBe('msw') - expect(body).toEqual({ + expect(body).toEqual({ id: 101, mocked: true, }) @@ -82,12 +93,10 @@ test('returns a combination of mocked and original responses', async () => { test('bypasses a mocked request when using "ctx.fetch"', async () => { const res = await fetch('https://test.mswjs.io/complex-request?bypass=true') - const { status, headers } = res - const body = await res.json() - expect(status).toBe(200) - expect(headers.get('x-powered-by')).toBe('msw') - expect(body).toEqual({ + expect(res.status).toBe(200) + expect(res.headers.get('x-powered-by')).toBe('msw') + expect(await res.json()).toEqual({ id: 202, mocked: true, }) @@ -95,12 +104,10 @@ test('bypasses a mocked request when using "ctx.fetch"', async () => { test('falls into the mocked request when using "fetch" directly', async () => { const res = await fetch('https://test.mswjs.io/complex-request') - const { status, headers } = res - const body = await res.json() - expect(status).toBe(200) - expect(headers.get('x-powered-by')).toBe('msw') - expect(body).toEqual({ + expect(res.status).toBe(200) + expect(res.headers.get('x-powered-by')).toBe('msw') + expect(await res.json()).toEqual({ id: 303, mocked: true, }) diff --git a/tsup.config.ts b/tsup.config.ts index 5b1a2d5ea..d313c8ed3 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -39,6 +39,7 @@ export default defineConfig([ entry: ['./src/node/index.ts'], format: ['esm', 'cjs'], outDir: './lib/node', + platform: 'node', external: [ 'http', 'https', diff --git a/yarn.lock b/yarn.lock index bf7902a9a..28cf6bc0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1692,17 +1692,17 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" -"@mswjs/interceptors@^0.15.1": - version "0.15.1" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.15.1.tgz#4a0009f56e51bc2cd3176f1507065c7d2f6c0d5e" - integrity sha512-D5B+ZJNlfvBm6ZctAfRBdNJdCHYAe2Ix4My5qfbHV5WH+3lkt3mmsjiWJzEh5ZwGDauzY487TldI275If7DJVw== +"@mswjs/interceptors@^0.16.3": + version "0.16.3" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.16.3.tgz#570ec131e379ab22bcd54820a24471f5aeaa1ce6" + integrity sha512-WxWere2XXSo49lblOwYnOn1J0fLTYDIrRAKdBjkpGfSPrfmkRKcTtuAghN0OoE5tWU3ljuVOJ09EH/ojQNVIdw== dependencies: "@open-draft/until" "^1.0.3" "@xmldom/xmldom" "^0.7.5" debug "^4.3.3" headers-polyfill "^3.0.4" outvariant "^1.2.1" - strict-event-emitter "^0.2.0" + strict-event-emitter "^0.2.4" "@nodelib/fs.scandir@2.1.4": version "2.1.4" @@ -8778,6 +8778,13 @@ strict-event-emitter@^0.2.0: dependencies: events "^3.3.0" +strict-event-emitter@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz#365714f0c95f059db31064ca745d5b33e5b30f6e" + integrity sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ== + dependencies: + events "^3.3.0" + string-argv@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"