diff --git a/.gitignore b/.gitignore index 7d4070eb7..bf05f48e8 100644 --- a/.gitignore +++ b/.gitignore @@ -130,6 +130,7 @@ packages/web/app/environment-*.mjs packages/web/app/src/gql/*.ts packages/web/app/src/gql/*.json packages/web/app/src/graphql/*.ts +packages/web/docs/public/feed.xml # Changelog packages/web/app/src/components/ui/changelog/generated-changelog.ts @@ -142,6 +143,4 @@ deployment/utils/contour.types.ts schema.graphql resolvers.generated.ts -# generated by tsup -configs/tsup/dev.config.*.mjs -packages/web/docs/public/feed.xml +docker/docker-compose.override.yml diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..53d1c14db --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v22 diff --git a/README.md b/README.md index a346487a2..37c337fc8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ + +

+ + Created by The guild + +

+ + # GraphQL Hive 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 -GraphQL Hive is well integrated with **Slack** and most **CI/CD** systems to get you up and running -as smoothly as possible! +GraphQL Hive is well integrated with **Slack**, **MS Teams** and most **CI/CD** systems to get you +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, BitBucket, Azure, and others). The same applies to schema publishing and operations checks. diff --git a/codegen.mts b/codegen.mts index 1fbf26bc6..c5401b80e 100644 --- a/codegen.mts +++ b/codegen.mts @@ -140,6 +140,7 @@ const config: CodegenConfig = { AlertChannel: '../modules/alerts/module.graphql.mappers#AlertChannelMapper', AlertSlackChannel: '../modules/alerts/module.graphql.mappers#AlertSlackChannelMapper', AlertWebhookChannel: '../modules/alerts/module.graphql.mappers#AlertWebhookChannelMapper', + TeamsWebhookChannel: '../modules/alerts/module.graphql.mappers#TeamsWebhookChannelMapper', Alert: '../modules/alerts/module.graphql.mappers#AlertMapper', AdminQuery: '../modules/admin/module.graphql.mappers#AdminQueryMapper', AdminStats: '../modules/admin/module.graphql.mappers#AdminStatsMapper', diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 6f23ad19d..1a0060271 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -5,8 +5,9 @@ Developing Hive locally requires you to have the following software installed locally: - Node.js 21 (or `nvm` or `fnm`) -- pnpm v8 -- Docker +- pnpm v9 +- 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 diff --git a/package.json b/package.json index e76ea4846..a279d8cc0 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "build:web": "pnpm prebuild && pnpm turbo build --filter=./packages/web/* --color", "cargo:fix": "bash ./scripts/fix-symbolic-link.sh", "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", "generate": "pnpm --filter @hive/storage db:generate && pnpm graphql:generate", "graphql:generate": "graphql-codegen --config codegen.mts", @@ -33,7 +34,7 @@ "lint:env-template": "tsx scripts/check-env-template.ts", "lint:fix": "pnpm lint --fix", "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", "pre-commit": "exit 0 && lint-staged", "prebuild": "rimraf deploy-tmp && rimraf packages/**/deploy-tmp", diff --git a/packages/libraries/cli/examples/single.with.mutation.graphql b/packages/libraries/cli/examples/single.with.mutation.graphql new file mode 100644 index 000000000..0692d095c --- /dev/null +++ b/packages/libraries/cli/examples/single.with.mutation.graphql @@ -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 diff --git a/packages/migrations/src/actions/2024.06.11T10-10-00.ms-teams-webhook.ts b/packages/migrations/src/actions/2024.06.11T10-10-00.ms-teams-webhook.ts new file mode 100644 index 000000000..b4f1f2ec5 --- /dev/null +++ b/packages/migrations/src/actions/2024.06.11T10-10-00.ms-teams-webhook.ts @@ -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; diff --git a/packages/migrations/src/run-pg-migrations.ts b/packages/migrations/src/run-pg-migrations.ts index cf3edfa52..76b7c9557 100644 --- a/packages/migrations/src/run-pg-migrations.ts +++ b/packages/migrations/src/run-pg-migrations.ts @@ -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_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_06_11T10_10_00_ms_teams_webhook from './actions/2024.06.11T10-10-00.ms-teams-webhook'; import { runMigrations } from './pg-migrator'; 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_02_19_01T00_00_00_schema_check_store_breaking_change_metadata, migration_2024_04_09T10_10_00_check_approval_comment, + migration_2024_06_11T10_10_00_ms_teams_webhook, ], }); diff --git a/packages/services/api/package.json b/packages/services/api/package.json index 263a40b22..f4418b25e 100644 --- a/packages/services/api/package.json +++ b/packages/services/api/package.json @@ -62,6 +62,7 @@ "slonik": "30.4.4", "supertokens-node": "15.2.1", "tslib": "2.6.3", + "vitest": "1.6.0", "zod": "3.23.8", "zod-validation-error": "3.3.0" } diff --git a/packages/services/api/src/modules/alerts/index.ts b/packages/services/api/src/modules/alerts/index.ts index 3c386cb63..dfcb8df39 100644 --- a/packages/services/api/src/modules/alerts/index.ts +++ b/packages/services/api/src/modules/alerts/index.ts @@ -1,4 +1,5 @@ import { createModule } from 'graphql-modules'; +import { TeamsCommunicationAdapter } from './providers/adapters/msteams'; import { SlackCommunicationAdapter } from './providers/adapters/slack'; import { WebhookCommunicationAdapter } from './providers/adapters/webhook'; import { AlertsManager } from './providers/alerts-manager'; @@ -10,5 +11,10 @@ export const alertsModule = createModule({ dirname: __dirname, typeDefs, resolvers, - providers: [AlertsManager, SlackCommunicationAdapter, WebhookCommunicationAdapter], + providers: [ + AlertsManager, + SlackCommunicationAdapter, + WebhookCommunicationAdapter, + TeamsCommunicationAdapter, + ], }); diff --git a/packages/services/api/src/modules/alerts/module.graphql.mappers.ts b/packages/services/api/src/modules/alerts/module.graphql.mappers.ts index 9e349a1b9..c34397be6 100644 --- a/packages/services/api/src/modules/alerts/module.graphql.mappers.ts +++ b/packages/services/api/src/modules/alerts/module.graphql.mappers.ts @@ -3,4 +3,5 @@ import type { Alert, AlertChannel } from '../../shared/entities'; export type AlertChannelMapper = AlertChannel; export type AlertSlackChannelMapper = AlertChannel; export type AlertWebhookChannelMapper = AlertChannel; +export type TeamsWebhookChannelMapper = AlertChannel; export type AlertMapper = Alert; diff --git a/packages/services/api/src/modules/alerts/module.graphql.ts b/packages/services/api/src/modules/alerts/module.graphql.ts index c4a304985..85fd65180 100644 --- a/packages/services/api/src/modules/alerts/module.graphql.ts +++ b/packages/services/api/src/modules/alerts/module.graphql.ts @@ -16,6 +16,7 @@ export default gql` enum AlertChannelType { SLACK WEBHOOK + MSTEAMS_WEBHOOK } enum AlertType { @@ -140,6 +141,13 @@ export default gql` endpoint: String! } + type TeamsWebhookChannel implements AlertChannel { + id: ID! + name: String! + type: AlertChannelType! + endpoint: String! + } + type Alert { id: ID! type: AlertType! diff --git a/packages/services/api/src/modules/alerts/providers/adapters/common.ts b/packages/services/api/src/modules/alerts/providers/adapters/common.ts index fb525ee40..c8ef280ee 100644 --- a/packages/services/api/src/modules/alerts/providers/adapters/common.ts +++ b/packages/services/api/src/modules/alerts/providers/adapters/common.ts @@ -8,6 +8,12 @@ import type { Target, } from '../../../../shared/entities'; +interface NotificationIntegrations { + slack: { + token: string | null | undefined; + }; +} + export interface SchemaChangeNotificationInput { event: { organization: Pick; @@ -25,11 +31,7 @@ export interface SchemaChangeNotificationInput { }; alert: Alert; channel: AlertChannel; - integrations: { - slack: { - token: string; - }; - }; + integrations: NotificationIntegrations; } export interface ChannelConfirmationInput { @@ -39,11 +41,7 @@ export interface ChannelConfirmationInput { project: Pick; }; channel: AlertChannel; - integrations: { - slack: { - token: string; - }; - }; + integrations: NotificationIntegrations; } export interface CommunicationAdapter { @@ -59,9 +57,13 @@ export function quotesTransformer(msg: string, symbols = '**') { const findSingleQuotes = /'([^']+)'/gim; const findDoubleQuotes = /"([^"]+)"/gim; - function transformm(_: string, value: string) { + function transform(_: string, value: string) { 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})`; +}; diff --git a/packages/services/api/src/modules/alerts/providers/adapters/msteams.spec.ts b/packages/services/api/src/modules/alerts/providers/adapters/msteams.spec.ts new file mode 100644 index 000000000..cb0791034 --- /dev/null +++ b/packages/services/api/src/modules/alerts/providers/adapters/msteams.spec.ts @@ -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; + 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'); + }); + }); +}); diff --git a/packages/services/api/src/modules/alerts/providers/adapters/msteams.ts b/packages/services/api/src/modules/alerts/providers/adapters/msteams.ts new file mode 100644 index 000000000..8b986577b --- /dev/null +++ b/packages/services/api/src/modules/alerts/providers/adapters/msteams.ts @@ -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`; +} diff --git a/packages/services/api/src/modules/alerts/providers/adapters/slack.ts b/packages/services/api/src/modules/alerts/providers/adapters/slack.ts index bba0576eb..a48987d5e 100644 --- a/packages/services/api/src/modules/alerts/providers/adapters/slack.ts +++ b/packages/services/api/src/modules/alerts/providers/adapters/slack.ts @@ -7,6 +7,7 @@ import { WEB_APP_URL } from '../../../shared/providers/tokens'; import { ChannelConfirmationInput, CommunicationAdapter, + createMDLink, SchemaChangeNotificationInput, slackCoderize, } from './common'; @@ -43,15 +44,15 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { const client = new WebClient(input.integrations.slack.token, {}); const totalChanges = input.event.changes.length + input.event.messages.length; - const projectLink = this.createLink({ + const projectLink = createMDLink({ text: input.event.project.name, url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`, }); - const targetLink = this.createLink({ + const targetLink = createMDLink({ text: input.event.target.name, 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', 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) { this.logger.debug( `Sending Channel Confirmation over Slack (organization=%s, project=%s, channel=%s)`, @@ -90,7 +94,7 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { input.channel.slackChannel, ); - const token = input.integrations.slack.token; + const token = input.integrations?.slack.token; if (!token) { this.logger.debug(`Slack Integration is not available`); @@ -103,7 +107,7 @@ export class SlackCommunicationAdapter implements CommunicationAdapter { : `I will no longer send here notifications`; try { - const projectLink = this.createLink({ + const projectLink = createMDLink({ text: input.event.project.name, url: `${this.appBaseUrl}/${input.event.organization.cleanId}/${input.event.project.cleanId}`, }); diff --git a/packages/services/api/src/modules/alerts/providers/alerts-manager.ts b/packages/services/api/src/modules/alerts/providers/alerts-manager.ts index e8db14d4f..293463642 100644 --- a/packages/services/api/src/modules/alerts/providers/alerts-manager.ts +++ b/packages/services/api/src/modules/alerts/providers/alerts-manager.ts @@ -12,7 +12,8 @@ import { ProjectManager } from '../../project/providers/project-manager'; import { Logger } from '../../shared/providers/logger'; import type { ProjectSelector } 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 { WebhookCommunicationAdapter } from './adapters/webhook'; @@ -29,6 +30,7 @@ export class AlertsManager { private slackIntegrationManager: SlackIntegrationManager, private slack: SlackCommunicationAdapter, private webhook: WebhookCommunicationAdapter, + private teamsWebhook: TeamsCommunicationAdapter, private organizationManager: OrganizationManager, private projectManager: ProjectManager, private storage: Storage, @@ -185,6 +187,7 @@ export class AlertsManager { channel: channels.find(channel => channel.id === alert.channelId)!, }; }); + console.log('pairs:', pairs); const slackToken = await this.slackIntegrationManager.getToken({ organization: event.organization.id, @@ -195,8 +198,9 @@ export class AlertsManager { const integrations: SchemaChangeNotificationInput['integrations'] = { 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 :) @@ -237,6 +241,14 @@ export class AlertsManager { integrations, }); } + if (channel.type === 'MSTEAMS_WEBHOOK') { + return this.teamsWebhook.sendSchemaChangeNotification({ + event: safeEvent, + alert, + channel, + integrations, + }); + } return this.webhook.sendSchemaChangeNotification({ 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') { const slackToken = await this.slackIntegrationManager.getToken({ organization: organization.id, project: project.id, context: IntegrationsAccessContext.ChannelConfirmation, }); + if (!slackToken) { + throw new Error(`Slack token was not found for channel "${channel.id}"`); + } - await this.slack.sendChannelConfirmation({ - 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: slackToken!, - }, - }, - }); + channelConfirmationContext.integrations.slack.token = slackToken; + await this.slack.sendChannelConfirmation(channelConfirmationContext); + } else if (channel.type === 'MSTEAMS_WEBHOOK') { + await this.teamsWebhook.sendChannelConfirmation(channelConfirmationContext); } else { await this.webhook.sendChannelConfirmation(); } diff --git a/packages/services/api/src/modules/alerts/resolvers.ts b/packages/services/api/src/modules/alerts/resolvers.ts index 0def9690d..91e99392a 100644 --- a/packages/services/api/src/modules/alerts/resolvers.ts +++ b/packages/services/api/src/modules/alerts/resolvers.ts @@ -217,4 +217,12 @@ export const resolvers: AlertsModule.Resolvers = { return channel.webhookEndpoint!; }, }, + TeamsWebhookChannel: { + __isTypeOf(channel) { + return channel.type === 'MSTEAMS_WEBHOOK'; + }, + endpoint(channel) { + return channel.webhookEndpoint!; + }, + }, }; diff --git a/packages/services/storage/src/db/types.ts b/packages/services/storage/src/db/types.ts index 932160950..51675daf2 100644 --- a/packages/services/storage/src/db/types.ts +++ b/packages/services/storage/src/db/types.ts @@ -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 schema_policy_resource = 'ORGANIZATION' | 'PROJECT'; export type user_role = 'ADMIN' | 'MEMBER'; diff --git a/packages/web/app/src/components/project/alerts/channels-table.tsx b/packages/web/app/src/components/project/alerts/channels-table.tsx index 3853695cf..f1a040575 100644 --- a/packages/web/app/src/components/project/alerts/channels-table.tsx +++ b/packages/web/app/src/components/project/alerts/channels-table.tsx @@ -1,6 +1,6 @@ import { Checkbox, Table, Tag, TBody, Td, Tr } from '@/components/v2'; import { FragmentType, graphql, useFragment } from '@/gql'; -import { AlertChannelType } from '@/gql/graphql'; +import { AlertChannelType, ChannelsTable_AlertChannelFragmentFragment } from '@/gql/graphql'; export const ChannelsTable_AlertChannelFragment = graphql(` fragment ChannelsTable_AlertChannelFragment on AlertChannel { @@ -13,9 +13,18 @@ export const ChannelsTable_AlertChannelFragment = graphql(` ... on AlertWebhookChannel { 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: { channels: FragmentType[]; isChecked: (channelId: string) => boolean; @@ -23,6 +32,20 @@ export function ChannelsTable(props: { }) { 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 ( @@ -36,18 +59,12 @@ export function ChannelsTable(props: { checked={props.isChecked(channel.id)} /> - - + - ))} diff --git a/packages/web/app/src/components/project/alerts/create-channel.tsx b/packages/web/app/src/components/project/alerts/create-channel.tsx index 80b024e1d..2d8609eb0 100644 --- a/packages/web/app/src/components/project/alerts/create-channel.tsx +++ b/packages/web/app/src/components/project/alerts/create-channel.tsx @@ -61,8 +61,8 @@ export const CreateChannelModal = ({ ), endpoint: Yup.string() .url() - .when('type', ([type], schema) => - type === AlertChannelType.Webhook ? schema.required('Must enter endpoint') : schema, + .when('type', ([_type], schema) => + isWebhookLike ? schema.required('Must enter endpoint') : schema, ), }), async onSubmit(values) { @@ -73,8 +73,7 @@ export const CreateChannelModal = ({ name: values.name, type: values.type, slack: values.type === AlertChannelType.Slack ? { channel: values.slackChannel } : null, - webhook: - values.type === AlertChannelType.Webhook ? { endpoint: values.endpoint } : null, + webhook: isWebhookLike ? { endpoint: values.endpoint } : null, }, }); if (error) { @@ -88,6 +87,9 @@ export const CreateChannelModal = ({ } }, }); + const isWebhookLike = [AlertChannelType.Webhook, AlertChannelType.MsteamsWebhook].includes( + values.type, + ); return ( @@ -132,12 +134,13 @@ export const CreateChannelModal = ({ options={[ { value: AlertChannelType.Slack, name: 'Slack' }, { value: AlertChannelType.Webhook, name: 'Webhook' }, + { value: AlertChannelType.MsteamsWebhook, name: 'MS Teams Webhook' }, ]} /> {touched.type && errors.type &&
{errors.type}
} - {values.type === AlertChannelType.Webhook && ( + {isWebhookLike && (
)} -

Hive will send alerts to your endpoint.

+ {values.endpoint ? ( +

Hive will send alerts to your endpoint.

+ ) : ( + + Follow this guide to set up an incoming webhook connector in MS Teams + + )} )} @@ -205,7 +214,7 @@ export const CreateChannelModal = ({
{channel.name} - {channel.__typename === 'AlertSlackChannel' - ? channel.channel - : channel.__typename === 'AlertWebhookChannel' - ? channel.endpoint - : ''} + {channel.name} + {renderChannelEndpoint(channel)} - - {channel.type} - + + {channel.type}
+
{children}
); diff --git a/packages/web/app/src/components/v2/tag.tsx b/packages/web/app/src/components/v2/tag.tsx index c3baff9f3..f458d7b42 100644 --- a/packages/web/app/src/components/v2/tag.tsx +++ b/packages/web/app/src/components/v2/tag.tsx @@ -5,6 +5,7 @@ const colors = { green: 'bg-green-500/10 text-green-500', yellow: 'bg-yellow-500/10 text-yellow-500', gray: 'bg-gray-500/10 text-gray-500', + orange: 'bg-orange-500/10 text-orange-500', } as const; export function Tag({ @@ -12,13 +13,17 @@ export function Tag({ color = 'gray', className, }: { - color?: 'green' | 'yellow' | 'gray'; + color?: keyof typeof colors; className?: string; children: ReactNode; }): ReactElement { return ( {children} diff --git a/packages/web/docs/public/changelog/2024-06-14-ms-teams-webhooks-alerts/project-msteams-alert-full.png b/packages/web/docs/public/changelog/2024-06-14-ms-teams-webhooks-alerts/project-msteams-alert-full.png new file mode 100644 index 000000000..4b30eb31b Binary files /dev/null and b/packages/web/docs/public/changelog/2024-06-14-ms-teams-webhooks-alerts/project-msteams-alert-full.png differ diff --git a/packages/web/docs/public/docs/pages/management/project-msteams-alert.png b/packages/web/docs/public/docs/pages/management/project-msteams-alert.png new file mode 100644 index 000000000..a6fc6aa83 Binary files /dev/null and b/packages/web/docs/public/docs/pages/management/project-msteams-alert.png differ diff --git a/packages/web/docs/src/authors.ts b/packages/web/docs/src/authors.ts index 502ea8849..ba83cbad4 100644 --- a/packages/web/docs/src/authors.ts +++ b/packages/web/docs/src/authors.ts @@ -21,4 +21,9 @@ export const authors: Record = { link: 'https://twitter.com/aleksandrasays', github: 'beerose', }, + jiri: { + name: 'Jiri Spac', + link: 'https://twitter.com/capajj', + github: 'capaj', + }, }; diff --git a/packages/web/docs/src/pages/docs/management/projects.mdx b/packages/web/docs/src/pages/docs/management/projects.mdx index 182ee262d..002ffc2fb 100644 --- a/packages/web/docs/src/pages/docs/management/projects.mdx +++ b/packages/web/docs/src/pages/docs/management/projects.mdx @@ -5,6 +5,7 @@ import githubIntegrationImage from '../../../../public/docs/pages/management/org import slackIntegrationImage from '../../../../public/docs/pages/management/org-slack-integration.png' import projectGitLinkImage from '../../../../public/docs/pages/management/project-git-link.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' # Project Management @@ -140,6 +141,28 @@ Hive project: invitation, our Slack app will be unable to post messages to the channel. +#### 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). + + + #### HTTP Webhook You can implement a custom HTTP webhook to receive alerts and notifications from Hive, when an alert diff --git a/packages/web/docs/src/pages/product-updates/2024-06-14-ms-teams-webhooks-alerts.mdx b/packages/web/docs/src/pages/product-updates/2024-06-14-ms-teams-webhooks-alerts.mdx new file mode 100644 index 000000000..1bd8fa659 --- /dev/null +++ b/packages/web/docs/src/pages/product-updates/2024-06-14-ms-teams-webhooks-alerts.mdx @@ -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] +--- + +We’re 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) + +Here’s 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. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbba0ff84..667530b75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -778,6 +778,9 @@ importers: tslib: specifier: 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: specifier: 3.23.8 version: 3.23.8 @@ -3628,12 +3631,6 @@ packages: resolution: {integrity: sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==} 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': resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} @@ -3652,12 +3649,6 @@ packages: cpu: [ppc64] 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': resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} @@ -3676,12 +3667,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} @@ -3700,12 +3685,6 @@ packages: cpu: [arm] 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': resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} @@ -3724,12 +3703,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} @@ -3748,12 +3721,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} @@ -3772,12 +3739,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} @@ -3796,12 +3757,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} @@ -3820,12 +3775,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} @@ -3844,12 +3793,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} @@ -3868,12 +3811,6 @@ packages: cpu: [arm] 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': resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} @@ -3892,12 +3829,6 @@ packages: cpu: [ia32] 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': resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} @@ -3916,12 +3847,6 @@ packages: cpu: [loong64] 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': resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} @@ -3940,12 +3865,6 @@ packages: cpu: [mips64el] 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': resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} @@ -3964,12 +3883,6 @@ packages: cpu: [ppc64] 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': resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} @@ -3988,12 +3901,6 @@ packages: cpu: [riscv64] 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': resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} @@ -4012,12 +3919,6 @@ packages: cpu: [s390x] 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': resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} @@ -4036,12 +3937,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} @@ -4066,12 +3961,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} @@ -4090,12 +3979,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} @@ -4114,12 +3997,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} @@ -4138,12 +4015,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} @@ -4162,12 +4033,6 @@ packages: cpu: [ia32] 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': resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} @@ -4994,7 +4859,6 @@ packages: '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -5002,7 +4866,6 @@ packages: '@humanwhocodes/object-schema@2.0.2': resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} - deprecated: Use @eslint/object-schema instead '@ianvs/prettier-plugin-sort-imports@4.2.1': resolution: {integrity: sha512-NKN1LVFWUDGDGr3vt+6Ey3qPeN/163uR1pOPAlkWpgvAqgxQ6kSdUf1F0it8aHUtKRUzEGcK38Wxd07O61d7+Q==} @@ -6900,11 +6763,6 @@ packages: cpu: [arm] 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': resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} cpu: [arm64] @@ -6915,11 +6773,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} cpu: [arm64] @@ -6930,11 +6783,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} cpu: [x64] @@ -6945,11 +6793,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} cpu: [arm] @@ -6960,11 +6803,6 @@ packages: cpu: [arm] 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': resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} cpu: [arm] @@ -6985,11 +6823,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} cpu: [arm64] @@ -7000,11 +6833,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} cpu: [ppc64] @@ -7045,11 +6873,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} cpu: [x64] @@ -7060,11 +6883,6 @@ packages: cpu: [x64] 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': resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} cpu: [arm64] @@ -7075,11 +6893,6 @@ packages: cpu: [arm64] 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': resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} cpu: [ia32] @@ -7090,11 +6903,6 @@ packages: cpu: [ia32] 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': resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} cpu: [x64] @@ -7105,11 +6913,6 @@ packages: cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.6.1': - resolution: {integrity: sha512-0zfTlFAIhgz8V2G8STq8toAjsYYA6eci1hnXuyOTUFnymrtJwnS6uGKiv3v5UrPZkBlamLvrLV2iiaeqCKzb0A==} - cpu: [x64] - os: [win32] - '@rushstack/eslint-patch@1.6.1': resolution: {integrity: sha512-UY+FGM/2jjMkzQLn8pxcHGMaVLh9aEitG3zY2CiY7XHdLiz3bZOwa6oDxNqEMv7zZkV+cj5DOdz0cQ1BP5Hjgw==} @@ -10491,11 +10294,6 @@ packages: peerDependencies: 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: resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} @@ -15360,11 +15158,6 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} 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: resolution: {integrity: sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg==} @@ -16796,34 +16589,6 @@ packages: vite: 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: resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} engines: {node: ^18.0.0 || >=20.0.0} @@ -19935,9 +19700,6 @@ snapshots: dependencies: tslib: 2.6.3 - '@esbuild/aix-ppc64@0.19.11': - optional: true - '@esbuild/aix-ppc64@0.20.2': optional: true @@ -19947,9 +19709,6 @@ snapshots: '@esbuild/aix-ppc64@0.23.0': optional: true - '@esbuild/android-arm64@0.19.11': - optional: true - '@esbuild/android-arm64@0.20.2': optional: true @@ -19959,9 +19718,6 @@ snapshots: '@esbuild/android-arm64@0.23.0': optional: true - '@esbuild/android-arm@0.19.11': - optional: true - '@esbuild/android-arm@0.20.2': optional: true @@ -19971,9 +19727,6 @@ snapshots: '@esbuild/android-arm@0.23.0': optional: true - '@esbuild/android-x64@0.19.11': - optional: true - '@esbuild/android-x64@0.20.2': optional: true @@ -19983,9 +19736,6 @@ snapshots: '@esbuild/android-x64@0.23.0': optional: true - '@esbuild/darwin-arm64@0.19.11': - optional: true - '@esbuild/darwin-arm64@0.20.2': optional: true @@ -19995,9 +19745,6 @@ snapshots: '@esbuild/darwin-arm64@0.23.0': optional: true - '@esbuild/darwin-x64@0.19.11': - optional: true - '@esbuild/darwin-x64@0.20.2': optional: true @@ -20007,9 +19754,6 @@ snapshots: '@esbuild/darwin-x64@0.23.0': optional: true - '@esbuild/freebsd-arm64@0.19.11': - optional: true - '@esbuild/freebsd-arm64@0.20.2': optional: true @@ -20019,9 +19763,6 @@ snapshots: '@esbuild/freebsd-arm64@0.23.0': optional: true - '@esbuild/freebsd-x64@0.19.11': - optional: true - '@esbuild/freebsd-x64@0.20.2': optional: true @@ -20031,9 +19772,6 @@ snapshots: '@esbuild/freebsd-x64@0.23.0': optional: true - '@esbuild/linux-arm64@0.19.11': - optional: true - '@esbuild/linux-arm64@0.20.2': optional: true @@ -20043,9 +19781,6 @@ snapshots: '@esbuild/linux-arm64@0.23.0': optional: true - '@esbuild/linux-arm@0.19.11': - optional: true - '@esbuild/linux-arm@0.20.2': optional: true @@ -20055,9 +19790,6 @@ snapshots: '@esbuild/linux-arm@0.23.0': optional: true - '@esbuild/linux-ia32@0.19.11': - optional: true - '@esbuild/linux-ia32@0.20.2': optional: true @@ -20067,9 +19799,6 @@ snapshots: '@esbuild/linux-ia32@0.23.0': optional: true - '@esbuild/linux-loong64@0.19.11': - optional: true - '@esbuild/linux-loong64@0.20.2': optional: true @@ -20079,9 +19808,6 @@ snapshots: '@esbuild/linux-loong64@0.23.0': optional: true - '@esbuild/linux-mips64el@0.19.11': - optional: true - '@esbuild/linux-mips64el@0.20.2': optional: true @@ -20091,9 +19817,6 @@ snapshots: '@esbuild/linux-mips64el@0.23.0': optional: true - '@esbuild/linux-ppc64@0.19.11': - optional: true - '@esbuild/linux-ppc64@0.20.2': optional: true @@ -20103,9 +19826,6 @@ snapshots: '@esbuild/linux-ppc64@0.23.0': optional: true - '@esbuild/linux-riscv64@0.19.11': - optional: true - '@esbuild/linux-riscv64@0.20.2': optional: true @@ -20115,9 +19835,6 @@ snapshots: '@esbuild/linux-riscv64@0.23.0': optional: true - '@esbuild/linux-s390x@0.19.11': - optional: true - '@esbuild/linux-s390x@0.20.2': optional: true @@ -20127,9 +19844,6 @@ snapshots: '@esbuild/linux-s390x@0.23.0': optional: true - '@esbuild/linux-x64@0.19.11': - optional: true - '@esbuild/linux-x64@0.20.2': optional: true @@ -20139,9 +19853,6 @@ snapshots: '@esbuild/linux-x64@0.23.0': optional: true - '@esbuild/netbsd-x64@0.19.11': - optional: true - '@esbuild/netbsd-x64@0.20.2': optional: true @@ -20154,9 +19865,6 @@ snapshots: '@esbuild/openbsd-arm64@0.23.0': optional: true - '@esbuild/openbsd-x64@0.19.11': - optional: true - '@esbuild/openbsd-x64@0.20.2': optional: true @@ -20166,9 +19874,6 @@ snapshots: '@esbuild/openbsd-x64@0.23.0': optional: true - '@esbuild/sunos-x64@0.19.11': - optional: true - '@esbuild/sunos-x64@0.20.2': optional: true @@ -20178,9 +19883,6 @@ snapshots: '@esbuild/sunos-x64@0.23.0': optional: true - '@esbuild/win32-arm64@0.19.11': - optional: true - '@esbuild/win32-arm64@0.20.2': optional: true @@ -20190,9 +19892,6 @@ snapshots: '@esbuild/win32-arm64@0.23.0': optional: true - '@esbuild/win32-ia32@0.19.11': - optional: true - '@esbuild/win32-ia32@0.20.2': optional: true @@ -20202,9 +19901,6 @@ snapshots: '@esbuild/win32-ia32@0.23.0': optional: true - '@esbuild/win32-x64@0.19.11': - optional: true - '@esbuild/win32-x64@0.20.2': optional: true @@ -24110,45 +23806,30 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.18.0': optional: true - '@rollup/rollup-android-arm-eabi@4.6.1': - optional: true - '@rollup/rollup-android-arm64@4.17.2': optional: true '@rollup/rollup-android-arm64@4.18.0': optional: true - '@rollup/rollup-android-arm64@4.6.1': - optional: true - '@rollup/rollup-darwin-arm64@4.17.2': optional: true '@rollup/rollup-darwin-arm64@4.18.0': optional: true - '@rollup/rollup-darwin-arm64@4.6.1': - optional: true - '@rollup/rollup-darwin-x64@4.17.2': optional: true '@rollup/rollup-darwin-x64@4.18.0': optional: true - '@rollup/rollup-darwin-x64@4.6.1': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.17.2': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.18.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.6.1': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.17.2': optional: true @@ -24161,18 +23842,12 @@ snapshots: '@rollup/rollup-linux-arm64-gnu@4.18.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.6.1': - optional: true - '@rollup/rollup-linux-arm64-musl@4.17.2': optional: true '@rollup/rollup-linux-arm64-musl@4.18.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.6.1': - optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.17.2': optional: true @@ -24197,45 +23872,30 @@ snapshots: '@rollup/rollup-linux-x64-gnu@4.18.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.6.1': - optional: true - '@rollup/rollup-linux-x64-musl@4.17.2': optional: true '@rollup/rollup-linux-x64-musl@4.18.0': optional: true - '@rollup/rollup-linux-x64-musl@4.6.1': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.17.2': optional: true '@rollup/rollup-win32-arm64-msvc@4.18.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.6.1': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.17.2': optional: true '@rollup/rollup-win32-ia32-msvc@4.18.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.6.1': - optional: true - '@rollup/rollup-win32-x64-msvc@4.17.2': optional: true '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.6.1': - optional: true - '@rushstack/eslint-patch@1.6.1': {} '@sec-ant/readable-stream@0.4.1': {} @@ -28745,32 +28405,6 @@ snapshots: transitivePeerDependencies: - 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: optionalDependencies: '@esbuild/aix-ppc64': 0.20.2 @@ -35021,22 +34655,6 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.18.0 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: dependencies: 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): dependencies: 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 - 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) transitivePeerDependencies: - '@types/node' @@ -36657,17 +36275,6 @@ snapshots: - supports-color - 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): dependencies: esbuild: 0.21.5 @@ -36688,17 +36295,17 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 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 local-pkg: 0.5.0 magic-string: 0.30.5 pathe: 1.1.1 - picocolors: 1.0.0 + picocolors: 1.0.1 std-env: 3.6.0 strip-literal: 2.0.0 tinybench: 2.5.1 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) why-is-node-running: 2.2.2 optionalDependencies: diff --git a/scripts/serializer.ts b/scripts/serializer.ts index 3cb1cc488..7c7978c2a 100644 --- a/scripts/serializer.ts +++ b/scripts/serializer.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata'; import rawSnapshotSerializer from 'jest-snapshot-serializer-raw/always'; import { expect } from 'vitest'; import { normalizeCliOutput } from './serializers/cli-output'; diff --git a/vitest.config.ts b/vitest.config.ts index 238f340c0..2a1e099fb 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,7 +9,12 @@ export default defineConfig({ .pathname, }, globals: true, - exclude: [...defaultExclude, 'integration-tests', 'packages/migrations/test'], + exclude: [ + ...defaultExclude, + 'integration-tests', + 'packages/migrations/test', + 'docker/.hive-dev', + ], setupFiles: ['./scripts/serializer.ts'], }, });