0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-01-20 21:32:31 -05:00

refactor(cloud,core): report exception to Applicaiton Insights

when status code >= 500 or other unhandled expcetions occurred.
This commit is contained in:
Gao Sun 2023-03-18 02:11:28 +08:00
parent 6344c4defb
commit d27499c553
No known key found for this signature in database
GPG key ID: 13EBE123E4773688
14 changed files with 292 additions and 32 deletions

View file

@ -48,7 +48,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,enable=${{ startsWith(github.ref, 'refs/tags/v') }},value=prerelease
type=raw,enable=${{ inputs.target == 'prod' }},value=prod
type=sha
type=edge
- name: Login to DockerHub
@ -111,7 +111,7 @@ jobs:
ghcr.io/logto-io/cloud
# https://github.com/docker/metadata-action
tags: |
type=raw,enable=${{ inputs.target == 'prod' }},value=prod
type=sha
type=edge
- name: Login to GitHub Container Registry
@ -172,13 +172,13 @@ jobs:
uses: azure/webapps-deploy@v2
with:
app-name: ${{ vars.APP_NAME_CORE }}
images: ghcr.io/logto-io/logto:${{ vars.DOCKER_TAG }}
images: ghcr.io/logto-io/logto:sha-${{ github.sha }}
- name: Deploy cloud to containerapp
uses: azure/webapps-deploy@v2
with:
app-name: ${{ vars.APP_NAME_CLOUD }}
images: ghcr.io/logto-io/cloud:${{ vars.DOCKER_TAG }}
images: ghcr.io/logto-io/cloud:sha-${{ github.sha }}
# Publish packages and create git tags if needed
publish-and-tag:

View file

@ -32,6 +32,7 @@
"@withtyped/postgres": "^0.8.1",
"@withtyped/server": "^0.8.1",
"accepts": "^1.3.8",
"applicationinsights": "^2.5.0",
"chalk": "^5.0.0",
"decamelize": "^6.0.0",
"dotenv": "^16.0.0",

View file

