feat: Teams communication adapter (#4968)

Co-authored-by: Dimitri POSTOLOV <en3m@ya.ru>
Co-authored-by: Kamil Kisiela <kamil.kisiela@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Jiri Spac 2024-07-09 17:14:53 +02:00 committed by GitHub
parent eec6c9c15d
commit e74722d9d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 731 additions and 476 deletions

5
.gitignore vendored
View file

@ -130,6 +130,7 @@ packages/web/app/environment-*.mjs
packages/web/app/src/gql/*.ts packages/web/app/src/gql/*.ts
packages/web/app/src/gql/*.json packages/web/app/src/gql/*.json
packages/web/app/src/graphql/*.ts packages/web/app/src/graphql/*.ts
packages/web/docs/public/feed.xml
# Changelog # Changelog
packages/web/app/src/components/ui/changelog/generated-changelog.ts packages/web/app/src/components/ui/changelog/generated-changelog.ts
@ -142,6 +143,4 @@ deployment/utils/contour.types.ts
schema.graphql schema.graphql
resolvers.generated.ts resolvers.generated.ts
# generated by tsup docker/docker-compose.override.yml
configs/tsup/dev.config.*.mjs
packages/web/docs/public/feed.xml

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
v22

View file

@ -1,3 +1,11 @@
<!-- Graphql logo readme banner START -->
<p style="float: right; margin: 0 0 10px 10px;">
<a href="https://the-guild.dev">
<img src="https://the-guild-org.github.io/press-kit/full-dark-logo.svg" alt="Created by The guild" style="width: 100px;"/>
</a>
</p>
<!-- Graphql logo readme banner END -->
# GraphQL Hive # GraphQL Hive
GraphQL Hive provides all the tools the get visibility of your GraphQL architecture at all stages, GraphQL Hive provides all the tools the get visibility of your GraphQL architecture at all stages,
@ -51,10 +59,11 @@ GraphQL Hive helps you get a global overview of the usage of your GraphQL API wi
### Integrations ### Integrations
GraphQL Hive is well integrated with **Slack** and most **CI/CD** systems to get you up and running GraphQL Hive is well integrated with **Slack**, **MS Teams** and most **CI/CD** systems to get you
as smoothly as possible! up and running as smoothly as possible!
GraphQL Hive can notify your team when schema changes occur, either via Slack or a custom webhook. GraphQL Hive can notify your team when schema changes occur, either via Slack, MS Teams or a custom
webhook.
Also, the Hive CLI allows integration of the schema checks mechanism to all CI/CD systems (GitHub, Also, the Hive CLI allows integration of the schema checks mechanism to all CI/CD systems (GitHub,
BitBucket, Azure, and others). The same applies to schema publishing and operations checks. BitBucket, Azure, and others). The same applies to schema publishing and operations checks.

View file

@ -140,6 +140,7 @@ const config: CodegenConfig = {
AlertChannel: '../modules/alerts/module.graphql.mappers#AlertChannelMapper', AlertChannel: '../modules/alerts/module.graphql.mappers#AlertChannelMapper',
AlertSlackChannel: '../modules/alerts/module.graphql.mappers#AlertSlackChannelMapper', AlertSlackChannel: '../modules/alerts/module.graphql.mappers#AlertSlackChannelMapper',
AlertWebhookChannel: '../modules/alerts/module.graphql.mappers#AlertWebhookChannelMapper', AlertWebhookChannel: '../modules/alerts/module.graphql.mappers#AlertWebhookChannelMapper',
TeamsWebhookChannel: '../modules/alerts/module.graphql.mappers#TeamsWebhookChannelMapper',
Alert: '../modules/alerts/module.graphql.mappers#AlertMapper', Alert: '../modules/alerts/module.graphql.mappers#AlertMapper',
AdminQuery: '../modules/admin/module.graphql.mappers#AdminQueryMapper', AdminQuery: '../modules/admin/module.graphql.mappers#AdminQueryMapper',
AdminStats: '../modules/admin/module.graphql.mappers#AdminStatsMapper', AdminStats: '../modules/admin/module.graphql.mappers#AdminStatsMapper',

View file

@ -5,8 +5,9 @@
Developing Hive locally requires you to have the following software installed locally: Developing Hive locally requires you to have the following software installed locally:
- Node.js 21 (or `nvm` or `fnm`) - Node.js 21 (or `nvm` or `fnm`)
- pnpm v8 - pnpm v9
- Docker - Docker version 26.1.1 or later(previous versions will not work correctly on arm64)
- make sure these ports are free: 5432, 6379, 9000, 9001, 8123, 9092, 8081, 8082, 9644, 3567, 7043
## Setup Instructions ## Setup Instructions

View file

@ -25,6 +25,7 @@
"build:web": "pnpm prebuild && pnpm turbo build --filter=./packages/web/* --color", "build:web": "pnpm prebuild && pnpm turbo build --filter=./packages/web/* --color",
"cargo:fix": "bash ./scripts/fix-symbolic-link.sh", "cargo:fix": "bash ./scripts/fix-symbolic-link.sh",
"docker:build": "docker buildx bake -f docker/docker.hcl --load build", "docker:build": "docker buildx bake -f docker/docker.hcl --load build",
"docker:override-up": "docker compose -f ./docker/docker-compose.override.yml up -d --remove-orphans",
"env:sync": "tsx scripts/sync-env-files.ts", "env:sync": "tsx scripts/sync-env-files.ts",
"generate": "pnpm --filter @hive/storage db:generate && pnpm graphql:generate", "generate": "pnpm --filter @hive/storage db:generate && pnpm graphql:generate",
"graphql:generate": "graphql-codegen --config codegen.mts", "graphql:generate": "graphql-codegen --config codegen.mts",
@ -33,7 +34,7 @@
"lint:env-template": "tsx scripts/check-env-template.ts", "lint:env-template": "tsx scripts/check-env-template.ts",
"lint:fix": "pnpm lint --fix", "lint:fix": "pnpm lint --fix",
"lint:prettier": "prettier --cache --check .", "lint:prettier": "prettier --cache --check .",
"local:setup": "docker-compose -f ./docker/docker-compose.dev.yml up -d --remove-orphans && pnpm --filter @hive/migrations db:init", "local:setup": "docker compose -f ./docker/docker-compose.dev.yml up -d --remove-orphans && pnpm --filter @hive/migrations db:init",
"postinstall": "node ./scripts/patch-manifests.js && pnpm env:sync && node ./scripts/turborepo-cleanup.js && pnpm cargo:fix", "postinstall": "node ./scripts/patch-manifests.js && pnpm env:sync && node ./scripts/turborepo-cleanup.js && pnpm cargo:fix",
"pre-commit": "exit 0 && lint-staged", "pre-commit": "exit 0 && lint-staged",
"prebuild": "rimraf deploy-tmp && rimraf packages/**/deploy-tmp", "prebuild": "rimraf deploy-tmp && rimraf packages/**/deploy-tmp",

View file

@ -0,0 +1,19 @@
type Query {
foo: Int!
bar: String
test: String
last: Boolean
fooTwo: Int!
}
type Mutation {
addFooAnother(foo: Int!): Int!
addBar(bar: String): String
addTest(test: String): String
}
schema {
query: Query
mutation: Mutation
}
# test 3

View file

@ -0,0 +1,8 @@
import { type MigrationExecutor } from '../pg-migrator';
export default {
name: '2024.06.11T10-10-00.ms-teams-webhook.ts',
run: ({ sql }) => sql`
ALTER TYPE alert_channel_type ADD VALUE 'MSTEAMS_WEBHOOK';
`,
} satisfies MigrationExecutor;

View file

@ -63,6 +63,7 @@ import migration_2024_01_12_01T00_00_00_contracts from './actions/2024.01.26T00.
import migration_2024_01_12_01T00_00_00_schema_check_pagination_index_update from './actions/2024.01.26T00.00.01.schema-check-pagination-index-update'; import migration_2024_01_12_01T00_00_00_schema_check_pagination_index_update from './actions/2024.01.26T00.00.01.schema-check-pagination-index-update';
import migration_2024_02_19_01T00_00_00_schema_check_store_breaking_change_metadata from './actions/2024.02.19T00.00.01.schema-check-store-breaking-change-metadata'; import migration_2024_02_19_01T00_00_00_schema_check_store_breaking_change_metadata from './actions/2024.02.19T00.00.01.schema-check-store-breaking-change-metadata';
import migration_2024_04_09T10_10_00_check_approval_comment from './actions/2024.04.09T10-10-00.check-approval-comment'; import migration_2024_04_09T10_10_00_check_approval_comment from './actions/2024.04.09T10-10-00.check-approval-comment';
import migration_2024_06_11T10_10_00_ms_teams_webhook from './actions/2024.06.11T10-10-00.ms-teams-webhook';
import { runMigrations } from './pg-migrator'; import { runMigrations } from './pg-migrator';
export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string }) => export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string }) =>
@ -134,5 +135,6 @@ export const runPGMigrations = (args: { slonik: DatabasePool; runTo?: string })
migration_2024_01_12_01T00_00_00_schema_check_pagination_index_update, migration_2024_01_12_01T00_00_00_schema_check_pagination_index_update,
migration_2024_02_19_01T00_00_00_schema_check_store_breaking_change_metadata, migration_2024_02_19_01T00_00_00_schema_check_store_breaking_change_metadata,
migration_2024_04_09T10_10_00_check_approval_comment, migration_2024_04_09T10_10_00_check_approval_comment,
migration_2024_06_11T10_10_00_ms_teams_webhook,
], ],
}); });

View file

@ -62,6 +62,7 @@
"slonik": "30.4.4", "slonik": "30.4.4",
"supertokens-node": "15.2.1", "supertokens-node": "15.2.1",
"tslib": "2.6.3", "tslib": "2.6.3",
"vitest": "1.6.0",
"zod": "3.23.8", "zod": "3.23.8",
"zod-validation-error": "3.3.0" "zod-validation-error": "3.3.0"
} }

