From 88ad471e33afdf657d7ec129b7e939043861a6f8 Mon Sep 17 00:00:00 2001 From: Dima Voytenko Date: Mon, 14 Aug 2023 11:45:50 -0700 Subject: [PATCH] Concept: test mode for Playwright and similar integration tools (#52520) An experimental test mode that enables integration tests to mock server-side fetch requests in Playwright tests. For explanation on how this works, see the [`next-playwright/README.md`](https://github.com/vercel/next.js/pull/52520/files#diff-3b8da7782c16f015df5afafe0ac11247f5b8e5a1c0dbede341ca2b5124dfd924). --- .../experimental/testmode/playwright.d.ts | 1 + .../next/experimental/testmode/playwright.js | 1 + .../experimental/testmode/playwright/msw.d.ts | 1 + .../experimental/testmode/playwright/msw.js | 1 + .../next/experimental/testmode/proxy.d.ts | 1 + packages/next/experimental/testmode/proxy.js | 1 + packages/next/package.json | 11 +- packages/next/src/cli/next-dev.ts | 3 + packages/next/src/cli/next-start.ts | 4 + .../testmode/playwright/README.md | 96 ++++++ .../experimental/testmode/playwright/index.ts | 36 ++ .../experimental/testmode/playwright/msw.ts | 115 +++++++ .../testmode/playwright/next-fixture.ts | 56 +++ .../playwright/next-worker-fixture.ts | 59 ++++ .../testmode/playwright/page-route.ts | 55 +++ .../experimental/testmode/proxy/fetch-api.ts | 60 ++++ .../src/experimental/testmode/proxy/index.ts | 3 + .../src/experimental/testmode/proxy/server.ts | 84 +++++ .../src/experimental/testmode/proxy/types.ts | 60 ++++ .../next/src/experimental/testmode/server.ts | 141 ++++++++ packages/next/src/server/base-server.ts | 4 + packages/next/src/server/lib/render-server.ts | 1 + packages/next/src/server/lib/router-server.ts | 2 + packages/next/src/server/lib/start-server.ts | 6 + packages/next/src/server/next-server.ts | 17 +- packages/next/taskfile.js | 8 + packages/next/tsconfig.json | 3 +- pnpm-lock.yaml | 323 +++++++++++++----- 28 files changed, 1071 insertions(+), 82 deletions(-) create mode 100644 packages/next/experimental/testmode/playwright.d.ts create mode 100644 packages/next/experimental/testmode/playwright.js create mode 100644 packages/next/experimental/testmode/playwright/msw.d.ts create mode 100644 packages/next/experimental/testmode/playwright/msw.js create mode 100644 packages/next/experimental/testmode/proxy.d.ts create mode 100644 packages/next/experimental/testmode/proxy.js create mode 100644 packages/next/src/experimental/testmode/playwright/README.md create mode 100644 packages/next/src/experimental/testmode/playwright/index.ts create mode 100644 packages/next/src/experimental/testmode/playwright/msw.ts create mode 100644 packages/next/src/experimental/testmode/playwright/next-fixture.ts create mode 100644 packages/next/src/experimental/testmode/playwright/next-worker-fixture.ts create mode 100644 packages/next/src/experimental/testmode/playwright/page-route.ts create mode 100644 packages/next/src/experimental/testmode/proxy/fetch-api.ts create mode 100644 packages/next/src/experimental/testmode/proxy/index.ts create mode 100644 packages/next/src/experimental/testmode/proxy/server.ts create mode 100644 packages/next/src/experimental/testmode/proxy/types.ts create mode 100644 packages/next/src/experimental/testmode/server.ts diff --git a/packages/next/experimental/testmode/playwright.d.ts b/packages/next/experimental/testmode/playwright.d.ts new file mode 100644 index 0000000000000..1e87a445dddcf --- /dev/null +++ b/packages/next/experimental/testmode/playwright.d.ts @@ -0,0 +1 @@ +export * from '../../dist/experimental/testmode/playwright' diff --git a/packages/next/experimental/testmode/playwright.js b/packages/next/experimental/testmode/playwright.js new file mode 100644 index 0000000000000..b6ce1e2c343c9 --- /dev/null +++ b/packages/next/experimental/testmode/playwright.js @@ -0,0 +1 @@ +module.exports = require('../../dist/experimental/testmode/playwright') diff --git a/packages/next/experimental/testmode/playwright/msw.d.ts b/packages/next/experimental/testmode/playwright/msw.d.ts new file mode 100644 index 0000000000000..4e9e89b500c7c --- /dev/null +++ b/packages/next/experimental/testmode/playwright/msw.d.ts @@ -0,0 +1 @@ +export * from '../../../dist/experimental/testmode/playwright/msw' diff --git a/packages/next/experimental/testmode/playwright/msw.js b/packages/next/experimental/testmode/playwright/msw.js new file mode 100644 index 0000000000000..46c8b48f26247 --- /dev/null +++ b/packages/next/experimental/testmode/playwright/msw.js @@ -0,0 +1 @@ +module.exports = require('../../../dist/experimental/testmode/playwright/msw') diff --git a/packages/next/experimental/testmode/proxy.d.ts b/packages/next/experimental/testmode/proxy.d.ts new file mode 100644 index 0000000000000..82ec16b54f583 --- /dev/null +++ b/packages/next/experimental/testmode/proxy.d.ts @@ -0,0 +1 @@ +export * from '../../dist/experimental/testmode/proxy' diff --git a/packages/next/experimental/testmode/proxy.js b/packages/next/experimental/testmode/proxy.js new file mode 100644 index 0000000000000..5abed8aa6f8bd --- /dev/null +++ b/packages/next/experimental/testmode/proxy.js @@ -0,0 +1 @@ +module.exports = require('../../dist/experimental/testmode/proxy') diff --git a/packages/next/package.json b/packages/next/package.json index ad822c88d4826..7691dd950299b 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -61,7 +61,13 @@ "headers.d.ts", "navigation-types", "web-vitals.js", - "web-vitals.d.ts" + "web-vitals.d.ts", + "experimental/testmode/playwright.js", + "experimental/testmode/playwright.d.ts", + "experimental/testmode/playwright/msw.js", + "experimental/testmode/playwright/msw.d.ts", + "experimental/testmode/proxy.js", + "experimental/testmode/proxy.d.ts" ], "bin": { "next": "./dist/bin/next" @@ -143,6 +149,7 @@ "@next/react-refresh-utils": "13.4.15", "@next/swc": "13.4.15", "@opentelemetry/api": "1.4.1", + "@playwright/test": "^1.35.1", "@segment/ajv-human-errors": "2.1.2", "@taskr/clear": "1.1.0", "@taskr/esnext": "1.1.0", @@ -245,6 +252,7 @@ "lru-cache": "5.1.1", "micromatch": "4.0.4", "mini-css-extract-plugin": "2.4.3", + "msw": "^1.2.2", "nanoid": "3.1.32", "native-url": "0.3.4", "neo-async": "2.6.1", @@ -283,6 +291,7 @@ "stacktrace-parser": "0.1.10", "stream-browserify": "3.0.0", "stream-http": "3.1.1", + "strict-event-emitter": "0.5.0", "string-hash": "1.1.3", "string_decoder": "1.3.0", "strip-ansi": "6.0.0", diff --git a/packages/next/src/cli/next-dev.ts b/packages/next/src/cli/next-dev.ts index f9936214f2787..a103669429053 100644 --- a/packages/next/src/cli/next-dev.ts +++ b/packages/next/src/cli/next-dev.ts @@ -189,6 +189,7 @@ const nextDev: CliCommand = async (argv) => { '--hostname': String, '--turbo': Boolean, '--experimental-turbo': Boolean, + '--experimental-test-proxy': Boolean, // To align current messages with native binary. // Will need to adjust subcommand later. @@ -274,6 +275,7 @@ const nextDev: CliCommand = async (argv) => { // some set-ups that rely on listening on other interfaces const host = args['--hostname'] config = await loadConfig(PHASE_DEVELOPMENT_SERVER, dir) + const isExperimentalTestProxy = args['--experimental-test-proxy'] const devServerOptions: StartServerOptions = { dir, @@ -281,6 +283,7 @@ const nextDev: CliCommand = async (argv) => { allowRetry, isDev: true, hostname: host, + isExperimentalTestProxy, } if (args['--turbo']) { diff --git a/packages/next/src/cli/next-start.ts b/packages/next/src/cli/next-start.ts index 83844964e79cb..ef54c2723de47 100755 --- a/packages/next/src/cli/next-start.ts +++ b/packages/next/src/cli/next-start.ts @@ -14,6 +14,7 @@ const nextStart: CliCommand = async (argv) => { '--port': Number, '--hostname': String, '--keepAliveTimeout': Number, + '--experimental-test-proxy': Boolean, // Aliases '-h': '--help', @@ -46,6 +47,8 @@ const nextStart: CliCommand = async (argv) => { const host = args['--hostname'] const port = getPort(args) + const isExperimentalTestProxy = args['--experimental-test-proxy'] + const keepAliveTimeoutArg: number | undefined = args['--keepAliveTimeout'] if ( typeof keepAliveTimeoutArg !== 'undefined' && @@ -66,6 +69,7 @@ const nextStart: CliCommand = async (argv) => { await startServer({ dir, isDev: false, + isExperimentalTestProxy, hostname: host, port, keepAliveTimeout, diff --git a/packages/next/src/experimental/testmode/playwright/README.md b/packages/next/src/experimental/testmode/playwright/README.md new file mode 100644 index 0000000000000..2d8ea11d76fb6 --- /dev/null +++ b/packages/next/src/experimental/testmode/playwright/README.md @@ -0,0 +1,96 @@ +# Experimental test mode for Playwright + +### Prerequisites + +You have a Next.js project. + +### Install `@playwright/test` in your project + +```sh +npm install -D @playwright/test +``` + +### Optionally install MSW in your project + +[MSW](https://mswjs.io/) can be helpful for fetch mocking. + +```sh +npm install -D msw +``` + +### Update `playwright.config.ts` + +```javascript +import { defineConfig } from 'next/experimental/testmode/playwright' + +export default defineConfig({ + webServer: { + command: 'npm dev -- --experimental-test-proxy', + url: 'http://localhost:3000', + }, +}) +``` + +### Use the `next/experimental/testmode/playwright` to create tests + +```javascript +import { test, expect } from 'next/experimental/testmode/playwright' + +test('/product/shoe', async ({ page, next }) => { + next.onFetch((request) => { + if (request.url === 'http://my-db/product/shoe') { + return new Response( + JSON.stringify({ + title: 'A shoe', + }), + { + headers: { + 'Content-Type': 'application/json', + }, + } + ) + } + return 'abort' + }) + + await page.goto('/product/shoe') + + await expect(page.locator('body')).toHaveText(/Shoe/) +}) +``` + +### Or use the `next/experimental/testmode/playwright/msw` + +```javascript +import { test, expect, rest } from 'next/experimental/testmode/playwright/msw' + +test.use({ + mswHandlers: [ + rest.get('http://my-db/product/shoe', (req, res, ctx) => { + return res( + ctx.status(200), + ctx.json({ + title: 'A shoe', + }) + ) + }), + ], +}) + +test('/product/shoe', async ({ page, msw }) => { + msw.use( + rest.get('http://my-db/product/boot', (req, res, ctx) => { + return res.once( + ctx.status(200), + ctx.json({ + title: 'A boot', + }) + ) + }) + ) + + await page.goto('/product/boot') + + await expect(page.locator('body')).toHaveText(/Boot/) +}) +``` diff --git a/packages/next/src/experimental/testmode/playwright/index.ts b/packages/next/src/experimental/testmode/playwright/index.ts new file mode 100644 index 0000000000000..0396243369948 --- /dev/null +++ b/packages/next/src/experimental/testmode/playwright/index.ts @@ -0,0 +1,36 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { test as base } from '@playwright/test' +import type { NextFixture } from './next-fixture' +import type { NextWorkerFixture } from './next-worker-fixture' +import { applyNextWorkerFixture } from './next-worker-fixture' +import { applyNextFixture } from './next-fixture' + +// eslint-disable-next-line import/no-extraneous-dependencies +export * from '@playwright/test' + +export type { NextFixture } +export type { FetchHandlerResult } from '../proxy' + +export const test = base.extend< + { next: NextFixture }, + { _nextWorker: NextWorkerFixture } +>({ + _nextWorker: [ + // eslint-disable-next-line no-empty-pattern + async ({}, use) => { + await applyNextWorkerFixture(use) + }, + { scope: 'worker', auto: true }, + ], + + next: async ({ _nextWorker, page, extraHTTPHeaders }, use, testInfo) => { + await applyNextFixture(use, { + testInfo, + nextWorker: _nextWorker, + page, + extraHTTPHeaders, + }) + }, +}) + +export default test diff --git a/packages/next/src/experimental/testmode/playwright/msw.ts b/packages/next/src/experimental/testmode/playwright/msw.ts new file mode 100644 index 0000000000000..fae63b6fffea9 --- /dev/null +++ b/packages/next/src/experimental/testmode/playwright/msw.ts @@ -0,0 +1,115 @@ +import { test as base } from './index' +import type { NextFixture } from './next-fixture' +import { + type RequestHandler, + type MockedResponse, + MockedRequest, + handleRequest, + // eslint-disable-next-line import/no-extraneous-dependencies +} from 'msw' +// eslint-disable-next-line import/no-extraneous-dependencies +import { Emitter } from 'strict-event-emitter' + +// eslint-disable-next-line import/no-extraneous-dependencies +export * from 'msw' +// eslint-disable-next-line import/no-extraneous-dependencies +export * from '@playwright/test' +export type { NextFixture } + +export interface MswFixture { + use: (...handlers: RequestHandler[]) => void +} + +export const test = base.extend<{ + msw: MswFixture + mswHandlers: RequestHandler[] +}>({ + mswHandlers: [], + + msw: [ + async ({ next, mswHandlers }, use) => { + const handlers: RequestHandler[] = [...mswHandlers] + const emitter = new Emitter() + + next.onFetch(async (request) => { + const { + body, + method, + headers, + credentials, + cache, + redirect, + integrity, + keepalive, + mode, + destination, + referrer, + referrerPolicy, + } = request + const mockedRequest = new MockedRequest(new URL(request.url), { + body: body ? await request.arrayBuffer() : undefined, + method, + headers: Object.fromEntries(headers), + credentials, + cache, + redirect, + integrity, + keepalive, + mode, + destination, + referrer, + referrerPolicy, + }) + let isPassthrough = false + let mockedResponse: MockedResponse | undefined + await handleRequest( + mockedRequest, + handlers.slice(0), + { onUnhandledRequest: 'error' }, + emitter as any, + { + onPassthroughResponse: () => { + isPassthrough = true + }, + onMockedResponse: (r) => { + mockedResponse = r + }, + } + ) + + if (isPassthrough) { + return 'continue' + } + + if (mockedResponse) { + const { + status, + headers: responseHeaders, + body: responseBody, + delay, + } = mockedResponse + if (delay) { + await new Promise((resolve) => setTimeout(resolve, delay)) + } + return new Response(responseBody, { + status, + headers: new Headers(responseHeaders), + }) + } + + return 'abort' + }) + + await use({ + use: (...newHandlers) => { + handlers.unshift(...newHandlers) + }, + }) + + handlers.length = 0 + }, + { auto: true }, + ], +}) + +export default test diff --git a/packages/next/src/experimental/testmode/playwright/next-fixture.ts b/packages/next/src/experimental/testmode/playwright/next-fixture.ts new file mode 100644 index 0000000000000..5648be551d5aa --- /dev/null +++ b/packages/next/src/experimental/testmode/playwright/next-fixture.ts @@ -0,0 +1,56 @@ +import type { Page, TestInfo } from '@playwright/test' +import type { NextWorkerFixture, FetchHandler } from './next-worker-fixture' +import { handleRoute } from './page-route' + +export interface NextFixture { + onFetch: (handler: FetchHandler) => void +} + +class NextFixtureImpl implements NextFixture { + private fetchHandler: FetchHandler | null = null + + constructor( + public testId: string, + private worker: NextWorkerFixture, + private page: Page + ) { + this.page.route('**', (route) => + handleRoute(route, page, this.fetchHandler) + ) + } + + teardown(): void { + this.worker.cleanupTest(this.testId) + } + + onFetch(handler: FetchHandler): void { + this.fetchHandler = handler + this.worker.onFetch(this.testId, handler) + } +} + +export async function applyNextFixture( + use: (fixture: NextFixture) => Promise, + { + testInfo, + nextWorker, + page, + extraHTTPHeaders, + }: { + testInfo: TestInfo + nextWorker: NextWorkerFixture + page: Page + extraHTTPHeaders: Record | undefined + } +): Promise { + const fixture = new NextFixtureImpl(testInfo.testId, nextWorker, page) + page.setExtraHTTPHeaders({ + ...extraHTTPHeaders, + 'Next-Test-Proxy-Port': String(nextWorker.proxyPort), + 'Next-Test-Data': fixture.testId, + }) + + await use(fixture) + + fixture.teardown() +} diff --git a/packages/next/src/experimental/testmode/playwright/next-worker-fixture.ts b/packages/next/src/experimental/testmode/playwright/next-worker-fixture.ts new file mode 100644 index 0000000000000..e262f43be8de1 --- /dev/null +++ b/packages/next/src/experimental/testmode/playwright/next-worker-fixture.ts @@ -0,0 +1,59 @@ +import type { FetchHandlerResult, ProxyServer } from '../proxy' +import { createProxyServer } from '../proxy' + +export type FetchHandler = ( + request: Request +) => FetchHandlerResult | Promise + +export interface NextWorkerFixture { + proxyPort: number + onFetch: (testId: string, handler: FetchHandler) => void + cleanupTest: (testId: string) => void +} + +class NextWorkerFixtureImpl implements NextWorkerFixture { + public proxyPort: number = 0 + private proxyServer: ProxyServer | null = null + private proxyFetchMap = new Map() + + async setup(): Promise { + const server = await createProxyServer({ + onFetch: this.handleProxyFetch.bind(this), + }) + + this.proxyPort = server.port + this.proxyServer = server + } + + teardown(): void { + if (this.proxyServer) { + this.proxyServer.close() + this.proxyServer = null + } + } + + cleanupTest(testId: string): void { + this.proxyFetchMap.delete(testId) + } + + onFetch(testId: string, handler: FetchHandler): void { + this.proxyFetchMap.set(testId, handler) + } + + private async handleProxyFetch( + testId: string, + request: Request + ): Promise { + const handler = this.proxyFetchMap.get(testId) + return handler?.(request) + } +} + +export async function applyNextWorkerFixture( + use: (fixture: NextWorkerFixture) => Promise +): Promise { + const fixture = new NextWorkerFixtureImpl() + await fixture.setup() + await use(fixture) + fixture.teardown() +} diff --git a/packages/next/src/experimental/testmode/playwright/page-route.ts b/packages/next/src/experimental/testmode/playwright/page-route.ts new file mode 100644 index 0000000000000..c0b3bd09e3162 --- /dev/null +++ b/packages/next/src/experimental/testmode/playwright/page-route.ts @@ -0,0 +1,55 @@ +import type { Page, Route } from '@playwright/test' +import type { FetchHandler } from './next-worker-fixture' + +export async function handleRoute( + route: Route, + page: Page, + fetchHandler: FetchHandler | null +) { + const request = route.request() + + // Continue the navigation and non-fetch requests. + if (request.isNavigationRequest() || request.resourceType() !== 'fetch') { + return route.continue() + } + + // Continue the local requests. The followup requests will be intercepted + // on the server. + const pageOrigin = new URL(page.url()).origin + const requestOrigin = new URL(request.url()).origin + if (pageOrigin === requestOrigin) { + return route.continue() + } + + if (!fetchHandler) { + return route.abort() + } + + const postData = request.postDataBuffer() + const fetchRequest = new Request(request.url(), { + method: request.method(), + headers: Object.fromEntries( + Object.entries(request.headers()).filter( + ([name]) => !name.toLowerCase().startsWith('next-test-') + ) + ), + body: postData ?? null, + }) + + const proxyResponse = await fetchHandler(fetchRequest) + if (!proxyResponse) { + return route.abort() + } + if (proxyResponse === 'abort') { + return route.abort() + } + if (proxyResponse === 'continue') { + return route.continue() + } + const { status, headers, body } = proxyResponse + return route.fulfill({ + status, + headers: Object.fromEntries(headers), + body: body ? Buffer.from(await proxyResponse.arrayBuffer()) : undefined, + }) +} diff --git a/packages/next/src/experimental/testmode/proxy/fetch-api.ts b/packages/next/src/experimental/testmode/proxy/fetch-api.ts new file mode 100644 index 0000000000000..9cb018295a38b --- /dev/null +++ b/packages/next/src/experimental/testmode/proxy/fetch-api.ts @@ -0,0 +1,60 @@ +import type { ProxyFetchRequest, ProxyResponse } from './types' +import { ABORT, CONTINUE, UNHANDLED } from './types' + +export type FetchHandlerResult = + | Response + | 'abort' + | 'continue' + | null + | undefined + +export type FetchHandler = ( + testData: string, + request: Request +) => FetchHandlerResult | Promise + +function buildRequest(req: ProxyFetchRequest): Request { + const { request: proxyRequest } = req + const { url, headers, body, ...options } = proxyRequest + return new Request(url, { + ...options, + headers: new Headers(headers), + body: body ? Buffer.from(body, 'base64') : null, + }) +} + +async function buildResponse( + response: FetchHandlerResult +): Promise { + if (!response) { + return UNHANDLED + } + if (response === 'abort') { + return ABORT + } + if (response === 'continue') { + return CONTINUE + } + + const { status, headers, body } = response + return { + api: 'fetch', + response: { + status, + headers: Array.from(headers), + body: body + ? Buffer.from(await response.arrayBuffer()).toString('base64') + : null, + }, + } +} + +export async function handleFetch( + req: ProxyFetchRequest, + onFetch: FetchHandler +): Promise { + const { testData } = req + const request = buildRequest(req) + const response = await onFetch(testData, request) + return buildResponse(response) +} diff --git a/packages/next/src/experimental/testmode/proxy/index.ts b/packages/next/src/experimental/testmode/proxy/index.ts new file mode 100644 index 0000000000000..72df20573329c --- /dev/null +++ b/packages/next/src/experimental/testmode/proxy/index.ts @@ -0,0 +1,3 @@ +export { createProxyServer } from './server' +export type { FetchHandler, FetchHandlerResult } from './fetch-api' +export type * from './types' diff --git a/packages/next/src/experimental/testmode/proxy/server.ts b/packages/next/src/experimental/testmode/proxy/server.ts new file mode 100644 index 0000000000000..fcf9f851bce45 --- /dev/null +++ b/packages/next/src/experimental/testmode/proxy/server.ts @@ -0,0 +1,84 @@ +import http from 'http' +import type { IncomingMessage } from 'http' +import type { ProxyRequest, ProxyResponse, ProxyServer } from './types' +import { UNHANDLED } from './types' +import type { FetchHandler } from './fetch-api' +import { handleFetch } from './fetch-api' + +function readBody(req: IncomingMessage): Promise { + return new Promise((resolve, reject) => { + const acc: Buffer[] = [] + req.on('data', (chunk) => { + acc.push(chunk) + }) + req.on('end', () => { + resolve(Buffer.concat(acc)) + }) + req.on('error', reject) + }) +} + +export async function createProxyServer({ + onFetch, +}: { + onFetch?: FetchHandler +}): Promise { + const server = http.createServer(async (req, res) => { + if (req.url !== '/') { + res.writeHead(404) + res.end() + return + } + + let json: ProxyRequest | undefined + try { + json = JSON.parse((await readBody(req)).toString('utf-8')) as ProxyRequest + } catch (e) { + res.writeHead(400) + res.end() + return + } + + const { api } = json + + let response: ProxyResponse | undefined + switch (api) { + case 'fetch': + if (onFetch) { + response = await handleFetch(json, onFetch) + } + break + default: + break + } + if (!response) { + response = UNHANDLED + } + + res.writeHead(200, { 'Content-Type': 'application/json' }) + res.write(JSON.stringify(response)) + res.end() + }) + + await new Promise((resolve) => { + server.listen(0, 'localhost', () => { + resolve(undefined) + }) + }) + + const address = server.address() + if (!address || typeof address !== 'object') { + server.close() + throw new Error('Failed to create a proxy server') + } + const port = address.port + + const fetchWith: ProxyServer['fetchWith'] = (input, init, testData) => { + const request = new Request(input, init) + request.headers.set('Next-Test-Proxy-Port', String(port)) + request.headers.set('Next-Test-Data', testData ?? '') + return fetch(request) + } + + return { port, close: () => server.close(), fetchWith } +} diff --git a/packages/next/src/experimental/testmode/proxy/types.ts b/packages/next/src/experimental/testmode/proxy/types.ts new file mode 100644 index 0000000000000..e0e569c02b175 --- /dev/null +++ b/packages/next/src/experimental/testmode/proxy/types.ts @@ -0,0 +1,60 @@ +export interface ProxyServer { + readonly port: number + fetchWith( + input: string | URL, + init?: RequestInit, + testData?: string + ): Promise + close(): void +} + +interface ProxyRequestBase { + testData: string + api: string +} + +interface ProxyResponseBase { + api: string +} + +export interface ProxyUnhandledResponse extends ProxyResponseBase { + api: 'unhandled' +} + +export interface ProxyAbortResponse extends ProxyResponseBase { + api: 'abort' +} + +export interface ProxyContinueResponse extends ProxyResponseBase { + api: 'continue' +} + +export interface ProxyFetchRequest extends ProxyRequestBase { + api: 'fetch' + request: { + url: string + headers: Array<[string, string]> + body: string | null + } & Omit +} + +export interface ProxyFetchResponse extends ProxyResponseBase { + api: 'fetch' + response: { + status: number + headers: Array<[string, string]> + body: string | null + } +} + +export type ProxyRequest = ProxyFetchRequest + +export type ProxyResponse = + | ProxyUnhandledResponse + | ProxyAbortResponse + | ProxyContinueResponse + | ProxyFetchResponse + +export const ABORT: ProxyResponse = { api: 'abort' } +export const CONTINUE: ProxyResponse = { api: 'continue' } +export const UNHANDLED: ProxyResponse = { api: 'unhandled' } diff --git a/packages/next/src/experimental/testmode/server.ts b/packages/next/src/experimental/testmode/server.ts new file mode 100644 index 0000000000000..7ea86ac336ba7 --- /dev/null +++ b/packages/next/src/experimental/testmode/server.ts @@ -0,0 +1,141 @@ +import { AsyncLocalStorage } from 'async_hooks' +import { NodeRequestHandler } from '../../server/next-server' +import type { + ProxyFetchRequest, + ProxyFetchResponse, + ProxyResponse, +} from './proxy' + +interface TestReqInfo { + url: string + proxyPort: number + testData: string +} + +const testStorage = new AsyncLocalStorage() + +type Fetch = typeof fetch +type FetchInputArg = Parameters[0] +type FetchInitArg = Parameters[1] + +async function buildProxyRequest( + testData: string, + request: Request +): Promise { + const { + url, + method, + headers, + body, + cache, + credentials, + integrity, + mode, + redirect, + referrer, + referrerPolicy, + } = request + return { + testData, + api: 'fetch', + request: { + url, + method, + headers: Array.from(headers), + body: body + ? Buffer.from(await request.arrayBuffer()).toString('base64') + : null, + cache, + credentials, + integrity, + mode, + redirect, + referrer, + referrerPolicy, + }, + } +} + +function buildResponse(proxyResponse: ProxyFetchResponse): Response { + const { status, headers, body } = proxyResponse.response + return new Response(body ? Buffer.from(body, 'base64') : null, { + status, + headers: new Headers(headers), + }) +} + +function interceptFetch() { + const originalFetch = global.fetch + global.fetch = async function testFetch( + input: FetchInputArg, + init?: FetchInitArg + ): Promise { + // Passthrough internal requests. + // @ts-ignore + if (init?.next?.internal) { + return originalFetch(input, init) + } + + const testInfo = testStorage.getStore() + if (!testInfo) { + throw new Error('No test info') + } + + const { testData, proxyPort } = testInfo + const originalRequest = new Request(input, init) + const proxyRequest = await buildProxyRequest(testData, originalRequest) + + const resp = await originalFetch(`http://localhost:${proxyPort}`, { + method: 'POST', + body: JSON.stringify(proxyRequest), + }) + if (!resp.ok) { + throw new Error(`Proxy request failed: ${resp.status}`) + } + + const proxyResponse = (await resp.json()) as ProxyResponse + const { api } = proxyResponse + switch (api) { + case 'continue': + return originalFetch(originalRequest) + case 'abort': + case 'unhandled': + throw new Error('Proxy request aborted') + default: + break + } + return buildResponse(proxyResponse) + } +} + +export function interceptTestApis(): () => void { + const originalFetch = global.fetch + interceptFetch() + + // Cleanup. + return () => { + global.fetch = originalFetch + } +} + +export function wrapRequestHandler( + handler: NodeRequestHandler +): NodeRequestHandler { + return async (req, res, parsedUrl) => { + const proxyPortHeader = req.headers['next-test-proxy-port'] + if (!proxyPortHeader) { + await handler(req, res, parsedUrl) + return + } + + const url = req.url ?? '' + const proxyPort = Number(proxyPortHeader) + const testData = (req.headers['next-test-data'] as string | undefined) ?? '' + const testReqInfo: TestReqInfo = { + url, + proxyPort, + testData, + } + await testStorage.run(testReqInfo, () => handler(req, res, parsedUrl)) + } +} diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index f1361ed58e259..1d0c81318453c 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -145,6 +145,10 @@ export interface Options { * Tells if Next.js is running in dev mode */ dev?: boolean + /** + * Enables the experimental testing mode. + */ + experimentalTestProxy?: boolean /** * Where the Next project is located */ diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index 398ad9b581d33..5e572c25145e7 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -72,6 +72,7 @@ export async function initialize(opts: { isNodeDebugging: boolean keepAliveTimeout?: number serverFields?: any + experimentalTestProxy: boolean }): Promise> { // if we already setup the server return as we only need to do // this on first worker boot diff --git a/packages/next/src/server/lib/router-server.ts b/packages/next/src/server/lib/router-server.ts index 118712c7adb93..e3b0987cbd001 100644 --- a/packages/next/src/server/lib/router-server.ts +++ b/packages/next/src/server/lib/router-server.ts @@ -65,6 +65,7 @@ export async function initialize(opts: { isNodeDebugging: boolean keepAliveTimeout?: number customServer?: boolean + experimentalTestProxy?: boolean }): Promise<[WorkerRequestHandler, WorkerUpgradeHandler]> { process.title = 'next-router-worker' @@ -131,6 +132,7 @@ export async function initialize(opts: { dev: !!opts.dev, isNodeDebugging: !!opts.isNodeDebugging, serverFields: devInstance?.serverFields || {}, + experimentalTestProxy: !!opts.experimentalTestProxy, } const renderWorkers: { app?: RenderWorker diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 193e250854916..2bc3aa28b66e6 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -25,6 +25,7 @@ export interface StartServerOptions { customServer?: boolean minimalMode?: boolean keepAliveTimeout?: number + isExperimentalTestProxy?: boolean } export async function getRequestHandlers({ @@ -35,6 +36,7 @@ export async function getRequestHandlers({ minimalMode, isNodeDebugging, keepAliveTimeout, + experimentalTestProxy, }: { dir: string port: number @@ -43,6 +45,7 @@ export async function getRequestHandlers({ minimalMode?: boolean isNodeDebugging?: boolean keepAliveTimeout?: number + experimentalTestProxy?: boolean }): ReturnType { return initialize({ dir, @@ -53,6 +56,7 @@ export async function getRequestHandlers({ workerType: 'router', isNodeDebugging: isNodeDebugging || false, keepAliveTimeout, + experimentalTestProxy, }) } @@ -64,6 +68,7 @@ export async function startServer({ minimalMode, allowRetry, keepAliveTimeout, + isExperimentalTestProxy, logReady = true, }: StartServerOptions): Promise { let handlersReady = () => {} @@ -203,6 +208,7 @@ export async function startServer({ minimalMode, isNodeDebugging: Boolean(isNodeDebugging), keepAliveTimeout, + experimentalTestProxy: !!isExperimentalTestProxy, }) requestHandler = initResult[0] upgradeHandler = initResult[1] diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index fd12f81ed6e26..53a692021b0a4 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -221,6 +221,12 @@ export default class NextNodeServer extends BaseServer { // ensure options are set when loadConfig isn't called setHttpClientAndAgentOptions(this.nextConfig) + + // Intercept fetch and other testmode apis. + if (this.serverOptions.experimentalTestProxy) { + const { interceptTestApis } = require('../experimental/testmode/server') + interceptTestApis() + } } protected async prepareImpl() { @@ -1065,11 +1071,20 @@ export default class NextNodeServer extends BaseServer { } public getRequestHandler(): NodeRequestHandler { + const handler = this.makeRequestHandler() + if (this.serverOptions.experimentalTestProxy) { + const { wrapRequestHandler } = require('../experimental/testmode/server') + return wrapRequestHandler(handler) + } + return handler + } + + private makeRequestHandler(): NodeRequestHandler { // This is just optimization to fire prepare as soon as possible // It will be properly awaited later void this.prepare() const handler = super.getRequestHandler() - return async (req, res, parsedUrl) => { + return (req, res, parsedUrl) => { const normalizedReq = this.normalizeReq(req) const normalizedRes = this.normalizeRes(res) diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 722fd8e414b26..634edb6eb7657 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -2374,6 +2374,7 @@ export async function compile(task, opts) { 'shared_re_exported', 'shared_re_exported_esm', 'server_wasm', + 'experimental_testmode', // we compile this each time so that fresh runtime data is pulled // before each publish 'ncc_amp_optimizer', @@ -2639,6 +2640,13 @@ export async function server_wasm(task, opts) { await task.source('src/server/**/*.+(wasm)').target('dist/server') } +export async function experimental_testmode(task, opts) { + await task + .source('src/experimental/testmode/**/!(*.test).+(js|ts|tsx)') + .swc('server', {}) + .target('dist/experimental/testmode') +} + export async function release(task) { await task.clear('dist').start('build') } diff --git a/packages/next/tsconfig.json b/packages/next/tsconfig.json index 19a34efa7da69..b13035993a163 100644 --- a/packages/next/tsconfig.json +++ b/packages/next/tsconfig.json @@ -18,6 +18,7 @@ "legacy/*.d.ts", "types/compiled.d.ts", "navigation-types/*.d.ts", - "navigation-types/compat/*.d.ts" + "navigation-types/compat/*.d.ts", + "experimental/**/*.d.ts" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49778e85c2b8c..85d0159852a75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -838,6 +838,9 @@ importers: '@opentelemetry/api': specifier: 1.4.1 version: 1.4.1 + '@playwright/test': + specifier: ^1.35.1 + version: 1.35.1 '@segment/ajv-human-errors': specifier: 2.1.2 version: 2.1.2(ajv@8.11.0) @@ -1144,6 +1147,9 @@ importers: mini-css-extract-plugin: specifier: 2.4.3 version: 2.4.3(webpack@5.86.0) + msw: + specifier: ^1.2.2 + version: 1.2.2(typescript@5.1.3) nanoid: specifier: 3.1.32 version: 3.1.32 @@ -1258,6 +1264,9 @@ importers: stream-http: specifier: 3.1.1 version: 3.1.1 + strict-event-emitter: + specifier: 0.5.0 + version: 0.5.0 string-hash: specifier: 1.1.3 version: 1.1.3 @@ -1913,7 +1922,7 @@ packages: '@babel/helper-module-imports': 7.21.4 '@babel/helper-plugin-utils': 7.21.5 '@babel/traverse': 7.18.0 - debug: 4.3.4 + debug: 4.1.1 lodash.debounce: 4.0.8 resolve: 1.22.2 semver: 6.3.0 @@ -1931,7 +1940,7 @@ packages: '@babel/helper-module-imports': 7.21.4 '@babel/helper-plugin-utils': 7.21.5 '@babel/traverse': 7.18.0 - debug: 4.3.4 + debug: 4.1.1 lodash.debounce: 4.0.8 resolve: 1.22.2 semver: 6.3.0 @@ -1946,7 +1955,7 @@ packages: '@babel/core': 7.18.0 '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.18.0) '@babel/helper-plugin-utils': 7.21.5 - debug: 4.3.4 + debug: 4.1.1 lodash.debounce: 4.0.8 resolve: 1.22.2 semver: 6.3.0 @@ -4912,7 +4921,7 @@ packages: '@babel/helper-split-export-declaration': 7.18.6 '@babel/parser': 7.18.0 '@babel/types': 7.18.0 - debug: 4.3.4 + debug: 4.1.1 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -5152,7 +5161,7 @@ packages: engines: {node: ^10.12.0 || >=12.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.1.1 espree: 7.3.1 globals: 13.19.0 ignore: 4.0.6 @@ -5503,7 +5512,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 + debug: 4.1.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5514,7 +5523,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 + debug: 4.1.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5556,7 +5565,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@types/node': 20.2.5 - chalk: 4.1.2 + chalk: 4.0.0 jest-message-util: 27.5.1 jest-util: 27.5.1 slash: 3.0.0 @@ -5568,7 +5577,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@types/node': 20.2.5 - chalk: 4.1.2 + chalk: 4.0.0 jest-message-util: 27.5.1 jest-util: 27.5.1 slash: 3.0.0 @@ -5589,7 +5598,7 @@ packages: '@jest/types': 27.5.1 '@types/node': 20.2.5 ansi-escapes: 4.3.2 - chalk: 4.1.2 + chalk: 4.0.0 emittery: 0.8.1 exit: 0.1.2 graceful-fs: 4.2.11 @@ -5683,7 +5692,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - chalk: 4.1.2 + chalk: 4.0.0 collect-v8-coverage: 1.0.1 exit: 0.1.2 glob: 7.2.0 @@ -5777,7 +5786,7 @@ packages: '@babel/core': 7.18.0 '@jest/types': 27.5.1 babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 + chalk: 4.0.0 convert-source-map: 1.9.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 @@ -5832,7 +5841,7 @@ packages: '@types/istanbul-reports': 3.0.1 '@types/node': 20.2.5 '@types/yargs': 15.0.15 - chalk: 4.1.2 + chalk: 4.0.0 dev: true /@jest/types@27.5.1: @@ -5843,7 +5852,7 @@ packages: '@types/istanbul-reports': 3.0.1 '@types/node': 20.2.5 '@types/yargs': 16.0.5 - chalk: 4.1.2 + chalk: 4.0.0 /@jest/types@29.5.0: resolution: {integrity: sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==} @@ -6649,6 +6658,30 @@ packages: '@types/react': 18.2.7 react: 18.2.0 + /@mswjs/cookies@0.2.2: + resolution: {integrity: sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==} + engines: {node: '>=14'} + dependencies: + '@types/set-cookie-parser': 2.4.3 + set-cookie-parser: 2.6.0 + dev: true + + /@mswjs/interceptors@0.17.9: + resolution: {integrity: sha512-4LVGt03RobMH/7ZrbHqRxQrS9cc2uh+iNKSj8UWr8M26A2i793ju+csaB5zaqYltqJmA2jUq4VeYfKmVqvsXQg==} + engines: {node: '>=14'} + dependencies: + '@open-draft/until': 1.0.3 + '@types/debug': 4.1.8 + '@xmldom/xmldom': 0.8.10 + debug: 4.3.4 + headers-polyfill: 3.1.2 + outvariant: 1.4.0 + strict-event-emitter: 0.2.8 + web-encoding: 1.1.5 + transitivePeerDependencies: + - supports-color + dev: true + /@napi-rs/cli@2.16.2: resolution: {integrity: sha512-U2aZfnr0s9KkXpZlYC0l5WxWCXL7vJUNpCnWMwq3T9GG9rhYAAUM9CTZsi1Z+0iR2LcHbfq9EfMgoqnuTyUjfg==} engines: {node: '>= 10'} @@ -6659,8 +6692,8 @@ packages: resolution: {integrity: sha512-XQr74QaLeMiqhStEhLn1im9EOMnkypp7MZOwQhGzqp2Weu5eQJbpPxWxixxlYRKWPOmJjsk6qYfYH9kq43yc2w==} dev: true - /@next/react-refresh-utils@13.4.4(react-refresh@0.12.0)(webpack@5.86.0): - resolution: {integrity: sha512-E9LtUbyxSIbkT06is0r6OBT7f2XJVx9xWuZuU0MdHsqn4OPk+D9Qvp2QitnXPXUwCVdu/ZYLiJOO74lnfkWYdw==} + /@next/react-refresh-utils@13.4.15(react-refresh@0.12.0)(webpack@5.86.0): + resolution: {integrity: sha512-CRPdown3t4eO5R2r0CkXSwil2hw3FjR7P7HfJYxIAjrDBlAl6lvTrCCdGSOx5X8qwjY2HWPvARaSSMc4LIcX3Q==} peerDependencies: react-refresh: 0.12.0 webpack: ^4 || ^5 @@ -7129,6 +7162,10 @@ packages: aggregate-error: 3.1.0 dev: true + /@open-draft/until@1.0.3: + resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==} + dev: true + /@opentelemetry/api@1.4.1: resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==} engines: {node: '>=8.0.0'} @@ -7146,6 +7183,17 @@ packages: tslib: 2.5.3 dev: false + /@playwright/test@1.35.1: + resolution: {integrity: sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@types/node': 20.2.5 + playwright-core: 1.35.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /@polka/url@1.0.0-next.11: resolution: {integrity: sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==} @@ -7665,7 +7713,7 @@ packages: '@babel/runtime': 7.16.7 '@types/aria-query': 5.0.1 aria-query: 5.0.0 - chalk: 4.1.0 + chalk: 4.1.2 dom-accessibility-api: 0.5.13 lz-string: 1.5.0 pretty-format: 27.5.1 @@ -7838,6 +7886,10 @@ packages: resolution: {integrity: sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==} dev: true + /@types/cookie@0.4.1: + resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + dev: true + /@types/cross-spawn@6.0.0: resolution: {integrity: sha512-evp2ZGsFw9YKprDbg8ySgC9NA15g3YgiI8ANkGmKKvvi0P2aDGYLPxQIC5qfeKNUOe3TjABVGuah6omPRpIYhg==} dependencies: @@ -7847,6 +7899,12 @@ packages: /@types/debug@4.1.5: resolution: {integrity: sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==} + /@types/debug@4.1.8: + resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} + dependencies: + '@types/ms': 0.7.31 + dev: true + /@types/eslint-scope@3.7.3: resolution: {integrity: sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==} dependencies: @@ -7949,7 +8007,7 @@ packages: resolution: {integrity: sha512-AayK4ZL5ssPzR1OtnOLGAwpT0Dda3Xi/h1G0l1oJDNrowp7T1423q4Zb8/emr7tzRlCy4ssEri0LWVexAqHyKQ==} dependencies: '@types/through': 0.0.30 - rxjs: 7.5.1 + rxjs: 7.5.7 dev: true /@types/istanbul-lib-coverage@2.0.3: @@ -7989,6 +8047,10 @@ packages: pretty-format: 27.5.1 dev: true + /@types/js-levenshtein@1.1.1: + resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==} + dev: true + /@types/jscodeshift@0.11.0: resolution: {integrity: sha512-OcJgr5GznWCEx2Oeg4eMUZYwssTHFj/tU8grrNCKdFQtAEAa0ezDiPHbCdSkyWrRSurXrYbNbHdhxbbB76pXNg==} dependencies: @@ -8068,6 +8130,10 @@ packages: /@types/minimist@1.2.0: resolution: {integrity: sha512-BsF2gEVEIOcbQCSwXR6V14fGD6QLLT0yQBK6RpblkxVYP9x8ANNThpxMUxV7h4KKjqMDR8qELlcnqrEoyvsohw==} + /@types/ms@0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + dev: true + /@types/node-fetch@2.3.2: resolution: {integrity: sha512-yW0EOebSsQme9yKu09XbdDfle4/SmWZMK4dfteWcSLCYNQQcF+YOv0kIrvm+9pO11/ghA4E6A+RNQqvYj4Nr3A==} dependencies: @@ -8198,6 +8264,12 @@ packages: '@types/mime': 2.0.1 dev: true + /@types/set-cookie-parser@2.4.3: + resolution: {integrity: sha512-7QhnH7bi+6KAhBB+Auejz1uV9DHiopZqu7LfR/5gZZTkejJV5nYeZZpgfFoE0N8aDsXuiYpfKyfyMatCwQhyTQ==} + dependencies: + '@types/node': 20.2.5 + dev: true + /@types/sharp@0.29.3: resolution: {integrity: sha512-83Xp05eK2hvfNnmKLr2Fz0C2A0jrr2TnSLqKRbkLTYuAu+Erj6mKQLoEMGafE73Om8p3q3ryZxtHFM/7hy4Adg==} dependencies: @@ -8700,6 +8772,11 @@ packages: '@webassemblyjs/ast': 1.11.6 '@xtuc/long': 4.2.2 + /@xmldom/xmldom@0.8.10: + resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} + engines: {node: '>=10.0.0'} + dev: true + /@xtuc/ieee754@1.2.0: resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -8725,6 +8802,12 @@ packages: - supports-color dev: true + /@zxing/text-encoding@0.9.0: + resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} + requiresBuild: true + dev: true + optional: true + /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -8862,7 +8945,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4 + debug: 4.1.1 transitivePeerDependencies: - supports-color dev: true @@ -8878,7 +8961,7 @@ packages: resolution: {integrity: sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ==} engines: {node: '>= 8.0.0'} dependencies: - debug: 4.3.4 + debug: 4.1.1 depd: 1.1.2 humanize-ms: 1.2.1 transitivePeerDependencies: @@ -9488,7 +9571,7 @@ packages: '@types/babel__core': 7.1.14 babel-plugin-istanbul: 6.1.1 babel-preset-jest: 27.0.6(@babel/core@7.18.0) - chalk: 4.1.2 + chalk: 4.0.0 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: @@ -9781,7 +9864,7 @@ packages: /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: - buffer: 5.7.1 + buffer: 5.6.0 inherits: 2.0.4 readable-stream: 3.6.0 dev: true @@ -10015,13 +10098,6 @@ packages: ieee754: 1.2.1 dev: true - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: @@ -10321,10 +10397,9 @@ packages: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - dev: false - /chalk@4.1.0: - resolution: {integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==} + /chalk@4.1.1: + resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==} engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 @@ -11050,6 +11125,11 @@ packages: engines: {node: '>= 0.6'} dev: true + /cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + dev: true + /cookiejar@2.1.2: resolution: {integrity: sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==} dev: true @@ -11781,7 +11861,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: true /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -12987,9 +13066,9 @@ packages: '@eslint/eslintrc': 0.4.3 '@humanwhocodes/config-array': 0.5.0 ajv: 6.12.6 - chalk: 4.1.2 + chalk: 4.0.0 cross-spawn: 7.0.3 - debug: 4.3.4 + debug: 4.1.1 doctrine: 3.0.0 enquirer: 2.3.6 escape-string-regexp: 4.0.0 @@ -14191,7 +14270,7 @@ packages: /git-spawned-stream@1.0.1: resolution: {integrity: sha512-W2Zo3sCiq5Hqv1/FLsNmGomkXdyimmkHncGzqjBHh7nWx+CbH5dkWGb6CiFdknooL7wfeZJ3gz14KrXl/gotCw==} dependencies: - debug: 4.3.4 + debug: 4.1.1 spawn-to-readstream: 0.1.3 transitivePeerDependencies: - supports-color @@ -14478,6 +14557,11 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /graphql@16.7.1: + resolution: {integrity: sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + dev: true + /growl@1.10.5: resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} engines: {node: '>=4.x'} @@ -14771,6 +14855,10 @@ packages: tslib: 2.5.3 dev: true + /headers-polyfill@3.1.2: + resolution: {integrity: sha512-tWCK4biJ6hcLqTviLXVR9DTRfYGQMXEIUj3gwJ2rZ5wO/at3XtkI4g8mCvFdUF9l1KMBNCfmNAdnahm1cgavQA==} + dev: true + /hex-color-regex@1.1.0: resolution: {integrity: sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==} dev: true @@ -14837,7 +14925,7 @@ packages: '@sidvind/better-ajv-errors': 0.6.10(ajv@6.12.6) acorn-walk: 8.2.0 ajv: 6.12.6 - chalk: 4.1.2 + chalk: 4.0.0 deepmerge: 4.2.2 eslint: 7.32.0 espree: 7.3.1 @@ -14935,7 +15023,7 @@ packages: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.1.1 transitivePeerDependencies: - supports-color dev: true @@ -14990,7 +15078,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.1.1 transitivePeerDependencies: - supports-color dev: true @@ -15293,7 +15381,7 @@ packages: mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 - rxjs: 7.5.1 + rxjs: 7.5.7 string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 @@ -15659,6 +15747,10 @@ packages: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} + /is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + dev: true + /is-npm@4.0.0: resolution: {integrity: sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==} engines: {node: '>=8'} @@ -15984,7 +16076,7 @@ packages: resolution: {integrity: sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==} engines: {node: '>=8'} dependencies: - debug: 4.3.4 + debug: 4.1.1 istanbul-lib-coverage: 3.2.0 source-map: 0.6.1 transitivePeerDependencies: @@ -16013,7 +16105,7 @@ packages: hasBin: true dependencies: async: 3.2.4 - chalk: 4.1.0 + chalk: 4.1.2 filelist: 1.0.4 minimatch: 3.1.2 dev: true @@ -16070,7 +16162,7 @@ packages: '@jest/core': 27.0.6 '@jest/test-result': 27.0.6 '@jest/types': 27.5.1 - chalk: 4.1.2 + chalk: 4.0.0 exit: 0.1.2 graceful-fs: 4.2.11 import-local: 3.0.2 @@ -16100,7 +16192,7 @@ packages: '@jest/test-sequencer': 27.0.6 '@jest/types': 27.5.1 babel-jest: 27.0.6(@babel/core@7.18.0) - chalk: 4.1.2 + chalk: 4.0.0 deepmerge: 4.2.2 glob: 7.2.0 graceful-fs: 4.2.11 @@ -16138,7 +16230,7 @@ packages: resolution: {integrity: sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==} engines: {node: '>= 10.14.2'} dependencies: - chalk: 4.1.2 + chalk: 4.0.0 diff-sequences: 26.6.2 jest-get-type: 26.3.0 pretty-format: 26.6.2 @@ -16148,7 +16240,7 @@ packages: resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - chalk: 4.1.2 + chalk: 4.0.0 diff-sequences: 27.5.1 jest-get-type: 27.5.1 pretty-format: 27.5.1 @@ -16172,7 +16264,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - chalk: 4.1.2 + chalk: 4.0.0 jest-get-type: 27.5.1 jest-util: 27.5.1 pretty-format: 27.5.1 @@ -16284,7 +16376,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 '@types/node': 20.2.5 - chalk: 4.1.2 + chalk: 4.0.0 co: 4.6.0 expect: 27.5.1 is-generator-fn: 2.1.0 @@ -16332,7 +16424,7 @@ packages: resolution: {integrity: sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==} engines: {node: '>= 10.14.2'} dependencies: - chalk: 4.1.2 + chalk: 4.0.0 jest-diff: 26.6.2 jest-get-type: 26.3.0 pretty-format: 26.6.2 @@ -16342,7 +16434,7 @@ packages: resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - chalk: 4.1.2 + chalk: 4.0.0 jest-diff: 27.5.1 jest-get-type: 27.5.1 pretty-format: 27.5.1 @@ -16370,7 +16462,7 @@ packages: '@babel/code-frame': 7.12.11 '@jest/types': 26.6.2 '@types/stack-utils': 2.0.1 - chalk: 4.1.2 + chalk: 4.0.0 graceful-fs: 4.2.11 micromatch: 4.0.5 pretty-format: 26.6.2 @@ -16385,7 +16477,7 @@ packages: '@babel/code-frame': 7.21.4 '@jest/types': 27.5.1 '@types/stack-utils': 2.0.1 - chalk: 4.1.2 + chalk: 4.0.0 graceful-fs: 4.2.11 micromatch: 4.0.5 pretty-format: 27.5.1 @@ -16399,7 +16491,7 @@ packages: '@babel/code-frame': 7.21.4 '@jest/types': 29.5.0 '@types/stack-utils': 2.0.1 - chalk: 4.1.2 + chalk: 4.0.0 graceful-fs: 4.2.11 micromatch: 4.0.5 pretty-format: 29.5.0 @@ -16481,7 +16573,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - chalk: 4.1.2 + chalk: 4.0.0 escalade: 3.1.1 graceful-fs: 4.2.11 jest-pnp-resolver: 1.2.2(jest-resolve@27.0.6) @@ -16496,7 +16588,7 @@ packages: engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: '@jest/types': 27.5.1 - chalk: 4.1.2 + chalk: 4.0.0 graceful-fs: 4.2.11 jest-haste-map: 27.5.1 jest-pnp-resolver: 1.2.2(jest-resolve@27.5.1) @@ -16516,7 +16608,7 @@ packages: '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/node': 20.2.5 - chalk: 4.1.2 + chalk: 4.0.0 emittery: 0.8.1 exit: 0.1.2 graceful-fs: 4.2.11 @@ -16552,7 +16644,7 @@ packages: '@jest/transform': 27.5.1 '@jest/types': 27.5.1 '@types/yargs': 16.0.5 - chalk: 4.1.2 + chalk: 4.0.0 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -16584,7 +16676,7 @@ packages: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - chalk: 4.1.2 + chalk: 4.0.0 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 execa: 5.0.0 @@ -16624,7 +16716,7 @@ packages: '@types/babel__traverse': 7.11.1 '@types/prettier': 2.2.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.0) - chalk: 4.1.2 + chalk: 4.0.0 expect: 27.5.1 graceful-fs: 4.2.11 jest-diff: 27.5.1 @@ -16655,7 +16747,7 @@ packages: '@types/babel__traverse': 7.11.1 '@types/prettier': 2.2.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.0) - chalk: 4.1.2 + chalk: 4.0.0 expect: 27.5.1 graceful-fs: 4.2.11 jest-diff: 27.5.1 @@ -16676,7 +16768,7 @@ packages: dependencies: '@jest/types': 27.5.1 '@types/node': 20.2.5 - chalk: 4.1.2 + chalk: 4.0.0 ci-info: 3.8.0 graceful-fs: 4.2.11 picomatch: 2.3.1 @@ -16687,7 +16779,7 @@ packages: dependencies: '@jest/types': 29.5.0 '@types/node': 20.2.5 - chalk: 4.1.2 + chalk: 4.0.0 ci-info: 3.8.0 graceful-fs: 4.2.11 picomatch: 2.3.1 @@ -16699,7 +16791,7 @@ packages: dependencies: '@jest/types': 27.5.1 camelcase: 6.2.0 - chalk: 4.1.2 + chalk: 4.0.0 jest-get-type: 27.5.1 leven: 3.1.0 pretty-format: 27.5.1 @@ -16711,7 +16803,7 @@ packages: dependencies: '@jest/types': 27.5.1 camelcase: 6.2.0 - chalk: 4.1.2 + chalk: 4.0.0 jest-get-type: 27.5.1 leven: 3.1.0 pretty-format: 27.5.1 @@ -16724,7 +16816,7 @@ packages: '@jest/types': 27.5.1 '@types/node': 20.2.5 ansi-escapes: 4.3.2 - chalk: 4.1.2 + chalk: 4.0.0 jest-util: 27.5.1 string-length: 4.0.1 dev: true @@ -16790,6 +16882,11 @@ packages: resolution: {integrity: sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==} dev: true + /js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + dev: true + /js-sdsl@4.2.0: resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} dev: false @@ -18452,7 +18549,7 @@ packages: /micromark@2.11.4: resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} dependencies: - debug: 4.3.4 + debug: 4.1.1 parse-entities: 2.0.0 transitivePeerDependencies: - supports-color @@ -18462,7 +18559,7 @@ packages: resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==} dependencies: '@types/debug': 4.1.5 - debug: 4.3.4 + debug: 4.1.1 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.0.6 micromark-factory-space: 1.0.0 @@ -18781,6 +18878,42 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /msw@1.2.2(typescript@5.1.3): + resolution: {integrity: sha512-GsW3PE/Es/a1tYThXcM8YHOZ1S1MtivcS3He/LQbbTCx3rbWJYCtWD5XXyJ53KlNPT7O1VI9sCW3xMtgFe8XpQ==} + engines: {node: '>=14'} + hasBin: true + requiresBuild: true + peerDependencies: + typescript: '>= 4.4.x <= 5.1.x' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@mswjs/cookies': 0.2.2 + '@mswjs/interceptors': 0.17.9 + '@open-draft/until': 1.0.3 + '@types/cookie': 0.4.1 + '@types/js-levenshtein': 1.1.1 + chalk: 4.1.1 + chokidar: 3.5.3 + cookie: 0.4.2 + graphql: 16.7.1 + headers-polyfill: 3.1.2 + inquirer: 8.2.0 + is-node-process: 1.2.0 + js-levenshtein: 1.1.6 + node-fetch: 2.6.7 + outvariant: 1.4.0 + path-to-regexp: 6.2.1 + strict-event-emitter: 0.4.6 + type-fest: 2.19.0 + typescript: 5.1.3 + yargs: 17.5.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + /multimatch@2.1.0: resolution: {integrity: sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=} engines: {node: '>=0.10.0'} @@ -19584,6 +19717,10 @@ packages: resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} dev: true + /outvariant@1.4.0: + resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==} + dev: true + /p-cancelable@0.3.0: resolution: {integrity: sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==} engines: {node: '>=4'} @@ -20055,6 +20192,10 @@ packages: resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} dev: true + /path-to-regexp@6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + dev: true + /path-type@1.1.0: resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} engines: {node: '>=0.10.0'} @@ -22937,7 +23078,7 @@ packages: peerDependencies: postcss: 8.x dependencies: - chalk: 4.1.0 + chalk: 4.1.2 concat-with-sourcemaps: 1.1.0 cssnano: 4.1.10 import-cwd: 3.0.0 @@ -23039,17 +23180,10 @@ packages: dependencies: tslib: 1.11.1 - /rxjs@7.5.1: - resolution: {integrity: sha512-KExVEeZWxMZnZhUZtsJcFwz8IvPvgu4G2Z2QyqjZQzUGr32KDYuSxrEYO4w3tFFNbfLozcrKUTvTPi+E9ywJkQ==} - dependencies: - tslib: 2.5.3 - dev: true - /rxjs@7.5.7: resolution: {integrity: sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==} dependencies: tslib: 2.5.3 - dev: false /sade@1.7.4: resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==} @@ -23340,6 +23474,10 @@ packages: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true + /set-cookie-parser@2.6.0: + resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + dev: true + /set-immediate-shim@1.0.1: resolution: {integrity: sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=} engines: {node: '>=0.10.0'} @@ -23547,7 +23685,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.1.1 socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -23830,6 +23968,20 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + /strict-event-emitter@0.2.8: + resolution: {integrity: sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==} + dependencies: + events: 3.3.0 + dev: true + + /strict-event-emitter@0.4.6: + resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==} + dev: true + + /strict-event-emitter@0.5.0: + resolution: {integrity: sha512-sqnMpVJLSB3daNO6FcvsEk4Mq5IJeAwDeH80DP1S8+pgxrF6yZnE1+VeapesGled7nEcIkz1Ax87HzaIy+02kA==} + dev: true + /string-argv@0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} @@ -24986,6 +25138,11 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: true + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -25123,7 +25280,7 @@ packages: resolution: {integrity: sha512-ptXTWUf9HZ2L9xto7tre+hSdSN7M9S0rypUpMAcFhiDYjrXLrND4If+8AZOtPFySKI/Zhfxf7GVAR34BqixDUA==} dependencies: concat-stream: 2.0.0 - debug: 4.3.4 + debug: 4.1.1 fault: 1.0.4 figures: 3.1.0 glob: 7.2.0 @@ -25699,6 +25856,14 @@ packages: defaults: 1.0.3 dev: true + /web-encoding@1.1.5: + resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} + dependencies: + util: 0.12.4 + optionalDependencies: + '@zxing/text-encoding': 0.9.0 + dev: true + /web-namespaces@1.1.4: resolution: {integrity: sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw==} dev: true @@ -25849,7 +26014,7 @@ packages: dev: true /whatwg-url@5.0.0: - resolution: {integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0=} + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 @@ -26229,7 +26394,7 @@ packages: name: '@vercel/turbopack-ecmascript-runtime' version: 0.0.0 dependencies: - '@next/react-refresh-utils': 13.4.4(react-refresh@0.12.0)(webpack@5.86.0) + '@next/react-refresh-utils': 13.4.15(react-refresh@0.12.0)(webpack@5.86.0) '@types/node': 20.2.5 transitivePeerDependencies: - react-refresh