diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92f25e5f5..0ddedb117 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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: diff --git a/packages/cloud/package.json b/packages/cloud/package.json index c93a5bdf9..72da1439f 100644 --- a/packages/cloud/package.json +++ b/packages/cloud/package.json @@ -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", diff --git a/packages/cloud/src/index.ts b/packages/cloud/src/index.ts index ec742eb84..93f71f634 100644 --- a/packages/cloud/src/index.ts +++ b/packages/cloud/src/index.ts @@ -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( diff --git a/packages/cloud/src/middleware/with-error-report.ts b/packages/cloud/src/middleware/with-error-report.ts new file mode 100644 index 000000000..c3691cb5b --- /dev/null +++ b/packages/cloud/src/middleware/with-error-report.ts @@ -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() { + return async (context: InputContext, next: NextFunction) => { + await tryThat(next(context), (error) => { + if (!(error instanceof RequestError && error.status < 500)) { + appInsights.trackException(error); + } + throw error; + }); + }; +} diff --git a/packages/core/package.json b/packages/core/package.json index 1da55b0b4..92507ecf9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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", diff --git a/packages/core/src/app/init.ts b/packages/core/src/app/init.ts index 7a9082bd8..f7b4cbb7c 100644 --- a/packages/core/src/app/init.ts +++ b/packages/core/src/app/init.ts @@ -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 { 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 { 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 { tenant.requestEnd(); } catch (error: unknown) { tenant.requestEnd(); + appInsights.trackException(error); + throw error; } }); diff --git a/packages/core/src/errors/RequestError/index.ts b/packages/core/src/errors/RequestError/index.ts index 48807f596..8a374ff11 100644 --- a/packages/core/src/errors/RequestError/index.ts +++ b/packages/core/src/errors/RequestError/index.ts @@ -33,6 +33,7 @@ export default class RequestError extends Error { super(message); + this.name = 'RequestError'; this.expose = expose; this.code = code; this.status = status; diff --git a/packages/core/src/errors/ServerError/index.ts b/packages/core/src/errors/ServerError/index.ts index 286656e5a..60ff43804 100644 --- a/packages/core/src/errors/ServerError/index.ts +++ b/packages/core/src/errors/ServerError/index.ts @@ -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'; + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 735348b46..1f416d51e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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'); diff --git a/packages/core/src/middleware/koa-error-handler.ts b/packages/core/src/middleware/koa-error-handler.ts index b393e7a75..2adc7735e 100644 --- a/packages/core/src/middleware/koa-error-handler.ts +++ b/packages/core/src/middleware/koa-error-handler.ts @@ -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(): 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; diff --git a/packages/core/src/middleware/koa-guard.ts b/packages/core/src/middleware/koa-guard.ts index 5e45df5e6..32986dd2b 100644 --- a/packages/core/src/middleware/koa-guard.ts +++ b/packages/core/src/middleware/koa-guard.ts @@ -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); } } }; diff --git a/packages/shared/package.json b/packages/shared/package.json index 1dcce883a..5b02de534 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -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" } } diff --git a/packages/shared/src/app-insights/index.ts b/packages/shared/src/app-insights/index.ts new file mode 100644 index 000000000..6cb6112a3 --- /dev/null +++ b/packages/shared/src/app-insights/index.ts @@ -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(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24966f0bd..4b471a31b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'}