View file

@ -1,4 +1,5 @@
import { createModule } from 'graphql-modules'; import { createModule } from 'graphql-modules';
import { TeamsCommunicationAdapter } from './providers/adapters/msteams';
import { SlackCommunicationAdapter } from './providers/adapters/slack'; import { SlackCommunicationAdapter } from './providers/adapters/slack';
import { WebhookCommunicationAdapter } from './providers/adapters/webhook'; import { WebhookCommunicationAdapter } from './providers/adapters/webhook';
import { AlertsManager } from './providers/alerts-manager'; import { AlertsManager } from './providers/alerts-manager';
@ -10,5 +11,10 @@ export const alertsModule = createModule({
dirname: __dirname, dirname: __dirname,
typeDefs, typeDefs,
resolvers, resolvers,
providers: [AlertsManager, SlackCommunicationAdapter, WebhookCommunicationAdapter], providers: [
AlertsManager,
SlackCommunicationAdapter,
WebhookCommunicationAdapter,
TeamsCommunicationAdapter,
],
}); });

View file

@ -3,4 +3,5 @@ import type { Alert, AlertChannel } from '../../shared/entities';
export type AlertChannelMapper = AlertChannel; export type AlertChannelMapper = AlertChannel;
export type AlertSlackChannelMapper = AlertChannel; export type AlertSlackChannelMapper = AlertChannel;
export type AlertWebhookChannelMapper = AlertChannel; export type AlertWebhookChannelMapper = AlertChannel;
export type TeamsWebhookChannelMapper = AlertChannel;
export type AlertMapper = Alert; export type AlertMapper = Alert;

View file

@ -16,6 +16,7 @@ export default gql`
enum AlertChannelType { enum AlertChannelType {
SLACK SLACK
WEBHOOK WEBHOOK
MSTEAMS_WEBHOOK
} }
enum AlertType { enum AlertType {
@ -140,6 +141,13 @@ export default gql`
endpoint: String! endpoint: String!
} }
type TeamsWebhookChannel implements AlertChannel {
id: ID!
name: String!
type: AlertChannelType!
endpoint: String!
}
type Alert { type Alert {
id: ID! id: ID!
type: AlertType! type: AlertType!

View file

@ -8,6 +8,12 @@ import type {
Target, Target,
} from '../../../../shared/entities'; } from '../../../../shared/entities';
interface NotificationIntegrations {
slack: {
token: string | null | undefined;
};
}
export interface SchemaChangeNotificationInput { export interface SchemaChangeNotificationInput {
event: { event: {
organization: Pick<Organization, 'id' | 'cleanId' | 'name'>; organization: Pick<Organization, 'id' | 'cleanId' | 'name'>;
@ -25,11 +31,7 @@ export interface SchemaChangeNotificationInput {
}; };
alert: Alert; alert: Alert;
channel: AlertChannel; channel: AlertChannel;
integrations: { integrations: NotificationIntegrations;
slack: {
token: string;
};
};
} }
export interface ChannelConfirmationInput { export interface ChannelConfirmationInput {
@ -39,11 +41,7 @@ export interface ChannelConfirmationInput {
project: Pick<Project, 'id' | 'cleanId' | 'name'>; project: Pick<Project, 'id' | 'cleanId' | 'name'>;
}; };
channel: AlertChannel; channel: AlertChannel;
integrations: { integrations: NotificationIntegrations;
slack: {
token: string;
};
};
} }
export interface CommunicationAdapter { export interface CommunicationAdapter {
@ -59,9 +57,13 @@ export function quotesTransformer(msg: string, symbols = '**') {
const findSingleQuotes = /'([^']+)'/gim; const findSingleQuotes = /'([^']+)'/gim;
const findDoubleQuotes = /"([^"]+)"/gim; const findDoubleQuotes = /"([^"]+)"/gim;
function transformm(_: string, value: string) { function transform(_: string, value: string) {
return `${symbols}${value}${symbols}`; return `${symbols}${value}${symbols}`;
} }
return msg.replace(findSingleQuotes, transformm).replace(findDoubleQuotes, transformm); return msg.replace(findSingleQuotes, transform).replace(findDoubleQuotes, transform);
} }
export const createMDLink = ({ text, url }: { text: string; url: string }) => {
return `[${text}](${url})`;
};

View file

@ -0,0 +1,255 @@
import { AlertChannel } from 'packages/services/api/src/shared/entities';
import { vi } from 'vitest';
import { SchemaChangeType } from '@hive/storage';
import { ChannelConfirmationInput, SchemaChangeNotificationInput } from './common';
import { TeamsCommunicationAdapter } from './msteams';
const logger = {
child: () => ({
debug: vi.fn(),
error: vi.fn(),
}),
};
const appBaseUrl = 'app-base-url';
const webhookUrl = 'webhook-url';
describe('TeamsCommunicationAdapter', () => {
describe('sendSchemaChangeNotification', () => {
it('should send schema change notification', async () => {
const changes = [
{
id: 'id-1',
type: 'FIELD_REMOVED',
approvalMetadata: null,
criticality: 'BREAKING',
message: "Field 'addFoo' was removed from object type 'Mutation'",
meta: {
typeName: 'Mutation',
removedFieldName: 'addFoo',
isRemovedFieldDeprecated: false,
typeType: 'object type',
},
path: 'Mutation.addFoo',
isSafeBasedOnUsage: false,
reason:
'Removing a field is a breaking change. It is preferable to deprecate the field before removing it.',
usageStatistics: null,
breakingChangeSchemaCoordinate: 'Mutation.addFoo',
},
{
id: 'id-2',
type: 'FIELD_REMOVED',
approvalMetadata: null,
criticality: 'BREAKING',
message: "Field 'foo3' was removed from object type 'Query'",
meta: {
typeName: 'Query',
removedFieldName: 'foo3',
isRemovedFieldDeprecated: false,
typeType: 'object type',
},
path: 'Query.foo3',
isSafeBasedOnUsage: false,
reason:
'Removing a field is a breaking change. It is preferable to deprecate the field before removing it.',
usageStatistics: null,
breakingChangeSchemaCoordinate: 'Query.foo3',
},
{
id: 'id-3',
type: 'FIELD_ADDED',
approvalMetadata: null,
criticality: 'NON_BREAKING',
message: "Field 'addFooT' was added to object type 'Mutation'",
meta: {
typeName: 'Mutation',
addedFieldName: 'addFooT',
typeType: 'object type',
},
path: 'Mutation.addFooT',
isSafeBasedOnUsage: false,
reason: null,
usageStatistics: null,
breakingChangeSchemaCoordinate: null,
},
{
id: 'id-4',
type: 'FIELD_ADDED',
approvalMetadata: null,
criticality: 'NON_BREAKING',
message: "Field 'foo4' was added to object type 'Query'",
meta: {
typeName: 'Query',
addedFieldName: 'foo4',
typeType: 'object type',
},
path: 'Query.foo4',
isSafeBasedOnUsage: false,
reason: null,
usageStatistics: null,
breakingChangeSchemaCoordinate: null,
},
] as Array<SchemaChangeType>;
const messages = [] as string[];
const input = {
alert: {
id: 'alert-id',
type: 'SCHEMA_CHANGE_NOTIFICATIONS',
channelId: 'channel-id',
projectId: 'project-id',
organizationId: 'org-id',
createdAt: new Date().toISOString(),
targetId: 'target-id',
},
integrations: {
slack: {
token: null,
},
},
event: {
organization: {
id: 'org-id',
cleanId: 'org-clean-id',
name: '',
},
project: {
id: 'project-id',
cleanId: 'project-clean-id',
name: 'project-name',
},
target: {
id: 'target-id',
cleanId: 'target-clean-id',
name: 'target-name',
},
changes,
messages,
initial: false,
errors: [],
schema: {
id: 'schema-id',
commit: 'commit',
valid: true,
},
},
channel: {
webhookEndpoint: webhookUrl,
} as AlertChannel,
} as SchemaChangeNotificationInput;
const adapter = new TeamsCommunicationAdapter(logger as any, appBaseUrl);
const sendTeamsMessageSpy = vi.spyOn(adapter as any, 'sendTeamsMessage');
await adapter.sendSchemaChangeNotification(input);
expect(sendTeamsMessageSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
webhook-url,
🐝 Hi, I found *4 changes* in project [project-name](app-base-url/org-clean-id/project-clean-id), target [target-name](app-base-url/org-clean-id/project-clean-id/target-clean-id) ([view details](app-base-url/org-clean-id/project-clean-id/target-clean-id/history/schema-id)):
### Breaking changes
- Field \`addFoo\` was removed from object type \`Mutation\`
- Field \`foo3\` was removed from object type \`Query\`
### Safe changes
- Field \`addFooT\` was added to object type \`Mutation\`
- Field \`foo4\` was added to object type \`Query\`
,
[view full report](app-base-url/org-clean-id/project-clean-id/target-clean-id/history/schema-id),
]
`);
});
// Add more test cases here if needed
});
});
describe('sendChannelConfirmation', () => {
it('should send channel confirmation', async () => {
const appBaseUrl = 'app-base-url';
const webhookUrl = 'webhook-url';
const input = {
event: {
organization: {
id: 'org-id',
cleanId: 'org-clean-id',
},
project: {
id: 'project-id',
cleanId: 'project-clean-id',
name: 'project-name',
},
kind: 'created',
},
channel: {
webhookEndpoint: webhookUrl,
},
} as ChannelConfirmationInput;
const adapter = new TeamsCommunicationAdapter(logger as any, appBaseUrl);
const sendTeamsMessageSpy = vi.spyOn(adapter as any, 'sendTeamsMessage');
await adapter.sendChannelConfirmation(input);
expect(sendTeamsMessageSpy.mock.calls[0]).toMatchInlineSnapshot(`
[
webhook-url,
👋 Hi! I'm the notification 🐝.
I will send here notifications about your [project-name](app-base-url/org-clean-id/project-clean-id) project.,
]
`);
input.event.kind = 'deleted';
await adapter.sendChannelConfirmation(input);
expect(sendTeamsMessageSpy.mock.calls[1]).toMatchInlineSnapshot(`
[
webhook-url,
👋 Hi! I'm the notification 🐝.
I will no longer send here notifications about your [project-name](app-base-url/org-clean-id/project-clean-id) project.,
]
`);
});
describe('sendTeamsMessage', () => {
const adapter = new TeamsCommunicationAdapter(logger as any, appBaseUrl);
beforeAll(() => {
// @ts-expect-error mocking fetch
global.fetch = vi.fn(() =>
Promise.resolve({
ok: true,
statusText: 'OK',
}),
);
});
it('sends a message under 27k characters', async () => {
const message = 'Short message';
await adapter.sendTeamsMessage('http://example.com/webhook', message);
expect(fetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
body: expect.stringContaining(message),
}),
);
});
it('truncates and appends link for messages over 27k characters', async () => {
const longMessage = 'a'.repeat(27001);
const link = 'http://example.com/fullReport';
await adapter.sendTeamsMessage('http://example.com/webhook', longMessage, link);
expect(fetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
body: expect.stringContaining('... message truncated. ' + link),
}),
);
});
it('handles failed send operation', async () => {
// @ts-expect-error types obviously don't account for the fact this is mocked
fetch.mockImplementationOnce(() => Promise.resolve({ ok: false, statusText: 'Bad Request' }));
await expect(
adapter.sendTeamsMessage('http://example.com/webhook', 'Test message'),
).rejects.toThrow('Failed to send Microsoft Teams message: Bad Request');
});
});
});