@ -6,10 +6,14 @@ import { findUp } from 'find-up';
dotenv.config({ path: await findUp('.env', {}) });
const { appInsights } = await import('@logto/shared/app-insights');
appInsights.setup('logto-cloud-eu');
const { default: withAuth } = await import('./middleware/with-auth.js');
const { default: withHttpProxy } = await import('./middleware/with-http-proxy.js');
const { default: withPathname } = await import('./middleware/with-pathname.js');
const { default: withSpa } = await import('./middleware/with-spa.js');
const { default: withErrorReport } = await import('./middleware/with-error-report.js');
const { EnvSet } = await import('./env-set/index.js');
const { default: router } = await import('./routes/index.js');
@ -19,7 +23,9 @@ const ignorePathnames = ['/api'];
const { listen } = createServer({
port: 3003,
composer: compose(withRequest())
composer: compose()
.and(withErrorReport())
.and(withRequest())
.and(anonymousRouter.routes())
.and(
withPathname(

View file

@ -0,0 +1,18 @@
import { appInsights } from '@logto/shared/app-insights';
import { tryThat } from '@silverhand/essentials';
import type { BaseContext, NextFunction } from '@withtyped/server';
import { RequestError } from '@withtyped/server';
/**
* Build a middleware function that reports error to Azure Application Insights.
*/
export default function withErrorReport<InputContext extends BaseContext>() {
return async (context: InputContext, next: NextFunction<InputContext>) => {
await tryThat(next(context), (error) => {
if (!(error instanceof RequestError && error.status < 500)) {
appInsights.trackException(error);
}
throw error;
});
};
}

View file

@ -36,6 +36,7 @@
"@logto/schemas": "workspace:*",
"@logto/shared": "workspace:*",
"@silverhand/essentials": "^2.5.0",
"applicationinsights": "^2.5.0",
"aws-sdk": "^2.1329.0",
"chalk": "^5.0.0",
"clean-deep": "^3.4.0",

View file

@ -1,7 +1,8 @@
import fs from 'fs/promises';
import http2 from 'http2';
import { toTitle } from '@silverhand/essentials';
import { appInsights } from '@logto/shared/app-insights';
import { toTitle, trySafe } from '@silverhand/essentials';
import chalk from 'chalk';
import type Koa from 'koa';
@ -19,21 +20,9 @@ const logListening = (type: 'core' | 'admin' = 'core') => {
const serverTimeout = 120_000;
const getTenant = async (tenantId: string) => {
try {
return await tenantPool.get(tenantId);
} catch (error: unknown) {
if (error instanceof TenantNotFoundError) {
return error;
}
throw error;
}
};
export default async function initApp(app: Koa): Promise<void> {
app.use(async (ctx, next) => {
if (EnvSet.values.isDomainBasedMultiTenancy && ctx.URL.pathname === '/status') {
if (EnvSet.values.isDomainBasedMultiTenancy && ['/status', '/'].includes(ctx.URL.pathname)) {
ctx.status = 204;
return next();
@ -47,11 +36,16 @@ export default async function initApp(app: Koa): Promise<void> {
return next();
}
const tenant = await getTenant(tenantId);
if (tenant instanceof TenantNotFoundError) {
ctx.status = 404;
const tenant = await trySafe(tenantPool.get(tenantId), (error) => {
if (error instanceof TenantNotFoundError) {
ctx.status = 404;
} else {
ctx.status = 500;
appInsights.trackException(error);
}
});
if (!tenant) {
return next();
}
@ -61,6 +55,8 @@ export default async function initApp(app: Koa): Promise<void> {
tenant.requestEnd();
} catch (error: unknown) {
tenant.requestEnd();
appInsights.trackException(error);
throw error;
}
});

View file

@ -33,6 +33,7 @@ export default class RequestError extends Error {
super(message);
this.name = 'RequestError';
this.expose = expose;
this.code = code;
this.status = status;

View file

@ -1,3 +1,28 @@
// Needs to standardize
export default class ServerError extends Error {}
import type { ZodError } from 'zod';
export default class ServerError extends Error {
constructor(public readonly message: string) {
super(message);
this.name = 'ServerError';
}
}
export class StatusCodeError extends ServerError {
constructor(public readonly expect: number | number[], public readonly received: number) {
super(
`Guard response status failed: Expected ${
Array.isArray(expect) ? expect.join(', ') : expect
}, but received ${received}.`
);
this.name = 'StatusCodeError';
}
}
export class ResponseBodyError extends ServerError {
constructor(public readonly cause: ZodError) {
super(`Guard response body failed: ${cause.message}`);
this.name = 'ResponseBodyError';
}
}

View file

@ -8,6 +8,9 @@ import SystemContext from './tenants/SystemContext.js';
dotenv.config({ path: await findUp('.env', {}) });
const { appInsights } = await import('@logto/shared/app-insights');
appInsights.setup('logto-cloud-eu');
// Import after env has been configured
const { loadConnectorFactories } = await import('./utils/connectors/index.js');
const { EnvSet } = await import('./env-set/index.js');

View file

@ -1,4 +1,5 @@
import type { RequestErrorBody } from '@logto/schemas';
import { appInsights } from '@logto/shared/app-insights';
import type { Middleware } from 'koa';
import { HttpError } from 'koa';
@ -22,9 +23,16 @@ export default function koaErrorHandler<StateT, ContextT, BodyT>(): Middleware<
ctx.status = error.status;
ctx.body = error.body;
if (error.status >= 500) {
appInsights.trackException(error);
}
return;
}
// Report unhandled exceptions
appInsights.trackException(error);
// Koa will handle `HttpError` with a built-in manner.
if (error instanceof HttpError) {
return;

View file

@ -7,7 +7,7 @@ import type { ZodType, ZodTypeDef } from 'zod';
import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import ServerError from '#src/errors/ServerError/index.js';
import { ResponseBodyError, StatusCodeError } from '#src/errors/ServerError/index.js';
import assertThat from '#src/utils/assert-that.js';
/** Configure what and how to guard. */
@ -158,7 +158,7 @@ export default function koaGuard<
Array.isArray(status)
? status.includes(ctx.response.status)
: status === ctx.response.status,
new ServerError()
new StatusCodeError(status, ctx.response.status)
);
}
@ -169,7 +169,8 @@ export default function koaGuard<
if (!EnvSet.values.isProduction) {
console.error('Invalid response:', result.error);
}
throw new ServerError();
throw new ResponseBodyError(result.error);
}
}
};

View file

@ -14,6 +14,9 @@
},
"./esm": {
"import": "./lib/esm/index.js"
},
"./app-insights": {
"import": "./lib/app-insights/index.js"
}
},
"publishConfig": {
@ -61,5 +64,8 @@
"find-up": "^6.3.0",
"nanoid": "^4.0.0",
"slonik": "^30.0.0"
},
"optionalDependencies": {
"applicationinsights": "^2.5.0"
}
}

View file

@ -0,0 +1,42 @@
import { trySafe } from '@silverhand/essentials';
import type { TelemetryClient } from 'applicationinsights';
import applicationinsights from 'applicationinsights';
export const normalizeError = (error: unknown) => {
const normalized = error instanceof Error ? error : new Error(String(error));
// Add message if empty otherwise Application Insights will respond 400
// and the error will not be recorded.
// eslint-disable-next-line @silverhand/fp/no-mutation
normalized.message ||= 'Error occurred.';
return normalized;
};
class AppInsights {
client?: TelemetryClient;
setup(cloudRole: string): boolean {
if (this.client) {
return true;
}
const ok = trySafe(() => applicationinsights.setup());
if (!ok) {
return false;
}
this.client = applicationinsights.defaultClient;
this.client.context.tags[this.client.context.keys.cloudRole] = cloudRole;
applicationinsights.start();
return true;
}
trackException(error: unknown) {
this.client?.trackException({ exception: normalizeError(error) });
}
}
export const appInsights = new AppInsights();

162
pnpm-lock.yaml generated
View file

@ -127,6 +127,7 @@ importers:
'@withtyped/postgres': ^0.8.1
'@withtyped/server': ^0.8.1
accepts: ^1.3.8
applicationinsights: ^2.5.0
chalk: ^5.0.0
decamelize: ^6.0.0
dotenv: ^16.0.0
@ -152,6 +153,7 @@ importers:
'@withtyped/postgres': 0.8.1_@withtyped+server@0.8.1
'@withtyped/server': 0.8.1
accepts: 1.3.8
applicationinsights: 2.5.0
chalk: 5.1.2
decamelize: 6.0.0
dotenv: 16.0.0
@ -365,6 +367,7 @@ importers:
'@types/semver': ^7.3.12
'@types/sinon': ^10.0.13
'@types/supertest': ^2.0.11
applicationinsights: ^2.5.0
aws-sdk: ^2.1329.0
chalk: ^5.0.0
clean-deep: ^3.4.0
@ -428,6 +431,7 @@ importers:
'@logto/schemas': link:../schemas
'@logto/shared': link:../shared
'@silverhand/essentials': 2.5.0
applicationinsights: 2.5.0
aws-sdk: 2.1329.0
chalk: 5.1.2
clean-deep: 3.4.0
@ -736,6 +740,7 @@ importers:
'@silverhand/ts-config': 2.0.3
'@types/jest': ^29.4.0
'@types/node': ^18.11.18
applicationinsights: ^2.5.0
chalk: ^5.0.0
eslint: ^8.34.0
find-up: ^6.3.0
@ -753,6 +758,8 @@ importers:
find-up: 6.3.0
nanoid: 4.0.0
slonik: 30.1.2
optionalDependencies:
applicationinsights: 2.5.0
devDependencies:
'@logto/connector-kit': link:../toolkit/connector-kit
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
@ -1065,6 +1072,24 @@ packages:
tslib: 2.4.1
dev: false
/@azure/core-rest-pipeline/1.10.2:
resolution: {integrity: sha512-e3WzAsRKLor5EgK2bQqR1OY5D7VBqzORHtlqtygZZQGCYOIBsynqrZBa8MFD1Ue9r8TPtofOLditalnlQHS45Q==}
engines: {node: '>=14.0.0'}
dependencies:
'@azure/abort-controller': 1.1.0
'@azure/core-auth': 1.4.0
'@azure/core-tracing': 1.0.1
'@azure/core-util': 1.2.0
'@azure/logger': 1.0.4
form-data: 4.0.0
http-proxy-agent: 5.0.0
https-proxy-agent: 5.0.1
tslib: 2.4.1
uuid: 8.3.2
transitivePeerDependencies:
- supports-color
dev: false
/@azure/core-tracing/1.0.0-preview.13:
resolution: {integrity: sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==}
engines: {node: '>=12.0.0'}
@ -1073,6 +1098,13 @@ packages:
tslib: 2.4.1
dev: false
/@azure/core-tracing/1.0.1:
resolution: {integrity: sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==}
engines: {node: '>=12.0.0'}
dependencies:
tslib: 2.4.1
dev: false
/@azure/core-util/1.2.0:
resolution: {integrity: sha512-ffGIw+Qs8bNKNLxz5UPkz4/VBM/EZY07mPve1ZYFqYUdPwFqRj0RPk0U7LZMOfT7GCck9YjuT1Rfp1PApNl1ng==}
engines: {node: '>=14.0.0'}
@ -2546,6 +2578,10 @@ packages:
resolution: {integrity: sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==}
dev: true
/@microsoft/applicationinsights-web-snippet/1.0.1:
resolution: {integrity: sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==}
dev: false
/@mischnic/json-sourcemap/0.1.0:
resolution: {integrity: sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA==}
engines: {node: '>=12.0.0'}
@ -2629,6 +2665,44 @@ packages:
engines: {node: '>=8.0.0'}
dev: false
/@opentelemetry/core/1.10.0_@opentelemetry+api@1.4.0:
resolution: {integrity: sha512-H5/mfU3TsEBe/cnnLu3VCkzjqyRARmhxQGsT64KwafxjzkDh+c2Bk4n140Cg/xhgrjK2sFsxbJj6d0xZlVo/OQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.5.0'
dependencies:
'@opentelemetry/api': 1.4.0
'@opentelemetry/semantic-conventions': 1.10.0
dev: false
/@opentelemetry/resources/1.10.0_@opentelemetry+api@1.4.0:
resolution: {integrity: sha512-bh4auHOdS0/cwSgviCPbkItLwLZRWCZKp/ns2soVwlWQMJH36FIHbcYJf7G9+Rthlc6u163VhUefho+eDrPVeA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.5.0'
dependencies:
'@opentelemetry/api': 1.4.0
'@opentelemetry/core': 1.10.0_@opentelemetry+api@1.4.0
'@opentelemetry/semantic-conventions': 1.10.0
dev: false
/@opentelemetry/sdk-trace-base/1.10.0_@opentelemetry+api@1.4.0:
resolution: {integrity: sha512-X4rRShtVQ893LCU4GNKS1TKFua9nSjVmo0VJvigfSFSOmyyOLfiyTWmVL9MKV7Ws0HqLOIWJixJY0x28fw3Tzg==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.5.0'
dependencies:
'@opentelemetry/api': 1.4.0
'@opentelemetry/core': 1.10.0_@opentelemetry+api@1.4.0
'@opentelemetry/resources': 1.10.0_@opentelemetry+api@1.4.0
'@opentelemetry/semantic-conventions': 1.10.0
dev: false
/@opentelemetry/semantic-conventions/1.10.0:
resolution: {integrity: sha512-Mzo5IyrI59YuYWeNoOZRXfUCc3upjmxCmczSm+pUgWprvSNfdOX70SVde84UxmuzU7MF1MEkPXKXTYG3ymRw2w==}
engines: {node: '>=14'}
dev: false
/@parcel/bundler-default/2.8.3_@parcel+core@2.8.3:
resolution: {integrity: sha512-yJvRsNWWu5fVydsWk3O2L4yIy3UZiKWO2cPDukGOIWMgp/Vbpp+2Ct5IygVRtE22bnseW/E/oe0PV3d2IkEJGg==}
engines: {node: '>= 12.0.0', parcel: ^2.8.3}
@ -3952,7 +4026,6 @@ packages:
/@tootallnate/once/2.0.0:
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
engines: {node: '>= 10'}
dev: true
/@trysound/sax/0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
@ -4733,7 +4806,6 @@ packages:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/aggregate-error/3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
@ -4830,6 +4902,30 @@ packages:
picomatch: 2.3.1
dev: true
/applicationinsights/2.5.0:
resolution: {integrity: sha512-6kIFmpANRok+6FhCOmO7ZZ/mh7fdNKn17BaT13cg/RV5roLPJlA6q8srWexayHd3MPcwMb9072e8Zp0P47s/pw==}
engines: {node: '>=8.0.0'}
peerDependencies:
applicationinsights-native-metrics: '*'
peerDependenciesMeta:
applicationinsights-native-metrics:
optional: true
dependencies:
'@azure/core-auth': 1.4.0
'@azure/core-rest-pipeline': 1.10.2
'@microsoft/applicationinsights-web-snippet': 1.0.1
'@opentelemetry/api': 1.4.0
'@opentelemetry/core': 1.10.0_@opentelemetry+api@1.4.0
'@opentelemetry/sdk-trace-base': 1.10.0_@opentelemetry+api@1.4.0
'@opentelemetry/semantic-conventions': 1.10.0
cls-hooked: 4.2.2
continuation-local-storage: 3.2.1
diagnostic-channel: 1.1.0
diagnostic-channel-publishers: 1.0.5_diagnostic-channel@1.1.0
transitivePeerDependencies:
- supports-color
dev: false
/arg/4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
@ -4935,6 +5031,21 @@ packages:
hasBin: true
dev: true
/async-hook-jl/1.7.6:
resolution: {integrity: sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==}
engines: {node: ^4.7 || >=6.9 || >=7.3}
dependencies:
stack-chain: 1.3.7
dev: false
/async-listener/0.6.10:
resolution: {integrity: sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==}
engines: {node: <=0.11.8 || >0.11.10}
dependencies:
semver: 5.7.1
shimmer: 1.2.1
dev: false
/async/3.2.4:
resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==}
dev: false
@ -5524,6 +5635,15 @@ packages:
engines: {node: '>=0.8'}
dev: true
/cls-hooked/4.2.2:
resolution: {integrity: sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==}
engines: {node: ^4.7 || >=6.9 || >=7.3 || >=8.2.1}
dependencies:
async-hook-jl: 1.7.6
emitter-listener: 1.1.2
semver: 5.7.1
dev: false
/co-body/5.2.0:
resolution: {integrity: sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==}
dependencies:
@ -5661,6 +5781,13 @@ packages:
engines: {node: '>= 0.6'}
dev: false
/continuation-local-storage/3.2.1:
resolution: {integrity: sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==}
dependencies:
async-listener: 0.6.10
emitter-listener: 1.1.2
dev: false
/conventional-changelog-angular/5.0.13:
resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==}
engines: {node: '>=10'}
@ -6225,6 +6352,20 @@ packages:
asap: 2.0.6
wrappy: 1.0.2
/diagnostic-channel-publishers/1.0.5_diagnostic-channel@1.1.0:
resolution: {integrity: sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg==}
peerDependencies:
diagnostic-channel: '*'
dependencies:
diagnostic-channel: 1.1.0
dev: false
/diagnostic-channel/1.1.0:
resolution: {integrity: sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ==}
dependencies:
semver: 5.7.1
dev: false
/diff-sequences/29.4.3:
resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@ -6359,6 +6500,12 @@ packages:
resolution: {integrity: sha512-yer0w5wCYdFoZytfmbNhwiGI/3cW06+RV7E23ln4490DVMxs7PvYpbsrSmAiBn/V6gode8wvJlST2YfWgvzWIg==}
dev: true
/emitter-listener/1.1.2:
resolution: {integrity: sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==}
dependencies:
shimmer: 1.2.1
dev: false
/emittery/0.13.1:
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
engines: {node: '>=12'}
@ -8017,7 +8164,6 @@ packages:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/http-proxy/1.18.1:
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
@ -8045,7 +8191,6 @@ packages:
debug: 4.3.4
transitivePeerDependencies:
- supports-color
dev: true
/human-id/1.0.2:
resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==}
@ -12896,7 +13041,6 @@ packages:
/semver/5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
hasBin: true
dev: true
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
@ -12984,6 +13128,10 @@ packages:
/shell-quote/1.7.3:
resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==}
/shimmer/1.2.1:
resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
dev: false
/side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
@ -13329,6 +13477,10 @@ packages:
deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
dev: true
/stack-chain/1.3.7:
resolution: {integrity: sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==}
dev: false
/stack-utils/2.0.5:
resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==}
engines: {node: '>=10'}