From e021c6f13b37747328ad385db7faf313ccc495e0 Mon Sep 17 00:00:00 2001 From: Arda TANRIKULU Date: Mon, 1 Aug 2022 12:13:49 +0300 Subject: [PATCH] Error Masking for Yoga hooks & Respect result processor in case of an error (#1521) * Respect result processors in case of an error * Prettier Hello? * :)) * CI :)) --- .github/workflows/algolia-integrity.yml | 2 +- .github/workflows/algolia-publish.yml | 2 +- .github/workflows/ci.yml | 2 +- .husky/pre-commit | 2 +- .vscode/settings.json | 4 +- package.json | 18 +- packages/graphql-yoga/__tests__/node.spec.ts | 141 +++++++++++++++- packages/graphql-yoga/src/error.ts | 69 ++++++-- packages/graphql-yoga/src/logger.ts | 8 +- .../src/plugins/resultProcessor/multipart.ts | 9 +- .../src/plugins/resultProcessor/push.ts | 9 +- .../src/plugins/resultProcessor/regular.ts | 23 ++- packages/graphql-yoga/src/server.ts | 72 ++++---- packages/graphql-yoga/src/types.ts | 8 +- ...rmatError.ts => yogaDefaultFormatError.ts} | 14 +- yarn.lock | 159 +++++++++++------- 16 files changed, 387 insertions(+), 155 deletions(-) rename packages/graphql-yoga/src/utils/{formatError.ts => yogaDefaultFormatError.ts} (78%) diff --git a/.github/workflows/algolia-integrity.yml b/.github/workflows/algolia-integrity.yml index a5113e6d86..97e4dde4e5 100644 --- a/.github/workflows/algolia-integrity.yml +++ b/.github/workflows/algolia-integrity.yml @@ -44,7 +44,7 @@ jobs: SITE_URL: https://www.graphql-yoga.com/ - name: Format - run: yarn format + run: yarn prettier - name: Compare run: git diff origin/${{ github.base_ref }}.. -- website/algolia-lockfile.json diff --git a/.github/workflows/algolia-publish.yml b/.github/workflows/algolia-publish.yml index ff242b4bc6..4bd329d9c5 100644 --- a/.github/workflows/algolia-publish.yml +++ b/.github/workflows/algolia-publish.yml @@ -46,7 +46,7 @@ jobs: SITE_URL: https://www.graphql-yoga.com/ - name: Format - run: yarn format + run: yarn prettier - name: Compare run: git diff website/algolia-lockfile.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbe753b529..8559f09bd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -115,4 +115,4 @@ jobs: run: yarn bob check - name: Prettier - run: yarn prettier-check + run: yarn prettier:check diff --git a/.husky/pre-commit b/.husky/pre-commit index 87188b06f9..d2ae35e84b 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -yarn pretty-quick +yarn lint-staged diff --git a/.vscode/settings.json b/.vscode/settings.json index 2316e46353..7acbc20650 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,8 +11,8 @@ "**/node_modules": true, "**/dist": true }, - "prettier.prettierPath": ".yarn/sdks/prettier/index.js", - "typescript.tsdk": ".yarn/sdks/typescript/lib", + "prettier.prettierPath": "node_modules/prettier/index.js", + "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, "files.exclude": { "**/.git": true, diff --git a/package.json b/package.json index cd5af08ac6..cdb7c84a87 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,6 @@ "name": "graphql-yoga-monorepo", "private": true, "scripts": { - "format": "prettier . --write", - "pretty-quick": "pretty-quick --staged", - "prettier-check": "prettier --check .", "prebuild": "rimraf packages/*/dist", "check": "yarn workspaces run check", "build": "yarn workspace @graphql-yoga/graphiql run build && yarn workspace @graphql-yoga/render-graphiql run build && yarn workspace graphql-yoga run generate-graphiql-html && bob build", @@ -14,8 +11,19 @@ "start:docs": "yarn workspace website dev", "postinstall": "patch-package && husky install", "fix-bin": "node scripts/fix-bin.js", + "lint": "eslint --ignore-path .gitignore --ext ts,js,tsx,jsx .", + "prettier": "prettier --ignore-path .prettierignore --write --list-different .", + "prettier:check": "prettier --ignore-path .prettierignore --check .", "build-website": "yarn build && cd website && yarn build" }, + "lint-staged": { + "packages/**/src/**/*.{ts,tsx}": [ + "eslint --fix" + ], + "**/*.{ts,tsx,graphql,yml,md,mdx,json}": [ + "prettier --write" + ] + }, "repository": { "type": "git", "url": "git+https://github.com/dotansimha/graphql-yoga.git" @@ -66,12 +74,12 @@ "babel-plugin-transform-typescript-metadata": "0.3.2", "bob-the-bundler": "3.0.5", "eslint": "^8.15.0", - "graphql": "^16.1.0", + "graphql": "^16.5.0", "husky": "^8.0.0", "jest": "^28.0.0", "patch-package": "^6.4.7", "prettier": "^2.4.1", - "pretty-quick": "^3.1.2", + "lint-staged": "^13.0.3", "rimraf": "^3.0.2", "supertest": "^6.1.6", "ts-jest": "^28.0.0", diff --git a/packages/graphql-yoga/__tests__/node.spec.ts b/packages/graphql-yoga/__tests__/node.spec.ts index 6a811f43e5..2d636c6e1c 100644 --- a/packages/graphql-yoga/__tests__/node.spec.ts +++ b/packages/graphql-yoga/__tests__/node.spec.ts @@ -22,7 +22,7 @@ import { File, FormData, } from '@whatwg-node/fetch' -import { ExecutionResult } from '@graphql-tools/utils' +import { createGraphQLError, ExecutionResult } from '@graphql-tools/utils' import { ServerResponse, Server } from 'http' describe('Disable Introspection with plugin', () => { @@ -54,7 +54,6 @@ describe('Disable Introspection with plugin', () => { expect(response.body.data).toBeNull() expect(response.body.errors![0]).toMatchInlineSnapshot(` Object { - "extensions": Object {}, "locations": Array [ Object { "column": 7, @@ -198,6 +197,28 @@ describe('Masked Error Option', () => { }, }) }) + it('should mask errors from onRequestParse(HTTP hook) with 500', async () => { + const yoga = createYoga({ + schema, + plugins: [ + { + onRequestParse() { + throw new Error('Some random error!') + }, + }, + ], + logging: false, + }) + + const response = await request(yoga).post('/graphql').send({ + query: '{ hi hello }', + }) + + expect(response.statusCode).toBe(500) + + const body = JSON.parse(response.text) + expect(body.errors?.[0]?.message).toBe('Unexpected error.') + }) }) describe('Context error', () => { @@ -305,6 +326,120 @@ describe('Context error', () => { }) }) +describe('HTTP Error Extensions', () => { + it('should respect the status code and headers given with the thrown error during the execution', async () => { + const yoga = createYoga({ + schema: { + typeDefs: /* GraphQL */ ` + type Query { + hello: String + } + `, + resolvers: { + Query: { + hello() { + throw new GraphQLError('Some random error!', { + extensions: { + http: { + status: 401, + headers: { + 'WWW-Authenticate': 'Bearer', + }, + }, + }, + }) + }, + }, + }, + }, + logging: false, + }) + + const response = await request(yoga).post('/graphql').send({ + query: '{ hello }', + }) + + expect(response.statusCode).toBe(401) + expect(response.headers['www-authenticate']).toBe('Bearer') + }) + it('should respect the highest status code if there are many errors thrown with different HTTP status codes', async () => { + const yoga = createYoga({ + schema: { + typeDefs: /* GraphQL */ ` + type Query { + secret: String + inaccessibleOnDb: String + } + `, + resolvers: { + Query: { + secret: () => { + throw createGraphQLError('You cannot access this secret', { + extensions: { + http: { + status: 401, + }, + }, + }) + }, + inaccessibleOnDb: () => { + throw createGraphQLError('DB is not available', { + extensions: { + http: { + status: 503, + }, + }, + }) + }, + }, + }, + }, + }) + + const response = await request(yoga).post('/graphql').send({ + query: '{ secret inaccessibleOnDb }', + }) + + expect(response.statusCode).toBe(503) + + const body = JSON.parse(response.text) + expect(body).toMatchInlineSnapshot(` + Object { + "data": Object { + "inaccessibleOnDb": null, + "secret": null, + }, + "errors": Array [ + Object { + "locations": Array [ + Object { + "column": 3, + "line": 1, + }, + ], + "message": "You cannot access this secret", + "path": Array [ + "secret", + ], + }, + Object { + "locations": Array [ + Object { + "column": 10, + "line": 1, + }, + ], + "message": "DB is not available", + "path": Array [ + "inaccessibleOnDb", + ], + }, + ], + } + `) + }) +}) + it('parse error is sent to clients', async () => { const yoga = createYoga({ logging: false, @@ -328,7 +463,6 @@ it('parse error is sent to clients', async () => { "data": null, "errors": Array [ Object { - "extensions": Object {}, "locations": Array [ Object { "column": 10, @@ -368,7 +502,6 @@ it('validation error is sent to clients', async () => { "data": null, "errors": Array [ Object { - "extensions": Object {}, "locations": Array [ Object { "column": 2, diff --git a/packages/graphql-yoga/src/error.ts b/packages/graphql-yoga/src/error.ts index aa695a2568..690ae4f076 100644 --- a/packages/graphql-yoga/src/error.ts +++ b/packages/graphql-yoga/src/error.ts @@ -1,5 +1,8 @@ +import { UseMaskedErrorsOpts } from '@envelop/core' import { createGraphQLError } from '@graphql-tools/utils' -import { GraphQLError } from 'graphql' +import { GraphQLError, GraphQLHTTPErrorExtensions } from 'graphql' +import { ResultProcessorInput } from './plugins/types' +import { YogaMaskedErrorOpts } from './types' declare module 'graphql' { interface GraphQLHTTPErrorExtensions { @@ -21,22 +24,66 @@ function hasToString(obj: any): obj is { toString(): string } { export function handleError( error: unknown, + maskedErrorsOpts: YogaMaskedErrorOpts | null, errors: GraphQLError[] = [], ): GraphQLError[] { if (isAggregateError(error)) { for (const singleError of error.errors) { - errors.push(...handleError(singleError)) + errors.push(...handleError(singleError, maskedErrorsOpts)) } - } else if (error instanceof GraphQLError) { - errors.push(error) - } else if (error instanceof Error) { - errors.push(createGraphQLError(error.message)) - } else if (typeof error === 'string') { - errors.push(createGraphQLError(error)) - } else if (hasToString(error)) { - errors.push(createGraphQLError(error.toString())) + } else if (maskedErrorsOpts) { + const maskedError = maskedErrorsOpts.formatError( + error, + maskedErrorsOpts.errorMessage, + maskedErrorsOpts.isDev, + ) + errors.push(maskedError) } else { - errors.push(createGraphQLError('Unexpected error!')) + if (error instanceof GraphQLError) { + errors.push(error) + } + if (error instanceof Error) { + errors.push(createGraphQLError(error.message)) + } else if (typeof error === 'string') { + errors.push(createGraphQLError(error)) + } else if (hasToString(error)) { + errors.push(createGraphQLError(error.toString())) + } else { + errors.push(createGraphQLError('Unexpected error!')) + } } return errors } + +export function getResponseInitByRespectingErrors( + result: ResultProcessorInput, + headers: Record = {}, +) { + let status: number | undefined + + if ('errors' in result && result.errors?.length) { + for (const error of result.errors) { + if (error.extensions?.http) { + if ( + error.extensions.http.status && + (!status || error.extensions.http.status > status) + ) { + status = error.extensions.http.status + } + if (error.extensions.http.headers) { + Object.assign(headers, error.extensions.http.headers) + } + // Remove http extensions from the final response + delete error.extensions.http + //TODO: avoid slow "delete" + } + } + } else { + status = 200 + } + + return { + status, + headers, + } +} diff --git a/packages/graphql-yoga/src/logger.ts b/packages/graphql-yoga/src/logger.ts index 94cac3b6b6..38a4dd7eca 100644 --- a/packages/graphql-yoga/src/logger.ts +++ b/packages/graphql-yoga/src/logger.ts @@ -41,13 +41,7 @@ export interface YogaLogger { error: (...args: any[]) => void } -const isDebug = () => - typeof process === 'object' - ? process.env.DEBUG - : // @ts-expect-error - typeof DEBUG !== 'undefined' - ? true - : false +const isDebug = () => !!globalThis.process?.env?.DEBUG const prefix = [LEVEL_COLOR.title, `🧘 Yoga -`, LEVEL_COLOR.reset] diff --git a/packages/graphql-yoga/src/plugins/resultProcessor/multipart.ts b/packages/graphql-yoga/src/plugins/resultProcessor/multipart.ts index 58675eaa9a..5f943d21cc 100644 --- a/packages/graphql-yoga/src/plugins/resultProcessor/multipart.ts +++ b/packages/graphql-yoga/src/plugins/resultProcessor/multipart.ts @@ -1,5 +1,6 @@ import { isAsyncIterable } from '@envelop/core' import { ExecutionResult } from 'graphql' +import { getResponseInitByRespectingErrors } from '../../error.js' import { FetchAPI } from '../../types.js' import { ResultProcessorInput } from '../types.js' @@ -12,15 +13,13 @@ export function processMultipartResult( result: ResultProcessorInput, fetchAPI: FetchAPI, ): Response { - const headersInit: HeadersInit = { + const headersInit = { Connection: 'keep-alive', 'Content-Type': 'multipart/mixed; boundary="-"', 'Transfer-Encoding': 'chunked', } - const responseInit: ResponseInit = { - headers: headersInit, - status: 200, - } + + const responseInit = getResponseInitByRespectingErrors(result, headersInit) let iterator: AsyncIterator> diff --git a/packages/graphql-yoga/src/plugins/resultProcessor/push.ts b/packages/graphql-yoga/src/plugins/resultProcessor/push.ts index 9d46613d6e..6337ed38d3 100644 --- a/packages/graphql-yoga/src/plugins/resultProcessor/push.ts +++ b/packages/graphql-yoga/src/plugins/resultProcessor/push.ts @@ -1,5 +1,6 @@ import { isAsyncIterable } from '@envelop/core' import { ExecutionResult } from 'graphql' +import { getResponseInitByRespectingErrors } from '../../error.js' import { FetchAPI } from '../../types.js' import { ResultProcessorInput } from '../types.js' @@ -12,16 +13,14 @@ export function processPushResult( result: ResultProcessorInput, fetchAPI: FetchAPI, ): Response { - const headersInit: HeadersInit = { + const headersInit = { 'Content-Type': 'text/event-stream', Connection: 'keep-alive', 'Cache-Control': 'no-cache', 'Content-Encoding': 'none', } - const responseInit: ResponseInit = { - headers: headersInit, - status: 200, - } + + const responseInit = getResponseInitByRespectingErrors(result, headersInit) let iterator: AsyncIterator> diff --git a/packages/graphql-yoga/src/plugins/resultProcessor/regular.ts b/packages/graphql-yoga/src/plugins/resultProcessor/regular.ts index e48b69885c..5242a84539 100644 --- a/packages/graphql-yoga/src/plugins/resultProcessor/regular.ts +++ b/packages/graphql-yoga/src/plugins/resultProcessor/regular.ts @@ -1,4 +1,5 @@ import { isAsyncIterable } from '@graphql-tools/utils' +import { getResponseInitByRespectingErrors } from '../../error.js' import { FetchAPI } from '../../types.js' import { ResultProcessorInput } from '../types.js' @@ -34,17 +35,21 @@ export function processRegularResult( executionResult: ResultProcessorInput, fetchAPI: FetchAPI, ): Response { - const textEncoder = new fetchAPI.TextEncoder() - const responseBody = JSON.stringify(executionResult) - const decodedString = textEncoder.encode(responseBody) const contentType = acceptHeaderByResult.get(executionResult) - const headersInit: HeadersInit = { + const headersInit = { 'Content-Type': contentType || 'application/json', - 'Content-Length': decodedString.byteLength.toString(), - } - const responseInit: ResponseInit = { - headers: headersInit, - status: 200, } + + const responseInit = getResponseInitByRespectingErrors( + executionResult, + headersInit, + ) + + const textEncoder = new fetchAPI.TextEncoder() + const responseBody = JSON.stringify(executionResult) + const decodedString = textEncoder.encode(responseBody) + + headersInit['Content-Length'] = decodedString.byteLength.toString() + return new fetchAPI.Response(decodedString, responseInit) } diff --git a/packages/graphql-yoga/src/server.ts b/packages/graphql-yoga/src/server.ts index 84bde881cb..64bf0dd845 100644 --- a/packages/graphql-yoga/src/server.ts +++ b/packages/graphql-yoga/src/server.ts @@ -18,6 +18,7 @@ import { YogaInitialContext, FetchAPI, GraphQLParams, + YogaMaskedErrorOpts, } from './types.js' import { OnRequestHook, @@ -81,7 +82,7 @@ import { useCheckGraphQLQueryParam } from './plugins/requestValidation/useCheckG import { useHTTPValidationError } from './plugins/requestValidation/useHTTPValidationError.js' import { usePreventMutationViaGET } from './plugins/requestValidation/usePreventMutationViaGET.js' import { useUnhandledRoute } from './plugins/useUnhandledRoute.js' -import { formatError } from './utils/formatError.js' +import { yogaDefaultFormatError } from './utils/yogaDefaultFormatError.js' interface OptionsWithPlugins { /** @@ -242,6 +243,7 @@ export class YogaServer< private onRequestHooks: OnRequestHook[] private onResultProcessHooks: OnResultProcess[] private onResponseHooks: OnResponseHook[] + private maskedErrorsOpts: YogaMaskedErrorOpts | null private id: string constructor( @@ -275,11 +277,13 @@ export class YogaServer< } : logger - const maskedErrorsOpts: UseMaskedErrorsOpts | null = + this.maskedErrorsOpts = options?.maskedErrors === false ? null : { - formatError, + formatError: yogaDefaultFormatError, + errorMessage: 'Unexpected error.', + isDev: globalThis.process?.env?.NODE_ENV === 'development', ...(typeof options?.maskedErrors === 'object' ? options.maskedErrors : {}), @@ -406,7 +410,7 @@ export class YogaServer< useHTTPValidationError(), // We make sure that the user doesn't send a mutation with GET usePreventMutationViaGET(), - maskedErrorsOpts != null && useMaskedErrors(maskedErrorsOpts), + this.maskedErrorsOpts != null && useMaskedErrors(this.maskedErrorsOpts), useUnhandledRoute({ graphqlEndpoint: this.graphqlEndpoint, showLandingPage: options?.landingPage ?? true, @@ -532,42 +536,18 @@ export class YogaServer< return response } catch (error: unknown) { - const finalResponseInit = { - status: 200, - headers: { - 'Content-Type': 'application/json', - }, - } - - const errors = handleError(error) - for (const error of errors) { - if (error.extensions?.http) { - if ( - error.extensions.http.status && - error.extensions?.http.status > finalResponseInit.status - ) { - finalResponseInit.status = error.extensions.http.status - } - if (error.extensions.http.headers) { - Object.assign( - finalResponseInit.headers, - error.extensions.http.headers, - ) - } - // Remove http extensions from the final response - error.extensions.http = undefined - } - } + const errors = handleError(error, this.maskedErrorsOpts) - const payload: ExecutionResult = { + const result: ExecutionResult = { data: null, errors, } - const textEncoder = new this.fetchAPI.TextEncoder() - const decodedString = textEncoder.encode(JSON.stringify(payload)) - finalResponseInit.headers['Content-Length'] = - decodedString.byteLength.toString() - return new this.fetchAPI.Response(decodedString, finalResponseInit) + return processResult({ + request, + result, + fetchAPI: this.fetchAPI, + onResultProcessHooks: this.onResultProcessHooks, + }) } } @@ -577,16 +557,22 @@ export class YogaServer< ? [serverContext?: TServerContext | undefined] : [serverContext: TServerContext] ) => { - const response = await this.getResponse(request, ...args) + try { + const response = await this.getResponse(request, ...args) - for (const onResponseHook of this.onResponseHooks) { - await onResponseHook({ - request, - response, - serverContext: args[0], + for (const onResponseHook of this.onResponseHooks) { + await onResponseHook({ + request, + response, + serverContext: args[0], + }) + } + return response + } catch (e) { + return new this.fetchAPI.Response('Internal Server Error', { + status: 500, }) } - return response } /** diff --git a/packages/graphql-yoga/src/types.ts b/packages/graphql-yoga/src/types.ts index 4ba83d5c30..f1c9a7d1f5 100644 --- a/packages/graphql-yoga/src/types.ts +++ b/packages/graphql-yoga/src/types.ts @@ -1,6 +1,6 @@ import type { DocumentNode, GraphQLError } from 'graphql' import type { TypedDocumentNode } from '@graphql-typed-document-node/core' -import { PromiseOrValue } from '@envelop/core' +import { FormatErrorHandler, PromiseOrValue } from '@envelop/core' import { createFetch } from '@whatwg-node/fetch' export interface ExecutionPatchResult< @@ -88,3 +88,9 @@ export interface FetchEvent extends Event { request: Request respondWith(response: PromiseOrValue): void } + +export type YogaMaskedErrorOpts = { + formatError: FormatErrorHandler + errorMessage: string + isDev: boolean +} diff --git a/packages/graphql-yoga/src/utils/formatError.ts b/packages/graphql-yoga/src/utils/yogaDefaultFormatError.ts similarity index 78% rename from packages/graphql-yoga/src/utils/formatError.ts rename to packages/graphql-yoga/src/utils/yogaDefaultFormatError.ts index 87d3a08029..10fcd8bd8e 100644 --- a/packages/graphql-yoga/src/utils/formatError.ts +++ b/packages/graphql-yoga/src/utils/yogaDefaultFormatError.ts @@ -2,7 +2,11 @@ import { FormatErrorHandler } from '@envelop/core' import { createGraphQLError } from '@graphql-tools/utils' import { GraphQLError } from 'graphql' -export const formatError: FormatErrorHandler = (err, message, isDev) => { +export const yogaDefaultFormatError: FormatErrorHandler = ( + err, + message, + isDev, +) => { if (err instanceof GraphQLError) { if (err.originalError) { if (err.originalError.name === 'GraphQLError') { @@ -28,5 +32,11 @@ export const formatError: FormatErrorHandler = (err, message, isDev) => { } return err } - return new GraphQLError(message) + return createGraphQLError(message, { + extensions: { + http: { + status: 500, + }, + }, + }) } diff --git a/yarn.lock b/yarn.lock index 34b5d35a5b..788653c70a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5989,7 +5989,7 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/minimatch@*", "@types/minimatch@^3.0.3": +"@types/minimatch@*": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== @@ -6795,7 +6795,7 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-escapes@^4.2.1, ansi-escapes@^4.3.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.1: version "4.3.2" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== @@ -7089,11 +7089,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-differ@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" - integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -8283,6 +8278,22 @@ cli-truncate@^0.2.1: slice-ansi "0.0.4" string-width "^1.0.1" +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-truncate@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== + dependencies: + slice-ansi "^5.0.0" + string-width "^5.0.0" + cli-width@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" @@ -8430,6 +8441,11 @@ color@^4.0.1: color-convert "^2.0.1" color-string "^1.9.0" +colorette@^2.0.16, colorette@^2.0.17: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + colorette@^2.0.7: version "2.0.18" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.18.tgz#4c260bcf86437ce94fa58e2e49a83b623f3c4d66" @@ -8847,7 +8863,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -10567,21 +10583,6 @@ eventsource@2.0.2: resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-2.0.2.tgz#76dfcc02930fb2ff339520b6d290da573a9e8508" integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA== -execa@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - execa@^5.0.0, execa@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -10597,7 +10598,7 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^6.0.0: +execa@^6.0.0, execa@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-6.1.0.tgz#cea16dee211ff011246556388effa0818394fb20" integrity sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA== @@ -12396,11 +12397,6 @@ human-id@^1.0.2: resolved "https://registry.yarnpkg.com/human-id/-/human-id-1.0.2.tgz#e654d4b2b0d8b07e45da9f6020d8af17ec0a5df3" integrity sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw== -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -12476,7 +12472,7 @@ ignore@^4.0.3: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0: +ignore@^5.1.8, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== @@ -14222,7 +14218,7 @@ light-my-request@^5.0.0: process-warning "^1.0.0" set-cookie-parser "^2.4.1" -lilconfig@^2.0.5: +lilconfig@2.0.5, lilconfig@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== @@ -14239,6 +14235,25 @@ linkify-it@^3.0.1: dependencies: uc.micro "^1.0.1" +lint-staged@^13.0.3: + version "13.0.3" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.3.tgz#d7cdf03a3830b327a2b63c6aec953d71d9dc48c6" + integrity sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug== + dependencies: + cli-truncate "^3.1.0" + colorette "^2.0.17" + commander "^9.3.0" + debug "^4.3.4" + execa "^6.1.0" + lilconfig "2.0.5" + listr2 "^4.0.5" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-inspect "^1.12.2" + pidtree "^0.6.0" + string-argv "^0.3.1" + yaml "^2.1.1" + listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" @@ -14268,6 +14283,20 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" +listr2@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5" + integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA== + dependencies: + cli-truncate "^2.1.0" + colorette "^2.0.16" + log-update "^4.0.0" + p-map "^4.0.0" + rfdc "^1.3.0" + rxjs "^7.5.5" + through "^2.3.8" + wrap-ansi "^7.0.0" + listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" @@ -14522,6 +14551,16 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + log-update@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/log-update/-/log-update-5.0.1.tgz#9e928bf70cb183c1f0c9e91d9e6b7115d597ce09" @@ -15455,7 +15494,7 @@ micromatch@^3.1.10: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -15712,7 +15751,7 @@ move-file@^3.0.0: dependencies: path-exists "^5.0.0" -mri@1.2.0, mri@^1.1.0, mri@^1.1.5: +mri@1.2.0, mri@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== @@ -15737,17 +15776,6 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multimatch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" - integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ== - dependencies: - "@types/minimatch" "^3.0.3" - array-differ "^3.0.0" - array-union "^2.1.0" - arrify "^2.0.1" - minimatch "^3.0.4" - multiparty@^4.2.1: version "4.2.3" resolved "https://registry.yarnpkg.com/multiparty/-/multiparty-4.2.3.tgz#6b14981badb5ad3f0929622868751810368d4633" @@ -16311,7 +16339,7 @@ npm-packlist@^1.1.6: npm-bundled "^1.0.1" npm-normalize-package-bin "^1.0.1" -npm-run-path@^4.0.0, npm-run-path@^4.0.1: +npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -16389,6 +16417,11 @@ object-inspect@^1.12.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== +object-inspect@^1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + object-is@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" @@ -17125,6 +17158,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pidtree@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -17451,18 +17489,6 @@ pretty-ms@7.0.1, pretty-ms@^7.0.0: dependencies: parse-ms "^2.1.0" -pretty-quick@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-3.1.3.tgz#15281108c0ddf446675157ca40240099157b638e" - integrity sha512-kOCi2FJabvuh1as9enxYmrnBC6tVMoVOenMaBqRfsvBHB0cbpYHjdQEpSglpASDFEXVwplpcGR4CLEaisYAFcA== - dependencies: - chalk "^3.0.0" - execa "^4.0.0" - find-up "^4.1.0" - ignore "^5.1.4" - mri "^1.1.5" - multimatch "^4.0.0" - prettyjson@^1.2.1: version "1.2.5" resolved "https://registry.yarnpkg.com/prettyjson/-/prettyjson-1.2.5.tgz#ef3cfffcc70505c032abc59785884b4027031835" @@ -18968,6 +18994,15 @@ slice-ansi@0.0.4: resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" @@ -19348,6 +19383,11 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +string-argv@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + string-env-interpolation@1.0.1, string-env-interpolation@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" @@ -21611,6 +21651,11 @@ yaml@^2.0.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.1.tgz#71886d6021f3da28169dbefde78d4dd0f8d83650" integrity sha512-1NpAYQ3wjzIlMs0mgdBmYzLkFgWBIWrzYVDYfrixhoFNNgJ444/jT2kUT2sicRbJES3oQYRZugjB6Ro8SjKeFg== +yaml@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" + integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== + yargs-parser@^18.1.1, yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"