View file

@ -0,0 +1,209 @@
import { Inject, Injectable } from 'graphql-modules';
import { CriticalityLevel } from '@graphql-inspector/core';
import { SchemaChangeType } from '@hive/storage';
import { Logger } from '../../../shared/providers/logger';
import { WEB_APP_URL } from '../../../shared/providers/tokens';
import {
ChannelConfirmationInput,
CommunicationAdapter,
createMDLink,
SchemaChangeNotificationInput,
slackCoderize,
} from './common';
@Injectable()
export class TeamsCommunicationAdapter implements CommunicationAdapter {
private logger: Logger;
constructor(
logger: Logger,
@Inject(WEB_APP_URL) private appBaseUrl: string,
) {
this.logger = logger.child({ service: 'TeamsCommunicationAdapter' });
}
async sendSchemaChangeNotification(input: SchemaChangeNotificationInput) {
this.logger.debug(
`Sending Schema Change Notifications over Microsoft Teams (organization=%s, project=%s, target=%s)`,
input.event.organization.id,
input.event.project.id,
input.event.target.id,
);
const webhookUrl = input.channel.webhookEndpoint;
if (!webhookUrl) {
this.logger.debug(`Microsoft Teams Integration is not available`);
return;
}
try {
const totalChanges = input.event.changes.length + input.event.messages.length;
const projectLink = createMDLink({
text: input.event.project.name,
url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`,
});
const targetLink = createMDLink({
text: input.event.target.name,
url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}/${input.event.target.cleanId}`,
});
const changeUrl = `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}/${input.event.target.cleanId}/history/${input.event.schema.id}`;
const viewLink = createMDLink({
text: 'view details',
url: changeUrl,
});
const message = input.event.initial
? `🐝 Hi, I received your *first* schema in project ${projectLink}, target ${targetLink} (${viewLink}):`
: `🐝 Hi, I found *${totalChanges} ${this.pluralize(
'change',
totalChanges,
)}* in project ${projectLink}, target ${targetLink} (${viewLink}):`;
const attachmentsText = input.event.initial
? ''
: createAttachmentsText(input.event.changes, input.event.messages);
await this.sendTeamsMessage(
webhookUrl,
`${message}\n\n${attachmentsText}`,
createMDLink({
text: 'view full report',
url: changeUrl,
}),
);
} catch (error) {
this.logger.error(`Failed to send Microsoft Teams notification`, error);
}
}
async sendChannelConfirmation(input: ChannelConfirmationInput) {
this.logger.debug(
`Sending Channel Confirmation over Microsoft Teams (organization=%s, project=%s, channel=%s)`,
input.event.organization.id,
input.event.project.id,
);
const webhookUrl = input.channel.webhookEndpoint;
if (!webhookUrl) {
this.logger.debug(`Microsoft Teams Integration is not available`);
return;
}
const actionMessage =
input.event.kind === 'created'
? `I will send here notifications`
: `I will no longer send here notifications`;
try {
const projectLink = createMDLink({
text: input.event.project.name,
url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`,
});
const message = [
`👋 Hi! I'm the notification 🐝.`,
`${actionMessage} about your ${projectLink} project.`,
].join('\n');
await this.sendTeamsMessage(webhookUrl, message);
} catch (error) {
this.logger.error(`Failed to send Microsoft Teams notification`, error);
}
}
private pluralize(word: string, num: number): string {
return word + (num > 1 ? 's' : '');
}
/**
* message gets truncated to max 27k characters-max payload size for Microsoft Teams is 28 KB
*/
async sendTeamsMessage(webhookUrl: string, message: string, fullReportMdLink?: string) {
if (message.length > 27000) {
message = message.slice(0, 27000) + `\n\n... message truncated. ${fullReportMdLink ?? ''}`;
}
const payload = {
'@type': 'MessageCard',
'@context': 'http://schema.org/extensions',
summary: 'Notification',
themeColor: '0076D7',
sections: [
{
activityTitle: 'Notification',
text: message,
markdown: true,
},
],
};
const response = await fetch(webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error(`Failed to send Microsoft Teams message: ${response.statusText}`);
}
}
}
function createAttachmentsText(
changes: readonly SchemaChangeType[],
messages: readonly string[],
): string {
const breakingChanges = changes.filter(
change => change.criticality === CriticalityLevel.Breaking,
);
const safeChanges = changes.filter(change => change.criticality !== CriticalityLevel.Breaking);
let text = '';
if (breakingChanges.length) {
text += renderChangeList({
color: '#E74C3B',
title: 'Breaking changes',
changes: breakingChanges,
});
}
if (safeChanges.length) {
text += renderChangeList({
color: '#23B99A',
title: 'Safe changes',
changes: safeChanges,
});
}
if (messages.length) {
text += `### Other changes\n${messages.map(message => slackCoderize(message)).join('\n')}\n`;
}
return text;
}
function renderChangeList({
changes,
title,
}: {
color: string;
title: string;
changes: readonly SchemaChangeType[];
}): string {
const text = changes
.map(change => {
let text = ` - ${change.message}`;
if (change.isSafeBasedOnUsage) {
text += ' (safe based on usage)';
}
return slackCoderize(text);
})
.join('\n');
return `### ${title}\n${text}\n`;
}

View file

@ -7,6 +7,7 @@ import { WEB_APP_URL } from '../../../shared/providers/tokens';
import { import {
ChannelConfirmationInput, ChannelConfirmationInput,
CommunicationAdapter, CommunicationAdapter,
createMDLink,
SchemaChangeNotificationInput, SchemaChangeNotificationInput,
slackCoderize, slackCoderize,
} from './common'; } from './common';
@ -43,15 +44,15 @@ export class SlackCommunicationAdapter implements CommunicationAdapter {
const client = new WebClient(input.integrations.slack.token, {}); const client = new WebClient(input.integrations.slack.token, {});
const totalChanges = input.event.changes.length + input.event.messages.length; const totalChanges = input.event.changes.length + input.event.messages.length;
const projectLink = this.createLink({ const projectLink = createMDLink({
text: input.event.project.name, text: input.event.project.name,
url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`, url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`,
}); });
const targetLink = this.createLink({ const targetLink = createMDLink({
text: input.event.target.name, text: input.event.target.name,
url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}/${input.event.target.cleanId}`, url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}/${input.event.target.cleanId}`,
}); });
const viewLink = this.createLink({ const viewLink = createMDLink({
text: 'view details', text: 'view details',
url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}/${input.event.target.cleanId}/history/${input.event.schema.id}`, url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}/${input.event.target.cleanId}/history/${input.event.schema.id}`,
}); });
@ -82,6 +83,9 @@ export class SlackCommunicationAdapter implements CommunicationAdapter {
} }
} }
/**
* triggered when a channel is created or deleted
*/
async sendChannelConfirmation(input: ChannelConfirmationInput) { async sendChannelConfirmation(input: ChannelConfirmationInput) {
this.logger.debug( this.logger.debug(
`Sending Channel Confirmation over Slack (organization=%s, project=%s, channel=%s)`, `Sending Channel Confirmation over Slack (organization=%s, project=%s, channel=%s)`,
@ -90,7 +94,7 @@ export class SlackCommunicationAdapter implements CommunicationAdapter {
input.channel.slackChannel, input.channel.slackChannel,
); );
const token = input.integrations.slack.token; const token = input.integrations?.slack.token;
if (!token) { if (!token) {
this.logger.debug(`Slack Integration is not available`); this.logger.debug(`Slack Integration is not available`);
@ -103,7 +107,7 @@ export class SlackCommunicationAdapter implements CommunicationAdapter {
: `I will no longer send here notifications`; : `I will no longer send here notifications`;
try { try {
const projectLink = this.createLink({ const projectLink = createMDLink({
text: input.event.project.name, text: input.event.project.name,
url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`, url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`,
}); });

View file

@ -12,7 +12,8 @@ import { ProjectManager } from '../../project/providers/project-manager';
import { Logger } from '../../shared/providers/logger'; import { Logger } from '../../shared/providers/logger';
import type { ProjectSelector } from '../../shared/providers/storage'; import type { ProjectSelector } from '../../shared/providers/storage';
import { Storage } from '../../shared/providers/storage'; import { Storage } from '../../shared/providers/storage';
import { SchemaChangeNotificationInput } from './adapters/common'; import { ChannelConfirmationInput, SchemaChangeNotificationInput } from './adapters/common';
import { TeamsCommunicationAdapter } from './adapters/msteams';
import { SlackCommunicationAdapter } from './adapters/slack'; import { SlackCommunicationAdapter } from './adapters/slack';
import { WebhookCommunicationAdapter } from './adapters/webhook'; import { WebhookCommunicationAdapter } from './adapters/webhook';
@ -29,6 +30,7 @@ export class AlertsManager {
private slackIntegrationManager: SlackIntegrationManager, private slackIntegrationManager: SlackIntegrationManager,
private slack: SlackCommunicationAdapter, private slack: SlackCommunicationAdapter,
private webhook: WebhookCommunicationAdapter, private webhook: WebhookCommunicationAdapter,
private teamsWebhook: TeamsCommunicationAdapter,
private organizationManager: OrganizationManager, private organizationManager: OrganizationManager,
private projectManager: ProjectManager, private projectManager: ProjectManager,
private storage: Storage, private storage: Storage,
@ -185,6 +187,7 @@ export class AlertsManager {
channel: channels.find(channel => channel.id === alert.channelId)!, channel: channels.find(channel => channel.id === alert.channelId)!,
}; };
}); });
console.log('pairs:', pairs);
const slackToken = await this.slackIntegrationManager.getToken({ const slackToken = await this.slackIntegrationManager.getToken({
organization: event.organization.id, organization: event.organization.id,
@ -195,8 +198,9 @@ export class AlertsManager {
const integrations: SchemaChangeNotificationInput['integrations'] = { const integrations: SchemaChangeNotificationInput['integrations'] = {
slack: { slack: {
token: slackToken!, token: slackToken,
}, },
// ms Teams is integrated via webhook. Webhook contains the token in itself, so we don't have any other token for it
}; };
// Let's not leak any data :) // Let's not leak any data :)
@ -237,6 +241,14 @@ export class AlertsManager {
integrations, integrations,
}); });
} }
if (channel.type === 'MSTEAMS_WEBHOOK') {
return this.teamsWebhook.sendSchemaChangeNotification({
event: safeEvent,
alert,
channel,
integrations,
});
}
return this.webhook.sendSchemaChangeNotification({ return this.webhook.sendSchemaChangeNotification({
event: safeEvent, event: safeEvent,
@ -265,34 +277,42 @@ export class AlertsManager {
}), }),
]); ]);
const channelConfirmationContext: ChannelConfirmationInput = {
event: {
kind: input.kind,
organization: {
id: organization.id,
cleanId: organization.cleanId,
name: organization.name,
},
project: {
id: project.id,
cleanId: project.cleanId,
name: project.name,
},
},
channel,
integrations: {
slack: {
token: null,
},
},
};
if (channel.type === 'SLACK') { if (channel.type === 'SLACK') {
const slackToken = await this.slackIntegrationManager.getToken({ const slackToken = await this.slackIntegrationManager.getToken({
organization: organization.id, organization: organization.id,
project: project.id, project: project.id,
context: IntegrationsAccessContext.ChannelConfirmation, context: IntegrationsAccessContext.ChannelConfirmation,
}); });
if (!slackToken) {
throw new Error(`Slack token was not found for channel "${channel.id}"`);
}
await this.slack.sendChannelConfirmation({ channelConfirmationContext.integrations.slack.token = slackToken;
event: { await this.slack.sendChannelConfirmation(channelConfirmationContext);
kind: input.kind, } else if (channel.type === 'MSTEAMS_WEBHOOK') {
organization: { await this.teamsWebhook.sendChannelConfirmation(channelConfirmationContext);
id: organization.id,
cleanId: organization.cleanId,
name: organization.name,
},
project: {
id: project.id,
cleanId: project.cleanId,
name: project.name,
},
},
channel,
integrations: {
slack: {
token: slackToken!,
},
},
});
} else { } else {
await this.webhook.sendChannelConfirmation(); await this.webhook.sendChannelConfirmation();
} }

View file

@ -217,4 +217,12 @@ export const resolvers: AlertsModule.Resolvers = {
return channel.webhookEndpoint!; return channel.webhookEndpoint!;
}, },
}, },
TeamsWebhookChannel: {
__isTypeOf(channel) {
return channel.type === 'MSTEAMS_WEBHOOK';
},
endpoint(channel) {
return channel.webhookEndpoint!;
},
},
}; };

View file

@ -7,7 +7,7 @@
* *
*/ */
export type alert_channel_type = 'SLACK' | 'WEBHOOK'; export type alert_channel_type = 'MSTEAMS_WEBHOOK' | 'SLACK' | 'WEBHOOK';
export type alert_type = 'SCHEMA_CHANGE_NOTIFICATIONS'; export type alert_type = 'SCHEMA_CHANGE_NOTIFICATIONS';
export type schema_policy_resource = 'ORGANIZATION' | 'PROJECT'; export type schema_policy_resource = 'ORGANIZATION' | 'PROJECT';
export type user_role = 'ADMIN' | 'MEMBER'; export type user_role = 'ADMIN' | 'MEMBER';

View file

@ -1,6 +1,6 @@
import { Checkbox, Table, Tag, TBody, Td, Tr } from '@/components/v2'; import { Checkbox, Table, Tag, TBody, Td, Tr } from '@/components/v2';
import { FragmentType, graphql, useFragment } from '@/gql'; import { FragmentType, graphql, useFragment } from '@/gql';
import { AlertChannelType } from '@/gql/graphql'; import { AlertChannelType, ChannelsTable_AlertChannelFragmentFragment } from '@/gql/graphql';
export const ChannelsTable_AlertChannelFragment = graphql(` export const ChannelsTable_AlertChannelFragment = graphql(`
fragment ChannelsTable_AlertChannelFragment on AlertChannel { fragment ChannelsTable_AlertChannelFragment on AlertChannel {
@ -13,9 +13,18 @@ export const ChannelsTable_AlertChannelFragment = graphql(`
... on AlertWebhookChannel { ... on AlertWebhookChannel {
endpoint endpoint
} }
... on TeamsWebhookChannel {
endpoint
}
} }
`); `);
const colorMap = {
[AlertChannelType.Slack]: 'green' as const,
[AlertChannelType.Webhook]: 'yellow' as const,
[AlertChannelType.MsteamsWebhook]: 'orange' as const,
};
export function ChannelsTable(props: { export function ChannelsTable(props: {
channels: FragmentType<typeof ChannelsTable_AlertChannelFragment>[]; channels: FragmentType<typeof ChannelsTable_AlertChannelFragment>[];
isChecked: (channelId: string) => boolean; isChecked: (channelId: string) => boolean;
@ -23,6 +32,20 @@ export function ChannelsTable(props: {
}) { }) {
const channels = useFragment(ChannelsTable_AlertChannelFragment, props.channels); const channels = useFragment(ChannelsTable_AlertChannelFragment, props.channels);
const renderChannelEndpoint = (channel: ChannelsTable_AlertChannelFragmentFragment) => {
if (channel.__typename === 'AlertSlackChannel') {
return channel.channel;
}
if (
channel.__typename === 'AlertWebhookChannel' ||
channel.__typename === 'TeamsWebhookChannel'
) {
return channel.endpoint;
}
return '';
};
return ( return (
<Table> <Table>
<TBody> <TBody>
@ -36,18 +59,12 @@ export function ChannelsTable(props: {
checked={props.isChecked(channel.id)} checked={props.isChecked(channel.id)}
/> />
</Td> </Td>
<Td>{channel.name}</Td> <Td className="text-ellipsis whitespace-nowrap">{channel.name}</Td>
<Td className="truncate text-xs text-gray-400"> <Td className="max-w-xs truncate text-xs text-gray-400">
{channel.__typename === 'AlertSlackChannel' {renderChannelEndpoint(channel)}
? channel.channel
: channel.__typename === 'AlertWebhookChannel'
? channel.endpoint
: ''}
</Td> </Td>
<Td> <Td className="flex max-w-24 content-end">
<Tag color={channel.type === AlertChannelType.Webhook ? 'green' : 'yellow'}> <Tag color={colorMap[channel.type]}>{channel.type}</Tag>
{channel.type}
</Tag>
</Td> </Td>
</Tr> </Tr>
))} ))}

View file

@ -61,8 +61,8 @@ export const CreateChannelModal = ({
), ),
endpoint: Yup.string() endpoint: Yup.string()
.url() .url()
.when('type', ([type], schema) => .when('type', ([_type], schema) =>
type === AlertChannelType.Webhook ? schema.required('Must enter endpoint') : schema, isWebhookLike ? schema.required('Must enter endpoint') : schema,
), ),
}), }),
async onSubmit(values) { async onSubmit(values) {
@ -73,8 +73,7 @@ export const CreateChannelModal = ({
name: values.name, name: values.name,
type: values.type, type: values.type,
slack: values.type === AlertChannelType.Slack ? { channel: values.slackChannel } : null, slack: values.type === AlertChannelType.Slack ? { channel: values.slackChannel } : null,
webhook: webhook: isWebhookLike ? { endpoint: values.endpoint } : null,
values.type === AlertChannelType.Webhook ? { endpoint: values.endpoint } : null,
}, },
}); });
if (error) { if (error) {
@ -88,6 +87,9 @@ export const CreateChannelModal = ({
} }
}, },
}); });
const isWebhookLike = [AlertChannelType.Webhook, AlertChannelType.MsteamsWebhook].includes(
values.type,
);
return ( return (
<Modal open={isOpen} onOpenChange={toggleModalOpen}> <Modal open={isOpen} onOpenChange={toggleModalOpen}>
@ -132,12 +134,13 @@ export const CreateChannelModal = ({
options={[ options={[
{ value: AlertChannelType.Slack, name: 'Slack' }, { value: AlertChannelType.Slack, name: 'Slack' },
{ value: AlertChannelType.Webhook, name: 'Webhook' }, { value: AlertChannelType.Webhook, name: 'Webhook' },
{ value: AlertChannelType.MsteamsWebhook, name: 'MS Teams Webhook' },
]} ]}
/> />
{touched.type && errors.type && <div className="text-sm text-red-500">{errors.type}</div>} {touched.type && errors.type && <div className="text-sm text-red-500">{errors.type}</div>}
</div> </div>
{values.type === AlertChannelType.Webhook && ( {isWebhookLike && (
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<label className="text-sm font-semibold" htmlFor="endpoint"> <label className="text-sm font-semibold" htmlFor="endpoint">
Endpoint Endpoint
@ -160,7 +163,13 @@ export const CreateChannelModal = ({
{mutation.data.addAlertChannel.error.inputErrors.webhookEndpoint} {mutation.data.addAlertChannel.error.inputErrors.webhookEndpoint}
</div> </div>
)} )}
<p className="text-sm text-gray-500">Hive will send alerts to your endpoint.</p> {values.endpoint ? (
<p className="text-sm text-gray-500">Hive will send alerts to your endpoint.</p>
) : (
<a href="https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cdotnet">
Follow this guide to set up an incoming webhook connector in MS Teams
</a>
)}
</div> </div>
)} )}
@ -205,7 +214,7 @@ export const CreateChannelModal = ({
<Button <Button
type="submit" type="submit"
size="lg" size="lg"
className="w-full justify-center" className="w-full justify-center text-ellipsis whitespace-nowrap"
variant="primary" variant="primary"
disabled={isSubmitting} disabled={isSubmitting}
> >

View file

@ -49,7 +49,7 @@ CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>( const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} /> <div ref={ref} className={cn('overflow-hidden p-6 pt-0', className)} {...props} />
), ),
); );
CardContent.displayName = 'CardContent'; CardContent.displayName = 'CardContent';

View file

@ -3,7 +3,7 @@ import { cn } from '@/lib/utils';
function Table({ children, className, ...props }: ComponentProps<'table'>): ReactElement { function Table({ children, className, ...props }: ComponentProps<'table'>): ReactElement {
return ( return (
<table className={cn('w-full', className)} {...props}> <table className={cn('w-full overflow-hidden', className)} {...props}>
{children} {children}
</table> </table>
); );

View file

@ -5,6 +5,7 @@ const colors = {
green: 'bg-green-500/10 text-green-500', green: 'bg-green-500/10 text-green-500',
yellow: 'bg-yellow-500/10 text-yellow-500', yellow: 'bg-yellow-500/10 text-yellow-500',
gray: 'bg-gray-500/10 text-gray-500', gray: 'bg-gray-500/10 text-gray-500',
orange: 'bg-orange-500/10 text-orange-500',
} as const; } as const;
export function Tag({ export function Tag({
@ -12,13 +13,17 @@ export function Tag({
color = 'gray', color = 'gray',
className, className,
}: { }: {
color?: 'green' | 'yellow' | 'gray'; color?: keyof typeof colors;
className?: string; className?: string;
children: ReactNode; children: ReactNode;
}): ReactElement { }): ReactElement {
return ( return (
<span <span
className={cn('inline-flex items-center gap-x-1 rounded-sm p-2', colors[color], className)} className={cn(
'inline-flex items-center gap-x-1 whitespace-nowrap rounded-sm p-2',
colors[color],
className,
)}
> >
{children} {children}
</span> </span>

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -21,4 +21,9 @@ export const authors: Record<string, Author> = {
link: 'https://twitter.com/aleksandrasays', link: 'https://twitter.com/aleksandrasays',
github: 'beerose', github: 'beerose',
}, },
jiri: {
name: 'Jiri Spac',
link: 'https://twitter.com/capajj',
github: 'capaj',
},
}; };

View file

@ -5,6 +5,7 @@ import githubIntegrationImage from '../../../../public/docs/pages/management/org
import slackIntegrationImage from '../../../../public/docs/pages/management/org-slack-integration.png' import slackIntegrationImage from '../../../../public/docs/pages/management/org-slack-integration.png'
import projectGitLinkImage from '../../../../public/docs/pages/management/project-git-link.png' import projectGitLinkImage from '../../../../public/docs/pages/management/project-git-link.png'
import projectHttpAlertImage from '../../../../public/docs/pages/management/project-http-alert.png' import projectHttpAlertImage from '../../../../public/docs/pages/management/project-http-alert.png'
import msteamsFormImage from '../../../../public/docs/pages/management/project-msteams-alert.png'
import slackAlertFormImage from '../../../../public/docs/pages/management/project-slack-alert.png' import slackAlertFormImage from '../../../../public/docs/pages/management/project-slack-alert.png'
# Project Management # Project Management
@ -140,6 +141,28 @@ Hive project:
invitation, our Slack app will be unable to post messages to the channel. invitation, our Slack app will be unable to post messages to the channel.
</Callout> </Callout>
#### MS Teams Webhook
You set up Hive MS Teams webhook to receive alerts and notifications from Hive, when an alert or a
notification is triggered. All you need to do is to provide a valid HTTP URL, and Hive will dispatch
a `POST` request to that URL, with a JSON payload acceptable by MS Teams.
[MS Teams Webhook JSON example](https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-using#example-of-connector-message)
Typically the webhook URL looks like:
```txt
https://USERNAME.webhook.office.com/webhookb2/XXXXX/IncomingWebhook/YYYY/ZZZZZ
```
You can find more information on how to set up a webhook in MS Teams in the
[official documentation - Create Incoming Webhooks](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cdotnet).
<NextImage
alt="MS Teams Alert Configuration"
src={msteamsFormImage}
className="mt-6 max-w-sm rounded-lg drop-shadow-md"
/>
#### HTTP Webhook #### HTTP Webhook
You can implement a custom HTTP webhook to receive alerts and notifications from Hive, when an alert You can implement a custom HTTP webhook to receive alerts and notifications from Hive, when an alert

View file

@ -0,0 +1,28 @@
---
title: MS Teams Webhooks for Alerts in Hive
description: MS Teams webhooks to receive alerts and notifications from Hive.
date: 2024-06-14
authors: [jiri]
---
Were excited to announce a new feature that lets you configure MS Teams webhooks to receive alerts
and notifications from Hive. By connecting Hive with MS Teams, you can make your team always aware
of critical changes and developments.
![MS Teams Webhook dialog](/changelog/2024-06-14-ms-teams-webhooks-alerts/project-msteams-alert-full.png)
Heres a simple guide to get you started:
1. **Go to Project:** Navigate to your project's page.
1. **Access Alerts:** Click on the "Alerts" tab.
1. **Add a Channel:** In the Channels section, click on the "Add channel" button.
1. **Select type**: Choose "MS Teams Webhook" from the dropdown menu.
1. **Enter Webhook URL:** Paste your webhook URL in the "Endpoint" field.
1. **Save:** Click "Save" to confirm your settings.
Once set up, you can create an alert that triggers whenever a schema change is detected upon
publishing. For more detailed instructions, check out our documentation
[here](/docs/management/projects#ms-teams-webhook).
Setting it up ensures you stay updated, making your workflow smoother and more efficient, without
stepping out of MS Teams application.

View file

@ -778,6 +778,9 @@ importers:
tslib: tslib:
specifier: 2.6.3 specifier: 2.6.3
version: 2.6.3 version: 2.6.3
vitest:
specifier: 1.6.0
version: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
zod: zod:
specifier: 3.23.8 specifier: 3.23.8
version: 3.23.8 version: 3.23.8
@ -3628,12 +3631,6 @@ packages:
resolution: {integrity: sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==} resolution: {integrity: sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@esbuild/aix-ppc64@0.19.11':
resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [aix]
'@esbuild/aix-ppc64@0.20.2': '@esbuild/aix-ppc64@0.20.2':
resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3652,12 +3649,6 @@ packages:
cpu: [ppc64] cpu: [ppc64]
os: [aix] os: [aix]
'@esbuild/android-arm64@0.19.11':
resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
'@esbuild/android-arm64@0.20.2': '@esbuild/android-arm64@0.20.2':
resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3676,12 +3667,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@esbuild/android-arm@0.19.11':
resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
'@esbuild/android-arm@0.20.2': '@esbuild/android-arm@0.20.2':
resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3700,12 +3685,6 @@ packages:
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@esbuild/android-x64@0.19.11':
resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
'@esbuild/android-x64@0.20.2': '@esbuild/android-x64@0.20.2':
resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3724,12 +3703,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [android] os: [android]
'@esbuild/darwin-arm64@0.19.11':
resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
'@esbuild/darwin-arm64@0.20.2': '@esbuild/darwin-arm64@0.20.2':
resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3748,12 +3721,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@esbuild/darwin-x64@0.19.11':
resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
'@esbuild/darwin-x64@0.20.2': '@esbuild/darwin-x64@0.20.2':
resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3772,12 +3739,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@esbuild/freebsd-arm64@0.19.11':
resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
'@esbuild/freebsd-arm64@0.20.2': '@esbuild/freebsd-arm64@0.20.2':
resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3796,12 +3757,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@esbuild/freebsd-x64@0.19.11':
resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
'@esbuild/freebsd-x64@0.20.2': '@esbuild/freebsd-x64@0.20.2':
resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3820,12 +3775,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@esbuild/linux-arm64@0.19.11':
resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
'@esbuild/linux-arm64@0.20.2': '@esbuild/linux-arm64@0.20.2':
resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3844,12 +3793,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@esbuild/linux-arm@0.19.11':
resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
'@esbuild/linux-arm@0.20.2': '@esbuild/linux-arm@0.20.2':
resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3868,12 +3811,6 @@ packages:
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@esbuild/linux-ia32@0.19.11':
resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
'@esbuild/linux-ia32@0.20.2': '@esbuild/linux-ia32@0.20.2':
resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3892,12 +3829,6 @@ packages:
cpu: [ia32] cpu: [ia32]
os: [linux] os: [linux]
'@esbuild/linux-loong64@0.19.11':
resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
'@esbuild/linux-loong64@0.20.2': '@esbuild/linux-loong64@0.20.2':
resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3916,12 +3847,6 @@ packages:
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
'@esbuild/linux-mips64el@0.19.11':
resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
'@esbuild/linux-mips64el@0.20.2': '@esbuild/linux-mips64el@0.20.2':
resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3940,12 +3865,6 @@ packages:
cpu: [mips64el] cpu: [mips64el]
os: [linux] os: [linux]
'@esbuild/linux-ppc64@0.19.11':
resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
'@esbuild/linux-ppc64@0.20.2': '@esbuild/linux-ppc64@0.20.2':
resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3964,12 +3883,6 @@ packages:
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
'@esbuild/linux-riscv64@0.19.11':
resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
'@esbuild/linux-riscv64@0.20.2': '@esbuild/linux-riscv64@0.20.2':
resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -3988,12 +3901,6 @@ packages:
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
'@esbuild/linux-s390x@0.19.11':
resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
'@esbuild/linux-s390x@0.20.2': '@esbuild/linux-s390x@0.20.2':
resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4012,12 +3919,6 @@ packages:
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
'@esbuild/linux-x64@0.19.11':
resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
'@esbuild/linux-x64@0.20.2': '@esbuild/linux-x64@0.20.2':
resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4036,12 +3937,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@esbuild/netbsd-x64@0.19.11':
resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
'@esbuild/netbsd-x64@0.20.2': '@esbuild/netbsd-x64@0.20.2':
resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4066,12 +3961,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [openbsd] os: [openbsd]
'@esbuild/openbsd-x64@0.19.11':
resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
'@esbuild/openbsd-x64@0.20.2': '@esbuild/openbsd-x64@0.20.2':
resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4090,12 +3979,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
'@esbuild/sunos-x64@0.19.11':
resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
'@esbuild/sunos-x64@0.20.2': '@esbuild/sunos-x64@0.20.2':
resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4114,12 +3997,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [sunos] os: [sunos]
'@esbuild/win32-arm64@0.19.11':
resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
'@esbuild/win32-arm64@0.20.2': '@esbuild/win32-arm64@0.20.2':
resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4138,12 +4015,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@esbuild/win32-ia32@0.19.11':
resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
'@esbuild/win32-ia32@0.20.2': '@esbuild/win32-ia32@0.20.2':
resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4162,12 +4033,6 @@ packages:
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@esbuild/win32-x64@0.19.11':
resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
'@esbuild/win32-x64@0.20.2': '@esbuild/win32-x64@0.20.2':
resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -4994,7 +4859,6 @@ packages:
'@humanwhocodes/config-array@0.11.14': '@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
deprecated: Use @eslint/config-array instead
'@humanwhocodes/module-importer@1.0.1': '@humanwhocodes/module-importer@1.0.1':
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
@ -5002,7 +4866,6 @@ packages:
'@humanwhocodes/object-schema@2.0.2': '@humanwhocodes/object-schema@2.0.2':
resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
deprecated: Use @eslint/object-schema instead
'@ianvs/prettier-plugin-sort-imports@4.2.1': '@ianvs/prettier-plugin-sort-imports@4.2.1':
resolution: {integrity: sha512-NKN1LVFWUDGDGr3vt+6Ey3qPeN/163uR1pOPAlkWpgvAqgxQ6kSdUf1F0it8aHUtKRUzEGcK38Wxd07O61d7+Q==} resolution: {integrity: sha512-NKN1LVFWUDGDGr3vt+6Ey3qPeN/163uR1pOPAlkWpgvAqgxQ6kSdUf1F0it8aHUtKRUzEGcK38Wxd07O61d7+Q==}
@ -6900,11 +6763,6 @@ packages:
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@rollup/rollup-android-arm-eabi@4.6.1':
resolution: {integrity: sha512-0WQ0ouLejaUCRsL93GD4uft3rOmB8qoQMU05Kb8CmMtMBe7XUDLAltxVZI1q6byNqEtU7N1ZX1Vw5lIpgulLQA==}
cpu: [arm]
os: [android]
'@rollup/rollup-android-arm64@4.17.2': '@rollup/rollup-android-arm64@4.17.2':
resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==}
cpu: [arm64] cpu: [arm64]
@ -6915,11 +6773,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@rollup/rollup-android-arm64@4.6.1':
resolution: {integrity: sha512-1TKm25Rn20vr5aTGGZqo6E4mzPicCUD79k17EgTLAsXc1zysyi4xXKACfUbwyANEPAEIxkzwue6JZ+stYzWUTA==}
cpu: [arm64]
os: [android]
'@rollup/rollup-darwin-arm64@4.17.2': '@rollup/rollup-darwin-arm64@4.17.2':
resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==}
cpu: [arm64] cpu: [arm64]
@ -6930,11 +6783,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@rollup/rollup-darwin-arm64@4.6.1':
resolution: {integrity: sha512-cEXJQY/ZqMACb+nxzDeX9IPLAg7S94xouJJCNVE5BJM8JUEP4HeTF+ti3cmxWeSJo+5D+o8Tc0UAWUkfENdeyw==}
cpu: [arm64]
os: [darwin]
'@rollup/rollup-darwin-x64@4.17.2': '@rollup/rollup-darwin-x64@4.17.2':
resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==}
cpu: [x64] cpu: [x64]
@ -6945,11 +6793,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@rollup/rollup-darwin-x64@4.6.1':
resolution: {integrity: sha512-LoSU9Xu56isrkV2jLldcKspJ7sSXmZWkAxg7sW/RfF7GS4F5/v4EiqKSMCFbZtDu2Nc1gxxFdQdKwkKS4rwxNg==}
cpu: [x64]
os: [darwin]
'@rollup/rollup-linux-arm-gnueabihf@4.17.2': '@rollup/rollup-linux-arm-gnueabihf@4.17.2':
resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==}
cpu: [arm] cpu: [arm]
@ -6960,11 +6803,6 @@ packages:
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@rollup/rollup-linux-arm-gnueabihf@4.6.1':
resolution: {integrity: sha512-EfI3hzYAy5vFNDqpXsNxXcgRDcFHUWSx5nnRSCKwXuQlI5J9dD84g2Usw81n3FLBNsGCegKGwwTVsSKK9cooSQ==}
cpu: [arm]
os: [linux]
'@rollup/rollup-linux-arm-musleabihf@4.17.2': '@rollup/rollup-linux-arm-musleabihf@4.17.2':
resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==}
cpu: [arm] cpu: [arm]
@ -6985,11 +6823,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@rollup/rollup-linux-arm64-gnu@4.6.1':
resolution: {integrity: sha512-9lhc4UZstsegbNLhH0Zu6TqvDfmhGzuCWtcTFXY10VjLLUe4Mr0Ye2L3rrtHaDd/J5+tFMEuo5LTCSCMXWfUKw==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-arm64-musl@4.17.2': '@rollup/rollup-linux-arm64-musl@4.17.2':
resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==}
cpu: [arm64] cpu: [arm64]
@ -7000,11 +6833,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@rollup/rollup-linux-arm64-musl@4.6.1':
resolution: {integrity: sha512-FfoOK1yP5ksX3wwZ4Zk1NgyGHZyuRhf99j64I5oEmirV8EFT7+OhUZEnP+x17lcP/QHJNWGsoJwrz4PJ9fBEXw==}
cpu: [arm64]
os: [linux]
'@rollup/rollup-linux-powerpc64le-gnu@4.17.2': '@rollup/rollup-linux-powerpc64le-gnu@4.17.2':
resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==}
cpu: [ppc64] cpu: [ppc64]
@ -7045,11 +6873,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@rollup/rollup-linux-x64-gnu@4.6.1':
resolution: {integrity: sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==}
cpu: [x64]
os: [linux]
'@rollup/rollup-linux-x64-musl@4.17.2': '@rollup/rollup-linux-x64-musl@4.17.2':
resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==}
cpu: [x64] cpu: [x64]
@ -7060,11 +6883,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@rollup/rollup-linux-x64-musl@4.6.1':
resolution: {integrity: sha512-RkJVNVRM+piYy87HrKmhbexCHg3A6Z6MU0W9GHnJwBQNBeyhCJG9KDce4SAMdicQnpURggSvtbGo9xAWOfSvIQ==}
cpu: [x64]
os: [linux]
'@rollup/rollup-win32-arm64-msvc@4.17.2': '@rollup/rollup-win32-arm64-msvc@4.17.2':
resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==}
cpu: [arm64] cpu: [arm64]
@ -7075,11 +6893,6 @@ packages:
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@rollup/rollup-win32-arm64-msvc@4.6.1':
resolution: {integrity: sha512-v2FVT6xfnnmTe3W9bJXl6r5KwJglMK/iRlkKiIFfO6ysKs0rDgz7Cwwf3tjldxQUrHL9INT/1r4VA0n9L/F1vQ==}
cpu: [arm64]
os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.17.2': '@rollup/rollup-win32-ia32-msvc@4.17.2':
resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==}
cpu: [ia32] cpu: [ia32]
@ -7090,11 +6903,6 @@ packages:
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.6.1':
resolution: {integrity: sha512-YEeOjxRyEjqcWphH9dyLbzgkF8wZSKAKUkldRY6dgNR5oKs2LZazqGB41cWJ4Iqqcy9/zqYgmzBkRoVz3Q9MLw==}
cpu: [ia32]
os: [win32]
'@rollup/rollup-win32-x64-msvc@4.17.2': '@rollup/rollup-win32-x64-msvc@4.17.2':
resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==}
cpu: [x64] cpu: [x64]
@ -7105,11 +6913,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@rollup/rollup-win32-x64-msvc@4.6.1':
resolution: {integrity: sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==}
cpu: [x64]
os: [win32]
'@rushstack/eslint-patch@1.6.1': '@rushstack/eslint-patch@1.6.1':
resolution: {integrity: sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==} resolution: {integrity: sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==}
@ -10491,11 +10294,6 @@ packages:
peerDependencies: peerDependencies:
esbuild: '>=0.12 <1' esbuild: '>=0.12 <1'
esbuild@0.19.11:
resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==}
engines: {node: '>=12'}
hasBin: true
esbuild@0.20.2: esbuild@0.20.2:
resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -15360,11 +15158,6 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
rollup@4.6.1:
resolution: {integrity: sha512-jZHaZotEHQaHLgKr8JnQiDT1rmatjgKlMekyksz+yk9jt/8z9quNjnKNRoaM0wd9DC2QKXjmWWuDYtM3jfF8pQ==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
rss@1.2.2: rss@1.2.2:
resolution: {integrity: sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg==} resolution: {integrity: sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg==}
@ -16796,34 +16589,6 @@ packages:
vite: vite:
optional: true optional: true
vite@5.0.5:
resolution: {integrity: sha512-OekeWqR9Ls56f3zd4CaxzbbS11gqYkEiBtnWFFgYR2WV8oPJRRKq0mpskYy/XaoCL3L7VINDhqqOMNDiYdGvGg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
peerDependenciesMeta:
'@types/node':
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
vite@5.3.3: vite@5.3.3:
resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
@ -19935,9 +19700,6 @@ snapshots:
dependencies: dependencies:
tslib: 2.6.3 tslib: 2.6.3
'@esbuild/aix-ppc64@0.19.11':
optional: true
'@esbuild/aix-ppc64@0.20.2': '@esbuild/aix-ppc64@0.20.2':
optional: true optional: true
@ -19947,9 +19709,6 @@ snapshots:
'@esbuild/aix-ppc64@0.23.0': '@esbuild/aix-ppc64@0.23.0':
optional: true optional: true
'@esbuild/android-arm64@0.19.11':
optional: true
'@esbuild/android-arm64@0.20.2': '@esbuild/android-arm64@0.20.2':
optional: true optional: true
@ -19959,9 +19718,6 @@ snapshots:
'@esbuild/android-arm64@0.23.0': '@esbuild/android-arm64@0.23.0':
optional: true optional: true
'@esbuild/android-arm@0.19.11':
optional: true
'@esbuild/android-arm@0.20.2': '@esbuild/android-arm@0.20.2':
optional: true optional: true
@ -19971,9 +19727,6 @@ snapshots:
'@esbuild/android-arm@0.23.0': '@esbuild/android-arm@0.23.0':
optional: true optional: true
'@esbuild/android-x64@0.19.11':
optional: true
'@esbuild/android-x64@0.20.2': '@esbuild/android-x64@0.20.2':
optional: true optional: true
@ -19983,9 +19736,6 @@ snapshots:
'@esbuild/android-x64@0.23.0': '@esbuild/android-x64@0.23.0':
optional: true optional: true
'@esbuild/darwin-arm64@0.19.11':
optional: true
'@esbuild/darwin-arm64@0.20.2': '@esbuild/darwin-arm64@0.20.2':
optional: true optional: true
@ -19995,9 +19745,6 @@ snapshots:
'@esbuild/darwin-arm64@0.23.0': '@esbuild/darwin-arm64@0.23.0':
optional: true optional: true
'@esbuild/darwin-x64@0.19.11':
optional: true
'@esbuild/darwin-x64@0.20.2': '@esbuild/darwin-x64@0.20.2':
optional: true optional: true
@ -20007,9 +19754,6 @@ snapshots:
'@esbuild/darwin-x64@0.23.0': '@esbuild/darwin-x64@0.23.0':
optional: true optional: true
'@esbuild/freebsd-arm64@0.19.11':
optional: true
'@esbuild/freebsd-arm64@0.20.2': '@esbuild/freebsd-arm64@0.20.2':
optional: true optional: true
@ -20019,9 +19763,6 @@ snapshots:
'@esbuild/freebsd-arm64@0.23.0': '@esbuild/freebsd-arm64@0.23.0':
optional: true optional: true
'@esbuild/freebsd-x64@0.19.11':
optional: true
'@esbuild/freebsd-x64@0.20.2': '@esbuild/freebsd-x64@0.20.2':
optional: true optional: true
@ -20031,9 +19772,6 @@ snapshots:
'@esbuild/freebsd-x64@0.23.0': '@esbuild/freebsd-x64@0.23.0':
optional: true optional: true
'@esbuild/linux-arm64@0.19.11':
optional: true
'@esbuild/linux-arm64@0.20.2': '@esbuild/linux-arm64@0.20.2':
optional: true optional: true
@ -20043,9 +19781,6 @@ snapshots:
'@esbuild/linux-arm64@0.23.0': '@esbuild/linux-arm64@0.23.0':
optional: true optional: true
'@esbuild/linux-arm@0.19.11':
optional: true
'@esbuild/linux-arm@0.20.2': '@esbuild/linux-arm@0.20.2':
optional: true optional: true
@ -20055,9 +19790,6 @@ snapshots:
'@esbuild/linux-arm@0.23.0': '@esbuild/linux-arm@0.23.0':
optional: true optional: true
'@esbuild/linux-ia32@0.19.11':
optional: true
'@esbuild/linux-ia32@0.20.2': '@esbuild/linux-ia32@0.20.2':
optional: true optional: true
@ -20067,9 +19799,6 @@ snapshots:
'@esbuild/linux-ia32@0.23.0': '@esbuild/linux-ia32@0.23.0':
optional: true optional: true
'@esbuild/linux-loong64@0.19.11':
optional: true
'@esbuild/linux-loong64@0.20.2': '@esbuild/linux-loong64@0.20.2':
optional: true optional: true
@ -20079,9 +19808,6 @@ snapshots:
'@esbuild/linux-loong64@0.23.0': '@esbuild/linux-loong64@0.23.0':
optional: true optional: true
'@esbuild/linux-mips64el@0.19.11':
optional: true
'@esbuild/linux-mips64el@0.20.2': '@esbuild/linux-mips64el@0.20.2':
optional: true optional: true
@ -20091,9 +19817,6 @@ snapshots:
'@esbuild/linux-mips64el@0.23.0': '@esbuild/linux-mips64el@0.23.0':
optional: true optional: true
'@esbuild/linux-ppc64@0.19.11':
optional: true
'@esbuild/linux-ppc64@0.20.2': '@esbuild/linux-ppc64@0.20.2':
optional: true optional: true
@ -20103,9 +19826,6 @@ snapshots:
'@esbuild/linux-ppc64@0.23.0': '@esbuild/linux-ppc64@0.23.0':
optional: true optional: true
'@esbuild/linux-riscv64@0.19.11':
optional: true
'@esbuild/linux-riscv64@0.20.2': '@esbuild/linux-riscv64@0.20.2':
optional: true optional: true
@ -20115,9 +19835,6 @@ snapshots:
'@esbuild/linux-riscv64@0.23.0': '@esbuild/linux-riscv64@0.23.0':
optional: true optional: true
'@esbuild/linux-s390x@0.19.11':
optional: true
'@esbuild/linux-s390x@0.20.2': '@esbuild/linux-s390x@0.20.2':
optional: true optional: true
@ -20127,9 +19844,6 @@ snapshots:
'@esbuild/linux-s390x@0.23.0': '@esbuild/linux-s390x@0.23.0':
optional: true optional: true
'@esbuild/linux-x64@0.19.11':
optional: true
'@esbuild/linux-x64@0.20.2': '@esbuild/linux-x64@0.20.2':
optional: true optional: true
@ -20139,9 +19853,6 @@ snapshots:
'@esbuild/linux-x64@0.23.0': '@esbuild/linux-x64@0.23.0':
optional: true optional: true
'@esbuild/netbsd-x64@0.19.11':
optional: true
'@esbuild/netbsd-x64@0.20.2': '@esbuild/netbsd-x64@0.20.2':
optional: true optional: true
@ -20154,9 +19865,6 @@ snapshots:
'@esbuild/openbsd-arm64@0.23.0': '@esbuild/openbsd-arm64@0.23.0':
optional: true optional: true
'@esbuild/openbsd-x64@0.19.11':
optional: true
'@esbuild/openbsd-x64@0.20.2': '@esbuild/openbsd-x64@0.20.2':
optional: true optional: true
@ -20166,9 +19874,6 @@ snapshots:
'@esbuild/openbsd-x64@0.23.0': '@esbuild/openbsd-x64@0.23.0':
optional: true optional: true
'@esbuild/sunos-x64@0.19.11':
optional: true
'@esbuild/sunos-x64@0.20.2': '@esbuild/sunos-x64@0.20.2':
optional: true optional: true
@ -20178,9 +19883,6 @@ snapshots:
'@esbuild/sunos-x64@0.23.0': '@esbuild/sunos-x64@0.23.0':
optional: true optional: true
'@esbuild/win32-arm64@0.19.11':
optional: true
'@esbuild/win32-arm64@0.20.2': '@esbuild/win32-arm64@0.20.2':
optional: true optional: true
@ -20190,9 +19892,6 @@ snapshots:
'@esbuild/win32-arm64@0.23.0': '@esbuild/win32-arm64@0.23.0':
optional: true optional: true
'@esbuild/win32-ia32@0.19.11':
optional: true
'@esbuild/win32-ia32@0.20.2': '@esbuild/win32-ia32@0.20.2':
optional: true optional: true
@ -20202,9 +19901,6 @@ snapshots:
'@esbuild/win32-ia32@0.23.0': '@esbuild/win32-ia32@0.23.0':
optional: true optional: true
'@esbuild/win32-x64@0.19.11':
optional: true
'@esbuild/win32-x64@0.20.2': '@esbuild/win32-x64@0.20.2':
optional: true optional: true
@ -24110,45 +23806,30 @@ snapshots:
'@rollup/rollup-android-arm-eabi@4.18.0': '@rollup/rollup-android-arm-eabi@4.18.0':
optional: true optional: true
'@rollup/rollup-android-arm-eabi@4.6.1':
optional: true
'@rollup/rollup-android-arm64@4.17.2': '@rollup/rollup-android-arm64@4.17.2':
optional: true optional: true
'@rollup/rollup-android-arm64@4.18.0': '@rollup/rollup-android-arm64@4.18.0':
optional: true optional: true
'@rollup/rollup-android-arm64@4.6.1':
optional: true
'@rollup/rollup-darwin-arm64@4.17.2': '@rollup/rollup-darwin-arm64@4.17.2':
optional: true optional: true
'@rollup/rollup-darwin-arm64@4.18.0': '@rollup/rollup-darwin-arm64@4.18.0':
optional: true optional: true
'@rollup/rollup-darwin-arm64@4.6.1':
optional: true
'@rollup/rollup-darwin-x64@4.17.2': '@rollup/rollup-darwin-x64@4.17.2':
optional: true optional: true
'@rollup/rollup-darwin-x64@4.18.0': '@rollup/rollup-darwin-x64@4.18.0':
optional: true optional: true
'@rollup/rollup-darwin-x64@4.6.1':
optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.17.2': '@rollup/rollup-linux-arm-gnueabihf@4.17.2':
optional: true optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.18.0': '@rollup/rollup-linux-arm-gnueabihf@4.18.0':
optional: true optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.6.1':
optional: true
'@rollup/rollup-linux-arm-musleabihf@4.17.2': '@rollup/rollup-linux-arm-musleabihf@4.17.2':
optional: true optional: true
@ -24161,18 +23842,12 @@ snapshots:
'@rollup/rollup-linux-arm64-gnu@4.18.0': '@rollup/rollup-linux-arm64-gnu@4.18.0':
optional: true optional: true
'@rollup/rollup-linux-arm64-gnu@4.6.1':
optional: true
'@rollup/rollup-linux-arm64-musl@4.17.2': '@rollup/rollup-linux-arm64-musl@4.17.2':
optional: true optional: true
'@rollup/rollup-linux-arm64-musl@4.18.0': '@rollup/rollup-linux-arm64-musl@4.18.0':
optional: true optional: true
'@rollup/rollup-linux-arm64-musl@4.6.1':
optional: true
'@rollup/rollup-linux-powerpc64le-gnu@4.17.2': '@rollup/rollup-linux-powerpc64le-gnu@4.17.2':
optional: true optional: true
@ -24197,45 +23872,30 @@ snapshots:
'@rollup/rollup-linux-x64-gnu@4.18.0': '@rollup/rollup-linux-x64-gnu@4.18.0':
optional: true optional: true
'@rollup/rollup-linux-x64-gnu@4.6.1':
optional: true
'@rollup/rollup-linux-x64-musl@4.17.2': '@rollup/rollup-linux-x64-musl@4.17.2':
optional: true optional: true
'@rollup/rollup-linux-x64-musl@4.18.0': '@rollup/rollup-linux-x64-musl@4.18.0':
optional: true optional: true
'@rollup/rollup-linux-x64-musl@4.6.1':
optional: true
'@rollup/rollup-win32-arm64-msvc@4.17.2': '@rollup/rollup-win32-arm64-msvc@4.17.2':
optional: true optional: true
'@rollup/rollup-win32-arm64-msvc@4.18.0': '@rollup/rollup-win32-arm64-msvc@4.18.0':
optional: true optional: true
'@rollup/rollup-win32-arm64-msvc@4.6.1':
optional: true
'@rollup/rollup-win32-ia32-msvc@4.17.2': '@rollup/rollup-win32-ia32-msvc@4.17.2':
optional: true optional: true
'@rollup/rollup-win32-ia32-msvc@4.18.0': '@rollup/rollup-win32-ia32-msvc@4.18.0':
optional: true optional: true
'@rollup/rollup-win32-ia32-msvc@4.6.1':
optional: true
'@rollup/rollup-win32-x64-msvc@4.17.2': '@rollup/rollup-win32-x64-msvc@4.17.2':
optional: true optional: true
'@rollup/rollup-win32-x64-msvc@4.18.0': '@rollup/rollup-win32-x64-msvc@4.18.0':
optional: true optional: true
'@rollup/rollup-win32-x64-msvc@4.6.1':
optional: true
'@rushstack/eslint-patch@1.6.1': {} '@rushstack/eslint-patch@1.6.1': {}
'@sec-ant/readable-stream@0.4.1': {} '@sec-ant/readable-stream@0.4.1': {}
@ -28745,32 +28405,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
esbuild@0.19.11:
optionalDependencies:
'@esbuild/aix-ppc64': 0.19.11
'@esbuild/android-arm': 0.19.11
'@esbuild/android-arm64': 0.19.11
'@esbuild/android-x64': 0.19.11
'@esbuild/darwin-arm64': 0.19.11
'@esbuild/darwin-x64': 0.19.11
'@esbuild/freebsd-arm64': 0.19.11
'@esbuild/freebsd-x64': 0.19.11
'@esbuild/linux-arm': 0.19.11
'@esbuild/linux-arm64': 0.19.11
'@esbuild/linux-ia32': 0.19.11
'@esbuild/linux-loong64': 0.19.11
'@esbuild/linux-mips64el': 0.19.11
'@esbuild/linux-ppc64': 0.19.11
'@esbuild/linux-riscv64': 0.19.11
'@esbuild/linux-s390x': 0.19.11
'@esbuild/linux-x64': 0.19.11
'@esbuild/netbsd-x64': 0.19.11
'@esbuild/openbsd-x64': 0.19.11
'@esbuild/sunos-x64': 0.19.11
'@esbuild/win32-arm64': 0.19.11
'@esbuild/win32-ia32': 0.19.11
'@esbuild/win32-x64': 0.19.11
esbuild@0.20.2: esbuild@0.20.2:
optionalDependencies: optionalDependencies:
'@esbuild/aix-ppc64': 0.20.2 '@esbuild/aix-ppc64': 0.20.2
@ -35021,22 +34655,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.18.0 '@rollup/rollup-win32-x64-msvc': 4.18.0
fsevents: 2.3.3 fsevents: 2.3.3
rollup@4.6.1:
optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.6.1
'@rollup/rollup-android-arm64': 4.6.1
'@rollup/rollup-darwin-arm64': 4.6.1
'@rollup/rollup-darwin-x64': 4.6.1
'@rollup/rollup-linux-arm-gnueabihf': 4.6.1
'@rollup/rollup-linux-arm64-gnu': 4.6.1
'@rollup/rollup-linux-arm64-musl': 4.6.1
'@rollup/rollup-linux-x64-gnu': 4.6.1
'@rollup/rollup-linux-x64-musl': 4.6.1
'@rollup/rollup-win32-arm64-msvc': 4.6.1
'@rollup/rollup-win32-ia32-msvc': 4.6.1
'@rollup/rollup-win32-x64-msvc': 4.6.1
fsevents: 2.3.3
rss@1.2.2: rss@1.2.2:
dependencies: dependencies:
mime-types: 2.1.13 mime-types: 2.1.13
@ -36632,9 +36250,9 @@ snapshots:
vite-node@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1): vite-node@1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
pathe: 1.1.1 pathe: 1.1.1
picocolors: 1.0.0 picocolors: 1.0.1
vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
@ -36657,17 +36275,6 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
vite@5.0.5(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
dependencies:
esbuild: 0.19.11
postcss: 8.4.39
rollup: 4.6.1
optionalDependencies:
'@types/node': 20.14.10
fsevents: 2.3.3
less: 4.2.0
terser: 5.31.1
vite@5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1): vite@5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1):
dependencies: dependencies:
esbuild: 0.21.5 esbuild: 0.21.5
@ -36688,17 +36295,17 @@ snapshots:
'@vitest/utils': 1.6.0 '@vitest/utils': 1.6.0
acorn-walk: 8.3.2 acorn-walk: 8.3.2
chai: 4.4.1 chai: 4.4.1
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.5(supports-color@8.1.1)
execa: 8.0.1 execa: 8.0.1
local-pkg: 0.5.0 local-pkg: 0.5.0
magic-string: 0.30.5 magic-string: 0.30.5
pathe: 1.1.1 pathe: 1.1.1
picocolors: 1.0.0 picocolors: 1.0.1
std-env: 3.6.0 std-env: 3.6.0
strip-literal: 2.0.0 strip-literal: 2.0.0
tinybench: 2.5.1 tinybench: 2.5.1
tinypool: 0.8.3 tinypool: 0.8.3
vite: 5.0.5(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) vite: 5.3.3(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
vite-node: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1) vite-node: 1.6.0(@types/node@20.14.10)(less@4.2.0)(terser@5.31.1)
why-is-node-running: 2.2.2 why-is-node-running: 2.2.2
optionalDependencies: optionalDependencies:

View file

@ -1,3 +1,4 @@
import 'reflect-metadata';
import rawSnapshotSerializer from 'jest-snapshot-serializer-raw/always'; import rawSnapshotSerializer from 'jest-snapshot-serializer-raw/always';
import { expect } from 'vitest'; import { expect } from 'vitest';
import { normalizeCliOutput } from './serializers/cli-output'; import { normalizeCliOutput } from './serializers/cli-output';

View file

@ -9,7 +9,12 @@ export default defineConfig({
.pathname, .pathname,
}, },
globals: true, globals: true,
exclude: [...defaultExclude, 'integration-tests', 'packages/migrations/test'], exclude: [
...defaultExclude,
'integration-tests',
'packages/migrations/test',
'docker/.hive-dev',
],
setupFiles: ['./scripts/serializer.ts'], setupFiles: ['./scripts/serializer.ts'],
}, },
}); });