diff --git a/integrations/slack/definitions/channels/text-input-schema.ts b/integrations/slack/definitions/channels/text-input-schema.ts index e8d5ce80278..69d66b49885 100644 --- a/integrations/slack/definitions/channels/text-input-schema.ts +++ b/integrations/slack/definitions/channels/text-input-schema.ts @@ -411,8 +411,7 @@ export const textSchema = sdk.z .string() .describe( 'Field text must be defined but it is ignored if blocks are provided. In this situation, the text must be provided in the blocks array' - ) - .optional(), + ), blocks: sdk.z .array(blocks) .max(50) diff --git a/integrations/slack/definitions/configuration.ts b/integrations/slack/definitions/configuration.ts index 2a00ca84de9..1a296adbdbf 100644 --- a/integrations/slack/definitions/configuration.ts +++ b/integrations/slack/definitions/configuration.ts @@ -13,22 +13,21 @@ const SHARED_CONFIGURATION = { .default(false) .title('Typing Indicator Emoji') .describe('Temporarily add an emoji to received messages to indicate when bot is processing message'), - createReplyThread: sdk.z + replyBehaviour: sdk.z .object({ - enabled: sdk.z - .boolean() - .default(false) - .title('Reply Threading Enabled') - .describe('When enabled, the bot will forward incoming messages to threads'), + location: sdk.z + .enum(['channel', 'thread', 'channelAndThread']) + .default('channel') + .title('Reply Location') + .describe('Where the bot sends replies: Channel only, Thread only (creates if needed), or both'), onlyOnBotMention: sdk.z .boolean() .default(false) - .title('Require Bot Mention for Reply Threading') - .describe('When enabled, the bot will only forward messages to threads when mentioned'), + .title('Require Bot Mention for Replies') + .describe('This ensures that the bot only replies to messages when it is explicitly mentioned'), }) - .optional() - .title('Proactive Threads') - .describe('Create reply threads for each incoming message'), + .title('Reply Behaviour') + .describe('How the bot should reply to messages'), } as const export const configuration = { diff --git a/integrations/slack/hub.md b/integrations/slack/hub.md index c77ffaa33dc..61d0177d6ab 100644 --- a/integrations/slack/hub.md +++ b/integrations/slack/hub.md @@ -1,5 +1,14 @@ The Slack integration enables seamless communication between your AI-powered chatbot and Slack, the popular collaboration platform. Connect your chatbot to Slack and streamline team communication, automate tasks, and enhance productivity. With this integration, your chatbot can send and receive messages, share updates, handle inquiries, and perform actions directly within Slack channels. Leverage Slack's extensive features such as chat, file sharing, notifications, and app integrations to create a powerful conversational AI experience. Enhance team collaboration and streamline workflows with the Slack Integration for Botpress. +## Migrating from version `3.x` to `4.x` + +Version 4.0 of the Slack integration refines the bot's reply behaviour by introducing the possibility to reply in either `channel`, `thread` or `channel and thread`. This replaces the previous `createReplyThread` configuration option by adding the ability to **only** reply in threads. + +Features that have been added are: + +- Improved reply behaviour +- Added rich text! Users are now able to input markdown text and it display in rich text in slack + ## Migrating from version `2.x` to `3.x` Version 3.0 of the Slack integration changes the way the mention system works with Botpress. @@ -133,13 +142,7 @@ Regardless of the configuration mode you choose, you can optionally set a custom ## Replying in threads instead of the main channel -To minimize disruption in busy Slack channels, you can activate reply threading in the integration settings. This feature creates a thread for each incoming message, where the bot will respond. For a more targeted approach, enable the "Require Bot Mention for Reply Threading Option" to only create threads when the bot is mentioned by name. - -Note that enabling reply threading alone doesn't stop your bot from posting in the main channel. To restrict responses exclusively to threads, modify your workflow in the Botpress Studio to terminate when receiving messages from the main channel: - -1. Insert an empty Standard Node at the very beginning of your Main workflow and connect it to your existing flow. -2. Add an Expression card with the condition `event.channel === 'channel'`. -3. Create an End card and connect the Expression card to it. +To minimize disruption in busy Slack channels, you can activate reply threading in the integration settings. This feature creates a thread for each incoming message, where the bot will respond. For a more targeted approach, enable the "Require Bot Mention for Replies" to only create threads when the bot is mentioned by name. ## Limitations diff --git a/integrations/slack/integration.definition.ts b/integrations/slack/integration.definition.ts index a0d07df3719..041e40fefe9 100644 --- a/integrations/slack/integration.definition.ts +++ b/integrations/slack/integration.definition.ts @@ -17,7 +17,7 @@ export default new IntegrationDefinition({ name: 'slack', title: 'Slack', description: 'Automate interactions with your team.', - version: '3.2.0', + version: '4.0.0', icon: 'icon.svg', readme: 'hub.md', configuration, diff --git a/integrations/slack/src/misc/markdown-to-slack.ts b/integrations/slack/src/misc/markdown-to-slack.ts index 467e8e9d850..9568319e9e1 100644 --- a/integrations/slack/src/misc/markdown-to-slack.ts +++ b/integrations/slack/src/misc/markdown-to-slack.ts @@ -47,10 +47,6 @@ const slackHandlers: MarkdownHandlers = { paragraph: (node, visit) => `${visit(node)}\n`, } -export function transformMarkdownForSlack(text: string | undefined): string | undefined { - if (!text) { - return text - } - +export function transformMarkdownForSlack(text: string): string { return transformMarkdown(text, slackHandlers) } diff --git a/integrations/slack/src/misc/replace-mentions.test.ts b/integrations/slack/src/misc/replace-mentions.test.ts index d0341872e5b..dc19c9e8538 100644 --- a/integrations/slack/src/misc/replace-mentions.test.ts +++ b/integrations/slack/src/misc/replace-mentions.test.ts @@ -1,10 +1,6 @@ import { test, expect, vi } from 'vitest' import { replaceMentions, Mention } from './replace-mentions' -test('returns undefined if text is undefined', () => { - expect(replaceMentions(undefined, [])).toBeUndefined() -}) - test('returns text unchanged if mentions is undefined', () => { expect(replaceMentions('hey <@John Doe>', undefined)).toBe('hey <@John Doe>') }) diff --git a/integrations/slack/src/misc/replace-mentions.ts b/integrations/slack/src/misc/replace-mentions.ts index ec256c9b43f..b1c97392be2 100644 --- a/integrations/slack/src/misc/replace-mentions.ts +++ b/integrations/slack/src/misc/replace-mentions.ts @@ -4,8 +4,8 @@ export type Mention = { user: { id: string; name: string } } -export const replaceMentions = (text: string | undefined, mentions: Mention[] | undefined): string | undefined => { - if (!mentions || !text) { +export const replaceMentions = (text: string, mentions: Mention[] | undefined): string => { + if (!mentions) { return text } diff --git a/integrations/slack/src/webhook-events/handlers/message-received.ts b/integrations/slack/src/webhook-events/handlers/message-received.ts index 3d2e482d9f4..bb2c3f8fdb2 100644 --- a/integrations/slack/src/webhook-events/handlers/message-received.ts +++ b/integrations/slack/src/webhook-events/handlers/message-received.ts @@ -47,62 +47,73 @@ export const handleEvent = async (props: HandleEventProps) => { return } - const { botpressConversation } = await getBotpressConversationFromSlackThread( - { slackChannelId: slackEvent.channel, slackThreadId: slackEvent.thread_ts }, - client - ) const { botpressUser } = await getBotpressUserFromSlackUser({ slackUserId: slackEvent.user }, client) await updateBotpressUserFromSlackUser(slackEvent.user, botpressUser, client, ctx, logger) const mentionsBot = await _isBotMentionedInMessage({ slackEvent, client, ctx }) const isSentInChannel = !slackEvent.thread_ts - const isThreadingEnabled = ctx.configuration.createReplyThread?.enabled ?? false - const threadingRequiresMention = ctx.configuration.createReplyThread?.onlyOnBotMention ?? false - const shouldForkToReplyThread = isSentInChannel && isThreadingEnabled && (!threadingRequiresMention || mentionsBot) - - await _sendMessage({ - botpressConversation, - botpressUser, - tags: { - ts: slackEvent.ts, - userId: slackEvent.user, - channelId: slackEvent.channel, - mentionsBot: mentionsBot ? 'true' : undefined, - forkedToThread: 'false', - }, - discriminateByTags: ['ts', 'channelId'], - slackEvent, - client, - ctx, - logger, - }) + const replyLocation = ctx.configuration.replyBehaviour?.location ?? 'channel' + const replyOnlyOnBotMention = ctx.configuration.replyBehaviour?.onlyOnBotMention ?? false - if (!shouldForkToReplyThread) { + if (replyOnlyOnBotMention && !mentionsBot) { + logger.forBot().warn('Message was not sent because the bot was not mentioned') return } - const { conversation: threadConversation } = await client.getOrCreateConversation({ - channel: 'thread', - tags: { id: slackEvent.channel, thread: slackEvent.ts, isBotReplyThread: 'true' }, - discriminateByTags: ['id', 'thread'], - }) + const shouldRespondInChannel = + isSentInChannel && (replyLocation === 'channel' || replyLocation === 'channelAndThread') + const shouldRespondInThread = !isSentInChannel || replyLocation === 'thread' || replyLocation === 'channelAndThread' - await _sendMessage({ - botpressConversation: threadConversation, - botpressUser, - tags: { - ts: slackEvent.ts, - userId: slackEvent.user, - channelId: slackEvent.channel, - mentionsBot: mentionsBot ? 'true' : undefined, - forkedToThread: 'true', - }, - discriminateByTags: ['ts', 'channelId', 'forkedToThread'], - slackEvent, - client, - ctx, - logger, - }) + if (shouldRespondInChannel) { + const { botpressConversation } = await getBotpressConversationFromSlackThread( + { slackChannelId: slackEvent.channel, slackThreadId: undefined }, + client + ) + + await _sendMessage({ + botpressConversation, + botpressUser, + tags: { + ts: slackEvent.ts, + userId: slackEvent.user, + channelId: slackEvent.channel, + mentionsBot: mentionsBot ? 'true' : undefined, + forkedToThread: 'false', + }, + discriminateByTags: ['ts', 'channelId'], + slackEvent, + client, + ctx, + logger, + }) + } + + if (shouldRespondInThread) { + const threadTs = slackEvent.thread_ts ?? slackEvent.ts + + const { conversation: threadConversation } = await client.getOrCreateConversation({ + channel: 'thread', + tags: { id: slackEvent.channel, thread: threadTs, isBotReplyThread: 'true' }, + discriminateByTags: ['id', 'thread'], + }) + + await _sendMessage({ + botpressConversation: threadConversation, + botpressUser, + tags: { + ts: slackEvent.ts, + userId: slackEvent.user, + channelId: slackEvent.channel, + mentionsBot: mentionsBot ? 'true' : undefined, + forkedToThread: isSentInChannel ? 'true' : 'false', + }, + discriminateByTags: ['ts', 'channelId', 'forkedToThread'], + slackEvent, + client, + ctx, + logger, + }) + } } type _SendMessageProps = HandleEventProps & { diff --git a/integrations/trello/.sentryclirc b/integrations/trello/.sentryclirc deleted file mode 100644 index cb58b269569..00000000000 --- a/integrations/trello/.sentryclirc +++ /dev/null @@ -1,5 +0,0 @@ -[defaults] -project=integration-trello - -[auth] -dsn=https://3eff74f9ec846c8b60bca9e10cef6a02@o363631.ingest.sentry.io/4505750271688704 diff --git a/integrations/trello/definitions/actions/card-actions.ts b/integrations/trello/definitions/actions/card-actions.ts index af9f511c795..35ee7c84e8d 100644 --- a/integrations/trello/definitions/actions/card-actions.ts +++ b/integrations/trello/definitions/actions/card-actions.ts @@ -47,21 +47,28 @@ export const createCard = { .object({ listId: listSchema.shape.id.title('List ID').describe('ID of the list in which to insert the new card'), cardName: cardSchema.shape.name.title('Card Name').describe('Name of the new card'), - cardBody: cardSchema.shape.description.optional().title('Card Body').describe('Body text of the new card'), - members: z + cardBody: cardSchema.shape.description.optional().title('Card Body').describe('The body text of the new card'), + memberIds: z .array(trelloIdSchema) .optional() - .title('Members') + .title('Member IDs') .describe('Members to add to the card (Optional). This should be a list of member IDs.'), - labels: z + labelIds: z .array(trelloIdSchema) .optional() - .title('Labels') + .title('Label IDs') .describe('Labels to add to the card (Optional). This should be a list of label IDs.'), dueDate: cardSchema.shape.dueDate .optional() .title('Due Date') .describe('The due date of the card in ISO 8601 format (Optional).'), + completionStatus: z + .enum(['Complete', 'Incomplete']) + .default('Incomplete') + .title('Completion Status') + .describe( + 'Whether the card should be marked as complete (Optional). Enter "Complete" or "Incomplete" (without quotes).' + ), }) .describe('Input schema for creating a new card'), }, @@ -80,55 +87,53 @@ export const updateCard = { input: { schema: hasCardId .extend({ - name: cardSchema.shape.name + cardName: cardSchema.shape.name .optional() - .title('Name') + .title('Card Name') .describe('The name of the card (Optional) (e.g. "My Test Card"). Leave empty to keep the current name.'), - bodyText: cardSchema.shape.description + cardBody: cardSchema.shape.description .optional() - .title('Body Text') - .describe('Body text of the new card (Optional). Leave empty to keep the current body.'), - closedState: z - .enum(['open', 'archived']) + .title('Card Body') + .describe('The new body text of the card (Optional). Leave empty to keep the current body.'), + lifecycleStatus: z + .enum(['Open', 'Archived']) .optional() - .title('Closed State') + .title('Lifecycle Status') .describe( - 'Whether the card should be archived (Optional). Enter "open", "archived" (without quotes), or leave empty to keep the previous status.' - ) - .optional(), - completeState: z - .enum(['complete', 'incomplete']) + 'Whether the card should be archived (Optional). Enter "Open", "Archived" (without quotes), or leave empty to keep the previous status.' + ), + completionStatus: z + .enum(['Complete', 'Incomplete']) .optional() - .title('State Completion') + .title('Completion Status') .describe( - 'Whether the card should be marked as complete (Optional). Enter "complete", "incomplete" (without quotes), or leave empty to keep the previous status.' - ) - .optional(), - membersToAdd: z + 'Whether the card should be marked as complete (Optional). Enter "Complete", "Incomplete" (without quotes), or leave empty to keep the previous status.' + ), + memberIdsToAdd: z .array(trelloIdSchema) .optional() - .title('Members to Add') + .title('Member IDs to Add') .describe( 'Members to add to the card (Optional). This should be a list of member IDs. Leave empty to keep the current members.' ), - membersToRemove: z + memberIdsToRemove: z .array(trelloIdSchema) .optional() - .title('Members to Remove') + .title('Member IDs to Remove') .describe( 'Members to remove from the card (Optional). This should be a list of member IDs. Leave empty to keep the current members.' ), - labelsToAdd: z + labelIdsToAdd: z .array(trelloIdSchema) .optional() - .title('Labels to Add') + .title('Label IDs to Add') .describe( 'Labels to add to the card (Optional). This should be a list of label IDs. Leave empty to keep the current labels.' ), - labelsToRemove: z + labelIdsToRemove: z .array(trelloIdSchema) .optional() - .title('Labels to Remove') + .title('Label IDs to Remove') .describe( 'Labels to remove from the card (Optional). This should be a list of label IDs. Leave empty to keep the current labels.' ), @@ -138,6 +143,21 @@ export const updateCard = { .describe( 'The due date of the card in ISO 8601 format (Optional). Leave empty to keep the current due date.' ), + listId: listSchema.shape.id + .optional() + .title('List ID') + .describe('Unique identifier of the list in which the card will be moved to'), + /** Note: The validation for "verticalPosition" must be done in the action + * implementation, since studio does not support union types in inputs yet + * and the JSON schema generation does not support zod runtime validation + * like "refine" (at the time of writing, 2026-01-13). */ + verticalPosition: z + .string() + .optional() + .title('Vertical Position') + .describe( + 'The new position of the card in the list, either "top", "bottom", or a float (Optional). Leave empty to keep the current position.' + ), }) .describe('Input schema for creating a new card'), }, @@ -146,6 +166,26 @@ export const updateCard = { }, } as const satisfies ActionDefinition +export const deleteCard = { + title: 'Delete card', + description: 'Deletes a card by its unique identifier', + input: { + schema: z.object({ + cardId: cardSchema.shape.id.title('Card ID').describe('ID of the card to delete'), + hardDelete: z + .boolean() + .default(false) + .title('Hard Delete') + .describe( + 'Whether to perform a hard delete or a soft delete (archive). Set to true for hard delete, false for soft delete.' + ), + }), + }, + output: { + schema: z.object({}), + }, +} as const satisfies ActionDefinition + export const addCardComment = { title: 'Add card comment', description: 'Add a new comment to a card', @@ -207,7 +247,7 @@ export const moveCardToList = { schema: hasCardId.extend({ newListId: listSchema.shape.id .title('New List ID') - .describe('Unique identifier of the list in which the card will be moved'), + .describe('Unique identifier of the list in which the card will be moved to'), }), }, output: { diff --git a/integrations/trello/definitions/actions/index.ts b/integrations/trello/definitions/actions/index.ts index 7b1b02e4707..e036b10f684 100644 --- a/integrations/trello/definitions/actions/index.ts +++ b/integrations/trello/definitions/actions/index.ts @@ -3,6 +3,7 @@ import { getAllBoards, getBoardById, getBoardsByDisplayName } from './board-acti import { addCardComment, createCard, + deleteCard, getCardById, getCardsByDisplayName, getCardsInList, @@ -34,6 +35,7 @@ export const actions = { getCardsInList, createCard, updateCard, + deleteCard, addCardComment, moveCardUp, moveCardDown, diff --git a/integrations/trello/definitions/events/card-attachment-events.ts b/integrations/trello/definitions/events/card-attachment-events.ts new file mode 100644 index 00000000000..3850c129cb0 --- /dev/null +++ b/integrations/trello/definitions/events/card-attachment-events.ts @@ -0,0 +1,36 @@ +import { z } from '@botpress/sdk' +import { boardSchema, cardSchema, listSchema, trelloIdSchema } from '../schemas' +import { botpressEventDataSchema, pickIdAndName } from './common' + +export const attachmentAddedToCardEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + list: pickIdAndName(listSchema).title('List').describe('List where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + attachment: z + .object({ + id: trelloIdSchema.title('Attachment ID').describe('Unique identifier of the attachment'), + name: z.string().title('Attachment Name').describe('Name of the attachment'), + url: z.string().url().title('Attachment URL').describe('URL of the attachment'), + previewUrl: z.string().url().optional().title('Attachment Preview URL').describe('URL of the attachment preview'), + previewUrl2x: z + .string() + .url() + .optional() + .title('Attachment Preview URL 2x') + .describe('URL of the attachment preview at up to 2x the resolution'), + }) + .title('Attachment') + .describe('Attachment that was added to the card'), +}) + +export const attachmentRemovedFromCardEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + attachment: z + .object({ + id: trelloIdSchema.title('Attachment ID').describe('Unique identifier of the attachment'), + name: z.string().title('Attachment Name').describe('Name of the attachment'), + }) + .title('Attachment') + .describe('Attachment that was deleted from the card'), +}) diff --git a/integrations/trello/definitions/events/card-comment-events.ts b/integrations/trello/definitions/events/card-comment-events.ts new file mode 100644 index 00000000000..e700c6c2ef6 --- /dev/null +++ b/integrations/trello/definitions/events/card-comment-events.ts @@ -0,0 +1,31 @@ +import { z } from '@botpress/sdk' +import { boardSchema, cardSchema, listSchema, trelloIdSchema } from '../schemas' +import { botpressEventDataSchema, pickIdAndName } from './common' + +const _cardCommentSchema = z.object({ + id: trelloIdSchema.title('Comment ID').describe('Unique identifier of the comment'), + text: z.string().title('Comment Text').describe('Text of the comment'), +}) + +export const cardCommentCreatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + list: pickIdAndName(listSchema).title('List').describe('List where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + comment: _cardCommentSchema.title('New Comment').describe('Comment that was added to the card'), +}) + +export const cardCommentUpdatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + comment: _cardCommentSchema.title('Updated Comment').describe('Comment that was updated'), + old: _cardCommentSchema.omit({ id: true }).title('Old Comment').describe('The previous data of the comment'), +}) + +export const cardCommentDeletedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + comment: _cardCommentSchema + .pick({ id: true }) + .title('Deleted Comment') + .describe('Comment that was deleted from the card'), +}) diff --git a/integrations/trello/definitions/events/card-events.ts b/integrations/trello/definitions/events/card-events.ts new file mode 100644 index 00000000000..817a5ac590b --- /dev/null +++ b/integrations/trello/definitions/events/card-events.ts @@ -0,0 +1,56 @@ +import { z } from '@botpress/sdk' +import { boardSchema, cardSchema, listSchema, trelloIdSchema } from '../schemas' +import { botpressEventDataSchema, dueReminderSchema, pickIdAndName } from './common' + +export const cardCreatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was created'), + list: pickIdAndName(listSchema).title('List').describe('List where the card was created'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was created'), +}) + +const _baseCardUpdateDataSchema = z + .object({ + id: trelloIdSchema.title('Card ID').describe('Unique identifier of the card'), + name: z.string().title('Card Name').describe('Name of the card'), + description: z.string().title('Card Description').describe('Description of the card'), + listId: trelloIdSchema.title('List ID').describe('Unique identifier of the list where the card is located'), + labelIds: z.array(trelloIdSchema).title('Label IDs').describe('Labels attached to the card'), + verticalPosition: z.number().title('Card Position').describe('Position of the card within the list'), + startDate: z.string().datetime().nullable().title('Start Date').describe('Start date of the card'), + dueDate: z.string().datetime().nullable().title('Due Date').describe('Due date of the card'), + dueDateReminder: dueReminderSchema, + isCompleted: z.boolean().title('Is Completed').describe('Whether the card is completed'), + isArchived: z.boolean().title('Is Archived').describe('Whether the card is archived'), + }) + .passthrough() + .partial() + +export const cardUpdatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: _baseCardUpdateDataSchema.required({ id: true, name: true }).title('Card').describe('Card that was updated'), + old: _baseCardUpdateDataSchema.omit({ id: true }).title('Old').describe('Previous state of the card'), + // Only excluded/optional when the card is moved between lists + list: pickIdAndName(listSchema).optional().title('List').describe('List where the card was updated'), + // Only included if the card was moved between lists + listBefore: pickIdAndName(listSchema) + .optional() + .title('List Before') + .describe('Previous list where the card was located'), + // Only included if the card was moved between lists + listAfter: pickIdAndName(listSchema) + .optional() + .title('List After') + .describe('New list where the card is now located'), +}) + +export const cardDeletedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was deleted'), + list: pickIdAndName(listSchema).title('List').describe('List where the card was deleted'), + card: cardSchema.pick({ id: true }).title('Card').describe('Card that was deleted'), +}) + +export const cardVotesUpdatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + voted: z.boolean().title('Has Voted').describe('Whether the user voted on the card'), +}) diff --git a/integrations/trello/definitions/events/card-label-events.ts b/integrations/trello/definitions/events/card-label-events.ts new file mode 100644 index 00000000000..d97f2865242 --- /dev/null +++ b/integrations/trello/definitions/events/card-label-events.ts @@ -0,0 +1,21 @@ +import { z } from '@botpress/sdk' +import { boardSchema, cardSchema, trelloIdSchema } from '../schemas' +import { botpressEventDataSchema, pickIdAndName } from './common' + +export const labelSchema = z.object({ + id: trelloIdSchema.title('Label ID').describe('Unique identifier of the label'), + name: z.string().title('Label Name').describe('Name of the label'), + color: z.string().title('Label Color').describe('Color of the label'), +}) + +export const labelAddedToCardEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was modified'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was modified'), + label: labelSchema.title('Label').describe('Label that was added to the card'), +}) + +export const labelRemovedFromCardEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was modified'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was modified'), + label: labelSchema.title('Label').describe('Label that was removed from the card'), +}) diff --git a/integrations/trello/definitions/events/checklist-events.ts b/integrations/trello/definitions/events/checklist-events.ts new file mode 100644 index 00000000000..40f9c55ceeb --- /dev/null +++ b/integrations/trello/definitions/events/checklist-events.ts @@ -0,0 +1,64 @@ +import { z } from '@botpress/sdk' +import { boardSchema, cardSchema, trelloIdSchema } from '../schemas' +import { botpressEventDataSchema, dueReminderSchema, pickIdAndName } from './common' + +export const checklistSchema = z.object({ + id: trelloIdSchema.title('Checklist ID').describe('Unique identifier of the checklist'), + name: z.string().title('Checklist Name').describe('Name of the checklist'), +}) + +const _basicChecklistItemSchema = z.object({ + id: trelloIdSchema.title('Checklist Item ID').describe('Unique identifier of the checklist item'), + name: z.string().title('Checklist Item Name').describe('Name of the checklist item'), + isCompleted: z.boolean().title('Is Completed').describe('Indicates if the checklist item is marked as completed'), + textData: z + .object({ + emoji: z.object({}).title('Checklist Item Emoji').describe('Emoji of the checklist item'), + }) + .title('Text data') + .describe('Text data of the checklist item'), +}) + +export const checklistAddedToCardEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + checklist: checklistSchema.title('Checklist').describe('Checklist that was added to the card'), +}) + +export const checklistItemCreatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + checklist: checklistSchema.title('Checklist').describe('Checklist where the item was added'), + checklistItem: _basicChecklistItemSchema.title('Checklist Item').describe('The checklist item that was added'), +}) + +const _baseChecklistItemUpdateDataSchema = _basicChecklistItemSchema.extend({ + dueDateReminder: dueReminderSchema.optional(), + dueDate: z.string().datetime().nullable().optional().title('Due Date').describe('Due date of the checklist item'), +}) + +export const checklistItemUpdatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + checklist: checklistSchema.title('Checklist').describe('Checklist where the item was updated'), + checklistItem: _baseChecklistItemUpdateDataSchema.title('Checklist Item').describe('Checklist item that was updated'), + old: _baseChecklistItemUpdateDataSchema + .omit({ id: true }) + .partial() + .title('Old Checklist Item') + .describe('The previous data of the checklist item'), +}) + +export const checklistItemStatusUpdatedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + checklist: checklistSchema.title('Checklist').describe('Checklist where the item was updated'), + checklistItem: _basicChecklistItemSchema.title('Checklist Item').describe('Checklist item that was updated'), +}) + +export const checklistItemDeletedEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + checklist: checklistSchema.title('Checklist').describe('Checklist where the item was removed'), + checklistItem: _basicChecklistItemSchema.title('Checklist Item').describe('Checklist item that was removed'), +}) diff --git a/integrations/trello/definitions/events/common.ts b/integrations/trello/definitions/events/common.ts new file mode 100644 index 00000000000..1cbfe56d551 --- /dev/null +++ b/integrations/trello/definitions/events/common.ts @@ -0,0 +1,69 @@ +import { z } from '@botpress/sdk' +import { trelloIdSchema } from '../schemas' + +export enum TrelloEventType { + // ---- Card Events ---- + CARD_CREATED = 'createCard', + CARD_UPDATED = 'updateCard', + CARD_DELETED = 'deleteCard', + CARD_VOTES_UPDATED = 'voteOnCard', + // ---- Card Comment Events ---- + CARD_COMMENT_CREATED = 'commentCard', + CARD_COMMENT_UPDATED = 'updateComment', + CARD_COMMENT_DELETED = 'deleteComment', + // ---- Card Label Events ---- + LABEL_ADDED_TO_CARD = 'addLabelToCard', + LABEL_REMOVED_FROM_CARD = 'removeLabelFromCard', + // ---- Card Attachment Events ---- + ATTACHMENT_ADDED_TO_CARD = 'addAttachmentToCard', + ATTACHMENT_REMOVED_FROM_CARD = 'deleteAttachmentFromCard', + // ---- Checklist Events ---- + CHECKLIST_ADDED_TO_CARD = 'addChecklistToCard', + CHECKLIST_ITEM_CREATED = 'createCheckItem', + CHECKLIST_ITEM_UPDATED = 'updateCheckItem', + CHECKLIST_ITEM_DELETED = 'deleteCheckItem', + CHECKLIST_ITEM_STATUS_UPDATED = 'updateCheckItemStateOnCard', + // ---- Member Events ---- + MEMBER_ADDED_TO_CARD = 'addMemberToCard', + MEMBER_REMOVED_FROM_CARD = 'removeMemberFromCard', +} + +type IdAndNameSchema = z.ZodObject<{ id: z.ZodString; name: z.ZodString }> +export const pickIdAndName = (schema: T) => schema.pick({ id: true, name: true }) + +/** The number of minutes before the due date when a reminder will be sent. + * + * @remark When the value is "-1", it means no due date reminder is set. */ +export const dueReminderSchema = z + .number() + .int('Due date reminder is not an integer') + .min(-1) + .title('Due Date Reminder') + .describe('The number of minutes before the due date when a reminder will be sent') + +export const botpressEventDataSchema = z.object({ + eventId: trelloIdSchema.title('Event ID').describe('Unique identifier of the event'), + actor: z + .union([ + z.object({ + id: trelloIdSchema.title('Actor ID').describe('Unique identifier of the actor who triggered the event'), + type: z + .literal('member') + .title('Actor Type') + .describe('The type of the actor (e.g. member or app) who triggered the event'), + name: z.string().title('Actor Name').describe('The name of the actor (e.g. member) who triggered the event'), + }), + z.object({ + id: trelloIdSchema.title('Actor ID').describe('Unique identifier of the actor who triggered the event'), + type: z + .literal('app') + .title('Actor Type') + .describe('The type of the actor (e.g. member or app) who triggered the event'), + }), + ]) + + .title('Actor') + .describe('The actor (e.g. member or app) who triggered the event'), + dateCreated: z.string().datetime().title('Date Created').describe('The datetime when the event was triggered'), +}) +export type CommonEventData = z.infer diff --git a/integrations/trello/definitions/events/index.ts b/integrations/trello/definitions/events/index.ts index dc3984211f5..aa7be027c0d 100644 --- a/integrations/trello/definitions/events/index.ts +++ b/integrations/trello/definitions/events/index.ts @@ -1,122 +1,136 @@ import { type IntegrationDefinitionProps } from '@botpress/sdk' +import { attachmentAddedToCardEventSchema, attachmentRemovedFromCardEventSchema } from './card-attachment-events' import { - TRELLO_EVENTS, - addMemberToCardEventSchema, - commentCardEventSchema, - createCardEventSchema, - deleteCardEventSchema, - removeMemberFromCardEventSchema, - updateCardEventSchema, - updateCheckItemStateOnCardEventSchema, - addLabelToCardEventSchema, - createCheckItemEventSchema, - deleteCheckItemEventSchema, - deleteCommentEventSchema, - removeLabelFromCardEventSchema, - updateCheckItemEventSchema, - updateCommentEventSchema, - voteOnCardEventSchema, - addAttachmentToCardEventSchema, - deleteAttachmentFromCardEventSchema, - CommentCardEvent, - AllSupportedEvents, - GenericWebhookEvent, - genericWebhookEventSchema, -} from './webhookEvents' + cardCommentCreatedEventSchema, + cardCommentDeletedEventSchema, + cardCommentUpdatedEventSchema, +} from './card-comment-events' +import { + cardCreatedEventSchema, + cardDeletedEventSchema, + cardUpdatedEventSchema, + cardVotesUpdatedEventSchema, +} from './card-events' +import { labelAddedToCardEventSchema, labelRemovedFromCardEventSchema } from './card-label-events' +import { + checklistAddedToCardEventSchema, + checklistItemCreatedEventSchema, + checklistItemDeletedEventSchema, + checklistItemUpdatedEventSchema, + checklistItemStatusUpdatedEventSchema, +} from './checklist-events' +import { CommonEventData, TrelloEventType } from './common' +import { memberAddedToCardEventSchema, memberRemovedFromCardEventSchema } from './member-events' export const events = { - [TRELLO_EVENTS.addMemberToCard]: { - title: 'Member added to card', - description: 'Triggered when a member is added to a card', - schema: addMemberToCardEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.commentCard]: { - title: 'Comment added to card', - description: 'Triggered when a comment is added to a card', - schema: commentCardEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.createCard]: { - title: 'Card created', + // =============================== + // Card Events + // =============================== + [TrelloEventType.CARD_CREATED]: { + title: 'Card Created', description: 'Triggered when a card is created', - schema: createCardEventSchema.shape.action.shape.data, + schema: cardCreatedEventSchema, }, - [TRELLO_EVENTS.deleteCard]: { - title: 'Card deleted', - description: 'Triggered when a card is deleted', - schema: deleteCardEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.removeMemberFromCard]: { - title: 'Member removed from card', - description: 'Triggered when a member is removed from a card', - schema: removeMemberFromCardEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.updateCard]: { - title: 'Card updated', + [TrelloEventType.CARD_UPDATED]: { + title: 'Card Updated', description: 'Triggered when a card is updated', - schema: updateCardEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.updateCheckItemStateOnCard]: { - title: 'Check item state updated on card', - description: 'Triggered when the state of a check item is updated in a checklist of a card', - schema: updateCheckItemStateOnCardEventSchema.shape.action.shape.data, + schema: cardUpdatedEventSchema, }, - [TRELLO_EVENTS.addLabelToCard]: { - title: 'Label added to card', + [TrelloEventType.CARD_DELETED]: { + title: 'Card Deleted', + description: 'Triggered when a card is deleted', + schema: cardDeletedEventSchema, + }, + [TrelloEventType.CARD_VOTES_UPDATED]: { + title: 'Card Votes Updated', + description: 'Triggered when a vote is added to or removed from a card', + schema: cardVotesUpdatedEventSchema, + }, + // =============================== + // Card Comment Events + // =============================== + [TrelloEventType.CARD_COMMENT_CREATED]: { + title: 'Comment Created', + description: 'Triggered when a comment is added to a card', + schema: cardCommentCreatedEventSchema, + }, + [TrelloEventType.CARD_COMMENT_UPDATED]: { + title: 'Comment Updated', + description: 'Triggered when a comment is updated on a card', + schema: cardCommentUpdatedEventSchema, + }, + [TrelloEventType.CARD_COMMENT_DELETED]: { + title: 'Comment Deleted', + description: 'Triggered when a comment is deleted from a card', + schema: cardCommentDeletedEventSchema, + }, + // =============================== + // Card Label Events + // =============================== + [TrelloEventType.LABEL_ADDED_TO_CARD]: { + title: 'Card Label Added', description: 'Triggered when a label is added to a card', - schema: addLabelToCardEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.createCheckItem]: { - title: 'Check item created', - description: 'Triggered when a check item is added to a checklist of a card', - schema: createCheckItemEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.deleteCheckItem]: { - title: 'Check item deleted', - description: 'Triggered when a check item is removed from a checklist of a card', - schema: deleteCheckItemEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.deleteComment]: { - title: 'Comment deleted', - description: 'Triggered when a comment is deleted', - schema: deleteCommentEventSchema.shape.action.shape.data, + schema: labelAddedToCardEventSchema, }, - [TRELLO_EVENTS.removeLabelFromCard]: { - title: 'Label removed from card', + [TrelloEventType.LABEL_REMOVED_FROM_CARD]: { + title: 'Card Label Removed', description: 'Triggered when a label is removed from a card', - schema: removeLabelFromCardEventSchema.shape.action.shape.data, + schema: labelRemovedFromCardEventSchema, }, - [TRELLO_EVENTS.updateCheckItem]: { - title: 'Check item updated', - description: 'Triggered when a check item is modified in a checklist of a card', - schema: updateCheckItemEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.updateComment]: { - title: 'Comment updated', - description: 'Triggered when a comment is updated', - schema: updateCommentEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.voteOnCard]: { - title: 'Vote on card', - description: 'Triggered when a vote is added to a card', - schema: voteOnCardEventSchema.shape.action.shape.data, - }, - [TRELLO_EVENTS.addAttachmentToCard]: { - title: 'Attachment added to card', + // ================================ + // Card Attachment Events + // ================================ + [TrelloEventType.ATTACHMENT_ADDED_TO_CARD]: { + title: 'Card Attachment Added', description: 'Triggered when an attachment is added to a card', - schema: addAttachmentToCardEventSchema.shape.action.shape.data, + schema: attachmentAddedToCardEventSchema, + }, + [TrelloEventType.ATTACHMENT_REMOVED_FROM_CARD]: { + title: 'Card Attachment Removed', + description: 'Triggered when an attachment is removed from a card', + schema: attachmentRemovedFromCardEventSchema, + }, + // ================================ + // Checklist Events + // ================================ + [TrelloEventType.CHECKLIST_ADDED_TO_CARD]: { + title: 'Checklist Added To Card', + description: 'Triggered when a checklist is added to a card', + schema: checklistAddedToCardEventSchema, + }, + [TrelloEventType.CHECKLIST_ITEM_CREATED]: { + title: 'Checklist Item Created', + description: 'Triggered when a checklist item is added to a card', + schema: checklistItemCreatedEventSchema, + }, + [TrelloEventType.CHECKLIST_ITEM_UPDATED]: { + title: 'Checklist Item Updated', + description: 'Triggered when a checklist item is modified on a card', + schema: checklistItemUpdatedEventSchema, + }, + [TrelloEventType.CHECKLIST_ITEM_DELETED]: { + title: 'Checklist Item Deleted', + description: 'Triggered when a checklist item is removed from a card', + schema: checklistItemDeletedEventSchema, + }, + [TrelloEventType.CHECKLIST_ITEM_STATUS_UPDATED]: { + title: 'Checklist Item Completion Updated', + description: 'Triggered when the completion status of a checklist item is updated', + schema: checklistItemStatusUpdatedEventSchema, + }, + // =============================== + // Member Events + // =============================== + [TrelloEventType.MEMBER_ADDED_TO_CARD]: { + title: 'Member Added To Card', + description: 'Triggered when a member is added to a card', + schema: memberAddedToCardEventSchema, }, - [TRELLO_EVENTS.deleteAttachmentFromCard]: { - title: 'Attachment deleted from card', - description: 'Triggered when an attachment is deleted from a card', - schema: deleteAttachmentFromCardEventSchema.shape.action.shape.data, + [TrelloEventType.MEMBER_REMOVED_FROM_CARD]: { + title: 'Member Removed From Card', + description: 'Triggered when a member is removed from a card', + schema: memberRemovedFromCardEventSchema, }, } as const satisfies NonNullable -export { - TRELLO_EVENTS, - type AllSupportedEvents, - type CommentCardEvent, - commentCardEventSchema, - type GenericWebhookEvent, - genericWebhookEventSchema, -} +export { TrelloEventType, type CommonEventData } diff --git a/integrations/trello/definitions/events/member-events.ts b/integrations/trello/definitions/events/member-events.ts new file mode 100644 index 00000000000..ce415adde50 --- /dev/null +++ b/integrations/trello/definitions/events/member-events.ts @@ -0,0 +1,28 @@ +import { z } from '@botpress/sdk' +import { boardSchema, cardSchema, trelloIdSchema } from '../schemas' +import { botpressEventDataSchema, pickIdAndName } from './common' + +export const eventMemberSchema = z.object({ + id: trelloIdSchema.title('Member ID').describe('Unique identifier of the member'), + name: z.string().title('Member Name').describe('Full name of the member'), +}) + +export const memberAddedToCardEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that the member was added to'), + member: eventMemberSchema.title('Member').describe('Member that was added to the card'), +}) + +export const memberRemovedFromCardEventSchema = botpressEventDataSchema.extend({ + board: pickIdAndName(boardSchema).title('Board').describe('Board where the card was updated'), + card: pickIdAndName(cardSchema).title('Card').describe('Card that was updated'), + member: eventMemberSchema + .extend({ + deactivated: z + .boolean() + .title('Deactivated') + .describe('Indicates if the member was deactivated at the time of removal'), + }) + .title('Member') + .describe('Member that was removed from the card'), +}) diff --git a/integrations/trello/definitions/events/webhookEvents.ts b/integrations/trello/definitions/events/webhookEvents.ts deleted file mode 100644 index 7dfe2d35aee..00000000000 --- a/integrations/trello/definitions/events/webhookEvents.ts +++ /dev/null @@ -1,855 +0,0 @@ -import { z } from '@botpress/sdk' -import { boardSchema, cardSchema, listSchema, memberSchema, trelloIdSchema } from '../schemas' - -export const TRELLO_EVENTS = { - addMemberToCard: 'addMemberToCard', - commentCard: 'commentCard', - createCard: 'createCard', - deleteCard: 'deleteCard', - removeMemberFromCard: 'removeMemberFromCard', - updateCard: 'updateCard', - updateCheckItemStateOnCard: 'updateCheckItemStateOnCard', - addLabelToCard: 'addLabelToCard', - createCheckItem: 'createCheckItem', - deleteCheckItem: 'deleteCheckItem', - deleteComment: 'deleteComment', - removeLabelFromCard: 'removeLabelFromCard', - updateCheckItem: 'updateCheckItem', - updateComment: 'updateComment', - voteOnCard: 'voteOnCard', - addAttachmentToCard: 'addAttachmentToCard', - deleteAttachmentFromCard: 'deleteAttachmentFromCard', -} as const - -export const genericWebhookEventSchema = z.object({ - action: z.object({ - id: trelloIdSchema.describe('Unique identifier of the action'), - idMemberCreator: memberSchema.shape.id.describe('Unique identifier of the member who initiated the action'), - type: z - .string() - .refine((e) => Reflect.ownKeys(TRELLO_EVENTS).includes(e)) - .describe('Type of the action'), - date: z.string().datetime().describe('Date of the action'), - data: z.any(), - memberCreator: z - .object({ - id: memberSchema.shape.id.describe('Unique identifier of the member'), - fullName: memberSchema.shape.fullName.describe('Full name of the member'), - username: memberSchema.shape.username.describe('Username of the member'), - initials: z.string().describe('Initials of the member'), - avatarHash: z.string().describe('Avatar hash of the member'), - avatarUrl: z.string().describe('Avatar URL of the member'), - }) - .describe('Member who initiated the action'), - }), - model: z.object({ - id: boardSchema.shape.id.describe('Unique identifier of the model that is being watched'), - }), - webhook: z.object({ - id: trelloIdSchema.describe('Unique identifier of the webhook'), - idModel: boardSchema.shape.id.describe('Unique identifier of the model that is being watched'), - active: z.boolean().describe('Whether the webhook is active'), - consecutiveFailures: z.number().min(0).describe('Number of consecutive failures'), - }), -}) - -export type AllSupportedEvents = keyof typeof TRELLO_EVENTS -export type GenericWebhookEvent = Omit, 'action'> & { - action: Omit, 'type'> & { type: AllSupportedEvents } -} - -export const addAttachmentToCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('addAttachmentToCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: boardSchema.shape.id.describe('Unique identifier of the board'), - name: boardSchema.shape.name.describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: cardSchema.shape.id.describe('Unique identifier of the card'), - name: cardSchema.shape.name.describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - list: z - .object({ - id: listSchema.shape.id.describe('Unique identifier of the list'), - name: listSchema.shape.name.describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was updated'), - attachment: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the attachment'), - name: z.string().describe('Name of the attachment'), - url: z.string().url().optional().describe('URL of the attachment'), - previewUrl: z.string().url().optional().describe('URL of the attachment preview'), - previewUrl2x: z.string().url().optional().describe('URL of the attachment preview in 2x'), - }) - .title('Attachment') - .describe('Attachment that was added to the card'), - }), - }) - .describe('Action that is triggered when an attachment is added to a card') - ), - }) -) - -export const voteOnCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('voteOnCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .optional() - .title('Card') - .describe('Card that was updated'), - voted: z.boolean().title('Voted').describe('Whether the user voted on the card'), - }), - }) - .describe('Action that is triggered when a user votes on a card') - ), - }) -) - -export const updateCommentEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('updateComment').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - list: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the list'), - name: z.string().describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was updated'), - action: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the comment that was updated'), - text: z.string().describe('New text of the comment'), - }) - .title('Action') - .describe('The action details for the updated comment'), - old: z - .object({ - text: z.string().describe('Old text of the comment'), - }) - .title('Old') - .describe('Old comment data'), - }), - }) - .describe('Action that is triggered when a comment is updated') - ), - }) -) - -export const updateCheckItemStateOnCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('updateCheckItemStateOnCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - checklist: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the checklist'), - name: z.string().describe('Name of the checklist'), - }) - .title('Checklist') - .describe('Checklist where the item was updated'), - checkItem: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the check item'), - name: z.string().describe('Name of the check item'), - state: z.union([z.literal('complete'), z.literal('incomplete')]).describe('State of the check item'), - textData: z.object({ - emoji: z.object({}).describe('Emoji of the check item'), - }), - due: z.string().datetime().optional().describe('Due date of the check item'), - }) - .title('Check Item') - .describe('Check item that was updated'), - }), - }) - .describe('Action that is triggered when an item is updated in a checklist') - ), - }) -) - -export const updateCheckItemEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('updateCheckItem').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - checklist: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the checklist'), - name: z.string().describe('Name of the checklist'), - }) - .title('Checklist') - .describe('Checklist where the item was updated'), - checkItem: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the check item'), - name: z.string().describe('Name of the check item'), - state: z.union([z.literal('complete'), z.literal('incomplete')]).describe('State of the check item'), - due: z.string().datetime().optional().describe('Due date of the check item'), - }) - .title('Check Item') - .describe('Check item that was updated'), - old: z - .object({ - name: z.string().describe('Old name of the check item'), - state: z - .union([z.literal('complete'), z.literal('incomplete')]) - .describe('Old state of the check item'), - due: z.string().datetime().optional().describe('Old due date of the check item'), - }) - .title('Old') - .describe('Old check item data'), - }), - }) - .describe('Action that is triggered when an item is updated in a checklist') - ), - }) -) - -export const updateCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('updateCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - idList: trelloIdSchema.optional().describe('Unique identifier of the list where the card is located'), - desc: z.string().optional().describe('Description of the card'), - idLabels: z.array(trelloIdSchema).optional().describe('Labels attached to the card'), - pos: z.number().optional().describe('Position of the card'), - start: z.union([z.string().datetime(), z.null()]).optional().describe('Start date of the card'), - due: z.union([z.string().datetime(), z.null()]).optional().describe('Due date of the card'), - dueReminder: z - .union([z.literal(-1), z.null(), z.number().min(0)]) - .optional() - .describe('Due reminder of the card'), - dueComplete: z.boolean().optional().describe('Whether the card is completed'), - closed: z.boolean().optional().describe('Whether the card is archived'), - }) - .title('Card') - .describe('Card that was updated'), - old: z - .object({ - name: z.string().describe('Previous name of the card'), - desc: z.string().or(z.null()).optional().describe('Previous description of the card'), - idList: trelloIdSchema.optional().describe('Previous list where the card was'), - idLabels: z.array(trelloIdSchema).optional().describe('Previous labels attached to the card'), - pos: z.number().optional().describe('Previous position of the card'), - start: z - .union([z.string().datetime(), z.null()]) - .optional() - .describe('Previous start date of the card'), - due: z.union([z.string().datetime(), z.null()]).optional().describe('Previous due date of the card'), - dueReminder: z - .union([z.literal(-1), z.null(), z.number().min(0)]) - .optional() - .describe('Previous due reminder of the card'), - dueComplete: z.boolean().optional().describe('Previous completion state of the card'), - closed: z.boolean().optional().describe('Previous archive state of the card'), - }) - .title('Old') - .describe('Previous state of the card'), - list: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the list'), - name: z.string().describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was updated'), - listBefore: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the previous list'), - name: z.string().describe('Name of the previous list'), - }) - .optional() - .title('List Before') - .describe('Previous list where the card was located'), - listAfter: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the new list'), - name: z.string().describe('Name of the new list'), - }) - .optional() - .title('List After') - .describe('New list where the card is now located'), - }), - }) - .describe('Action that is triggered when a card is updated') - ), - }) -) - -export const removeMemberFromCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('removeMemberFromCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - member: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the member'), - name: z.string().describe('Full name of the member'), - }) - .title('Member') - .describe('Member that was removed from the card'), - }), - }) - .describe('Action that is triggered when a member is removed from a card') - ), - }) -) - -export const removeLabelFromCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('removeLabelFromCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was modified'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was modified'), - label: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the label'), - name: z.string().describe('Name of the label'), - color: z.string().describe('Color of the label'), - }) - .title('Label') - .describe('Label that was removed from the card'), - }), - }) - .describe('Action that is triggered when a label is removed from a card') - ), - }) -) - -export const deleteCommentEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('deleteComment').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - list: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the list'), - name: z.string().describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was updated'), - action: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the comment that was deleted'), - }) - .title('Action') - .describe('The action details for the deleted comment'), - }), - }) - .describe('Action that is triggered when a comment is deleted from a card') - ), - }) -) - -export const deleteCheckItemEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('deleteCheckItem').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - checklist: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the checklist'), - name: z.string().describe('Name of the checklist'), - }) - .title('Checklist') - .describe('Checklist where the item was removed'), - checkItem: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the check item'), - name: z.string().describe('Name of the check item'), - state: z.union([z.literal('complete'), z.literal('incomplete')]).describe('State of the check item'), - textData: z.object({ - emoji: z.object({}).describe('Emoji of the check item'), - }), - due: z.string().datetime().optional().describe('Due date of the check item'), - }) - .title('Check Item') - .describe('Check item that was removed from the checklist'), - }), - }) - .describe('Action that is triggered when an item is removed from a checklist') - ), - }) -) - -export const deleteCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('deleteCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was deleted'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - }) - .title('Card') - .describe('Card that was deleted'), - list: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the list'), - name: z.string().describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was deleted'), - }), - }) - .describe('Action that is triggered when a card is deleted') - ), - }) -) - -export const deleteAttachmentFromCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('deleteAttachmentFromCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - list: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the list'), - name: z.string().describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was updated'), - attachment: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the attachment'), - name: z.string().describe('Name of the attachment'), - }) - .title('Attachment') - .describe('Attachment that was deleted from the card'), - }), - }) - .describe('Action that is triggered when an attachment is deleted from a card') - ), - }) -) - -export const createCheckItemEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('createCheckItem').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - checklist: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the checklist'), - name: z.string().describe('Name of the checklist'), - }) - .title('Checklist') - .describe('Checklist where the item was added'), - checkItem: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the check item'), - name: z.string().describe('Name of the check item'), - state: z.union([z.literal('complete'), z.literal('incomplete')]).describe('State of the check item'), - textData: z.object({ - emoji: z.object({}).describe('Emoji of the check item'), - }), - due: z.string().datetime().optional().describe('Due date of the check item'), - }) - .title('Check Item') - .describe('Check item that was added to the checklist'), - }), - }) - .describe('Action that is triggered when a new item si added to a checklist') - ), - }) -) - -export const createCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('createCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was created'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was created'), - list: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the list'), - name: z.string().describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was created'), - }), - }) - .describe('Action that is triggered when a card is created') - ), - }) -) - -export const commentCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('commentCard').describe('Type of the action'), - id: trelloIdSchema.describe('Unique identifier of the comment'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - list: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the list'), - name: z.string().describe('Name of the list'), - }) - .optional() - .title('List') - .describe('List where the card was updated'), - text: z.string().title('Text').describe('Text of the comment'), - }), - }) - .describe('Action that is triggered when a new comment is added to a card') - ), - }) -) - -export type CommentCardEvent = z.infer - -export const addMemberToCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('addMemberToCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - member: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the member'), - name: z.string().describe('Full name of the member'), - }) - .title('Member') - .describe('Member that was added to the card'), - }), - }) - .describe('Action that is triggered when a member is added to a card') - ), - }) -) - -export const addLabelToCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('addLabelToCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was modified'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was modified'), - label: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the label'), - name: z.string().describe('Name of the label'), - color: z.string().describe('Color of the label'), - }) - .title('Label') - .describe('Label that was added to the card'), - }), - }) - .describe('Action that is triggered when a label is added to a card') - ), - }) -) - -export const addChecklistToCardEventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z - .object({ - type: z.literal('addChecklistToCard').describe('Type of the action'), - data: z.object({ - board: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the board'), - name: z.string().describe('Name of the board'), - }) - .optional() - .title('Board') - .describe('Board where the card was updated'), - card: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the card'), - name: z.string().describe('Name of the card'), - }) - .title('Card') - .describe('Card that was updated'), - checklist: z - .object({ - id: trelloIdSchema.describe('Unique identifier of the checklist'), - name: z.string().describe('Name of the checklist'), - }) - .title('Checklist') - .describe('Checklist that was added to the card'), - }), - }) - .describe('Action that is triggered when a member is added to a card') - ), - }) -) diff --git a/integrations/trello/definitions/schemas.ts b/integrations/trello/definitions/schemas.ts index 3040f292469..47c19277824 100644 --- a/integrations/trello/definitions/schemas.ts +++ b/integrations/trello/definitions/schemas.ts @@ -6,41 +6,45 @@ export const trelloIdSchema = z.string().regex(trelloIdRegex) export type TrelloID = z.infer export const boardSchema = z.object({ - id: trelloIdSchema, - name: z.string(), + id: trelloIdSchema.title('Board ID').describe('Unique identifier of the board'), + name: z.string().title('Board Name').describe('The name of the board'), }) export type Board = z.infer export const cardSchema = z.object({ - id: trelloIdSchema, - name: z.string(), - description: z.string(), - listId: trelloIdSchema, - verticalPosition: z.number(), - isClosed: z.boolean(), - isCompleted: z.boolean(), - dueDate: z.string().datetime().optional(), - labelIds: z.array(trelloIdSchema), - memberIds: z.array(trelloIdSchema), + id: trelloIdSchema.title('Card ID').describe('Unique identifier of the card'), + name: z.string().title('Card Name').describe('The preview name of the card'), + description: z.string().title('Card Description').describe('Detailed description of the card'), + listId: trelloIdSchema.title('List ID').describe('Identifier of the list the card belongs to'), + verticalPosition: z.number().title('Position').describe('Position of the card within the list'), + isClosed: z.boolean().title('Is Closed').describe('Indicates if the card is closed'), + isCompleted: z.boolean().title('Is Completed').describe('Indicates if the card is completed'), + dueDate: z.string().datetime().optional().title('Due Date').describe('The expected completed by date (Optional)'), + labelIds: z.array(trelloIdSchema).title('Label IDs').describe('A list of label IDs attached to the card'), + memberIds: z.array(trelloIdSchema).title('Member IDs').describe('A list of member IDs assigned to the card'), }) export type Card = z.infer export const listSchema = z.object({ - id: trelloIdSchema, - name: z.string(), + id: trelloIdSchema.title('List ID').describe('Unique identifier of the list'), + name: z.string().title('List Name').describe('The name of the list'), }) export type List = z.infer export const memberSchema = z.object({ - id: trelloIdSchema, - username: z.string(), - fullName: z.string(), + id: trelloIdSchema.title('Member ID').describe('Unique identifier of the member'), + username: z.string().title('Username').describe('A public alias that represents the member'), + fullName: z.string().title('Full Name').describe('Full name of the member'), }) export type Member = z.infer export const webhookSchema = z.object({ - id: trelloIdSchema, - modelId: trelloIdSchema, - callbackUrl: z.string().url(), + id: trelloIdSchema.title('Webhook ID').describe('Unique identifier of the webhook'), + modelId: trelloIdSchema.title('Model ID').describe('ID of the Trello model the webhook watches for events'), + callbackUrl: z + .string() + .url() + .title('Callback URL') + .describe('The URL that Trello will call when a webhook event occurs'), }) export type Webhook = z.infer diff --git a/integrations/trello/definitions/states.ts b/integrations/trello/definitions/states.ts index 6fff3116a4f..2af47bfd2b3 100644 --- a/integrations/trello/definitions/states.ts +++ b/integrations/trello/definitions/states.ts @@ -5,14 +5,12 @@ const _webhookIdStateSchema = trelloIdSchema .nullable() .default(null) .title('Trello Webhook ID') - .describe('Unique id of the webhook that is created upon integration registration') + .describe('Unique id of the webhook that is created by Trello upon integration registration') export type WebhookIdState = z.infer export const states = { - webhookState: { + webhook: { type: 'integration', - schema: z - .object({ trelloWebhookId: _webhookIdStateSchema }) - .describe('State that stores the webhook id for the Trello integration'), + schema: z.object({ trelloWebhookId: _webhookIdStateSchema }), }, } as const satisfies NonNullable diff --git a/integrations/trello/hub.md b/integrations/trello/hub.md index e112dbeecc6..c0f83a6ae66 100644 --- a/integrations/trello/hub.md +++ b/integrations/trello/hub.md @@ -67,3 +67,41 @@ The id of the board should be 24 characters long consisting of letters and numbe - Some Trello paid features may not be available. [Trello - API Introduction]: https://developer.atlassian.com/cloud/trello/guides/rest-api/api-introduction/ + +## Migration from 1.x.x to 2.x.x + +- Replace "Move Card Up" action with "Move Card Down" actions (and vice versa) as the directions were reversed to match the visual displacement of the cards on Trello. +- Replace "Board List" action with "Get All Boards" +- Replace "Board Read" action with "Get Board By ID" +- Replace "List List" action with "Get Lists In Board" +- Replace "List Read" action with "Get List By ID" +- Replace "Card Create" action with "Create New Card" +- Replace "Card Update" action with "Update Card" +- Replace "Card Delete" action with "Delete Card" +- Replace "Card List" action with "Get Cards In List" +- Replace "Card Read" action with "Get Card By ID" +- Replace "Board Member List" action with "Get All Board Members" +- Replace "Board Member Read" action with "Get Member By ID Or Username" +- Replace "Card Member List" action with "Get All Card Members" +- Replace "Card Member Read" action with "Get Member By ID Or Username" +- Redefine the following properties in any "Create Card" actions + - Member IDs (formerly "Members") + - Label IDs (formerly "Labels") +- Redefine the following properties in any "Update Card" actions + - "Card Name" (formerly "Name") + - "Card Body" (formerly "Body Text") + - "Lifecycle Status" (formerly "Closed State") + - "Completion Status" (formerly "Complete State") + - "Member IDs To Add" (formerly "Members To Add") + - "Member IDs To Remove" (formerly "Members To Remove") + - "Label IDs To Add" (formerly "Labels To Add") + - "Label IDs To Remove" (formerly "Labels To Remove") +- Adjust the following events since their output data structure have changed + - "updateCard" event + - "commentCard" event + - "updateComment" event + - "deleteComment" event + - "createCheckItem" event + - "updateCheckItem" event + - "deleteCheckItem" event + - "updateCheckItemStateOnCard" event diff --git a/integrations/trello/integration.definition.ts b/integrations/trello/integration.definition.ts index 7b429232b9b..bb31db6ab62 100644 --- a/integrations/trello/integration.definition.ts +++ b/integrations/trello/integration.definition.ts @@ -1,18 +1,15 @@ +import { posthogHelper } from '@botpress/common' import * as sdk from '@botpress/sdk' -import { sentry as sentryHelpers } from '@botpress/sdk-addons' -import creatable from './bp_modules/creatable' -import deletable from './bp_modules/deletable' -import listable from './bp_modules/listable' -import readable from './bp_modules/readable' -import updatable from './bp_modules/updatable' import { events, states, actions, channels, user, configuration, entities } from './definitions' -import { integrationName } from './package.json' + +export const INTEGRATION_NAME = 'trello' +export const INTEGRATION_VERSION = '2.0.0' export default new sdk.IntegrationDefinition({ - name: integrationName, + name: INTEGRATION_NAME, title: 'Trello', - version: '1.2.0', + version: INTEGRATION_VERSION, readme: 'hub.md', description: 'Update cards, add comments, create new cards, and read board members from your chatbot.', icon: 'icon.svg', @@ -24,61 +21,6 @@ export default new sdk.IntegrationDefinition({ events, entities, secrets: { - ...sentryHelpers.COMMON_SECRET_NAMES, + ...posthogHelper.COMMON_SECRET_NAMES, }, }) - .extend(listable, ({ entities }) => ({ - entities: { item: entities.card }, - actions: { list: { name: 'cardList' } }, - })) - .extend(readable, ({ entities }) => ({ - entities: { item: entities.card }, - actions: { read: { name: 'cardRead' } }, - })) - .extend(creatable, ({ entities }) => ({ - entities: { item: entities.card }, - actions: { create: { name: 'cardCreate' } }, - events: { created: { name: 'cardCreated' } }, - })) - .extend(updatable, ({ entities }) => ({ - entities: { item: entities.card }, - actions: { update: { name: 'cardUpdate' } }, - events: { updated: { name: 'cardUpdated' } }, - })) - .extend(deletable, ({ entities }) => ({ - entities: { item: entities.card }, - actions: { delete: { name: 'cardDelete' } }, - events: { deleted: { name: 'cardDeleted' } }, - })) - .extend(listable, ({ entities }) => ({ - entities: { item: entities.list }, - actions: { list: { name: 'listList' } }, - })) - .extend(readable, ({ entities }) => ({ - entities: { item: entities.list }, - actions: { read: { name: 'listRead' } }, - })) - .extend(listable, ({ entities }) => ({ - entities: { item: entities.board }, - actions: { list: { name: 'boardList' } }, - })) - .extend(readable, ({ entities }) => ({ - entities: { item: entities.board }, - actions: { read: { name: 'boardRead' } }, - })) - .extend(listable, ({ entities }) => ({ - entities: { item: entities.boardMember }, - actions: { list: { name: 'boardMemberList' } }, - })) - .extend(readable, ({ entities }) => ({ - entities: { item: entities.boardMember }, - actions: { read: { name: 'boardMemberRead' } }, - })) - .extend(listable, ({ entities }) => ({ - entities: { item: entities.cardMember }, - actions: { list: { name: 'cardMemberList' } }, - })) - .extend(readable, ({ entities }) => ({ - entities: { item: entities.cardMember }, - actions: { read: { name: 'cardMemberRead' } }, - })) diff --git a/integrations/trello/package.json b/integrations/trello/package.json index c00034dfc3b..c83b1048131 100644 --- a/integrations/trello/package.json +++ b/integrations/trello/package.json @@ -1,6 +1,5 @@ { "name": "@botpresshub/trello", - "integrationName": "trello", "scripts": { "build": "bp add -y && bp build", "check:type": "tsc --noEmit", @@ -18,19 +17,6 @@ "devDependencies": { "@botpress/cli": "workspace:*", "@botpress/common": "workspace:*", - "@botpress/sdk": "workspace:*", - "@botpresshub/creatable": "workspace:*", - "@botpresshub/deletable": "workspace:*", - "@botpresshub/listable": "workspace:*", - "@botpresshub/readable": "workspace:*", - "@botpresshub/updatable": "workspace:*", - "@sentry/cli": "^2.39.1" - }, - "bpDependencies": { - "creatable": "../../interfaces/creatable", - "deletable": "../../interfaces/deletable", - "listable": "../../interfaces/listable", - "readable": "../../interfaces/readable", - "updatable": "../../interfaces/updatable" + "@botpress/sdk": "workspace:*" } } diff --git a/integrations/trello/src/actions/action-wrapper.ts b/integrations/trello/src/actions/action-wrapper.ts deleted file mode 100644 index 320a7037739..00000000000 --- a/integrations/trello/src/actions/action-wrapper.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { createActionWrapper } from '@botpress/common' -import { TrelloClient } from '../trello-api/trello-client' -import * as bp from '.botpress' - -export const wrapAction: typeof _wrapAction = (meta, actionImpl) => - _wrapAction(meta, (props) => { - props.logger - .forBot() - .debug(`Running action "${meta.actionName}" [bot id: ${props.ctx.botId}]`, { input: props.input }) - - return actionImpl(props as Parameters[0], props.input) - }) - -const _wrapAction = createActionWrapper()({ - toolFactories: { - trelloClient: ({ ctx }) => new TrelloClient({ ctx }), - }, -}) diff --git a/integrations/trello/src/actions/board-actions.ts b/integrations/trello/src/actions/board-actions.ts new file mode 100644 index 00000000000..c607571cec4 --- /dev/null +++ b/integrations/trello/src/actions/board-actions.ts @@ -0,0 +1,33 @@ +import { nameCompare } from '../string-utils' +import { printActionTriggeredMsg, getTools } from './helpers' +import * as bp from '.botpress' + +export const getAllBoards: bp.Integration['actions']['getAllBoards'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const {} = props.input + const boards = await trelloClient.getAllBoards() + return { boards } +} + +export const getBoardById: bp.Integration['actions']['getBoardById'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { boardId } = props.input + const board = await trelloClient.getBoardById({ boardId }) + + return { board } +} + +export const getBoardsByDisplayName: bp.Integration['actions']['getBoardsByDisplayName'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { boardName } = props.input + const boards = await trelloClient.getAllBoards() + const matchingBoards = boards.filter((b) => nameCompare(b.name, boardName)) + + return { boards: matchingBoards } +} diff --git a/integrations/trello/src/actions/card-actions.ts b/integrations/trello/src/actions/card-actions.ts new file mode 100644 index 00000000000..5f2576d3e71 --- /dev/null +++ b/integrations/trello/src/actions/card-actions.ts @@ -0,0 +1,162 @@ +import { z, RuntimeError } from '@botpress/sdk' +import { nameCompare } from '../string-utils' +import { CardPosition } from '../trello-api' +import { printActionTriggeredMsg, getTools } from './helpers' +import { moveCardVertically } from './move-card-helpers' +import * as bp from '.botpress' + +export const getCardsInList: bp.Integration['actions']['getCardsInList'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { listId } = props.input + const matchingCards = await trelloClient.getCardsInList({ listId }) + + return { cards: matchingCards } +} + +export const getCardById: bp.Integration['actions']['getCardById'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const card = await trelloClient.getCardById({ cardId: props.input.cardId }) + return { card } +} + +export const getCardsByDisplayName: bp.Integration['actions']['getCardsByDisplayName'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { listId, cardName } = props.input + const cards = await trelloClient.getCardsInList({ listId }) + const matchingCards = cards.filter((c) => nameCompare(c.name, cardName)) + + return { cards: matchingCards } +} + +export const createCard: bp.Integration['actions']['createCard'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { listId, cardName, cardBody, memberIds, labelIds, dueDate, completionStatus } = props.input + const newCard = await trelloClient.createCard({ + card: { + name: cardName, + description: cardBody ?? '', + listId, + memberIds, + labelIds, + dueDate, + isCompleted: completionStatus === 'Complete', + }, + }) + + return { message: `Card created successfully. Card ID: ${newCard.id}`, newCardId: newCard.id } +} + +const _verticalPositionSchema = z.union([z.literal('top'), z.literal('bottom'), z.coerce.number()]).optional() +const _validateVerticalPosition = (verticalPosition: string | undefined): CardPosition | undefined => { + const result = _verticalPositionSchema.safeParse(verticalPosition?.toLowerCase().trim()) + if (!result.success) { + throw new RuntimeError( + `Invalid verticalPosition value. It must be either "top", "bottom", or a float. -> ${result.error.message}` + ) + } + + return result.data +} + +export const updateCard: bp.Integration['actions']['updateCard'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { + cardId, + listId, + cardName, + cardBody, + lifecycleStatus, + completionStatus, + dueDate, + labelIdsToAdd, + labelIdsToRemove, + memberIdsToAdd, + memberIdsToRemove, + verticalPosition, + } = props.input + + const card = await trelloClient.getCardById({ cardId }) + await trelloClient.updateCard({ + partialCard: { + id: cardId, + listId, + name: cardName, + description: cardBody, + isClosed: lifecycleStatus ? lifecycleStatus === 'Archived' : undefined, + isCompleted: completionStatus ? completionStatus === 'Complete' : undefined, + dueDate, + labelIds: card.labelIds.concat(labelIdsToAdd ?? []).filter((labelId) => !labelIdsToRemove?.includes(labelId)), + memberIds: card.memberIds + .concat(memberIdsToAdd ?? []) + .filter((memberId) => !memberIdsToRemove?.includes(memberId)), + verticalPosition: _validateVerticalPosition(verticalPosition), + }, + }) + + return { message: 'Card updated successfully.' } +} + +export const deleteCard: bp.Integration['actions']['deleteCard'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { cardId, hardDelete = false } = props.input + + if (hardDelete) { + await trelloClient.deleteCard(cardId) + } else { + await trelloClient.updateCard({ + partialCard: { + id: cardId, + isClosed: true, + }, + }) + } + + return {} +} + +export const moveCardToList: bp.Integration['actions']['moveCardToList'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { cardId, newListId } = props.input + const card = await trelloClient.getCardById({ cardId }) + const newList = await trelloClient.getListById({ listId: newListId }) + + await trelloClient.updateCard({ partialCard: { id: card.id, listId: newList.id } }) + + return { message: 'Card successfully moved to the new list' } +} + +export const moveCardDown: bp.Integration['actions']['moveCardDown'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { cardId, moveDownByNSpaces } = props.input + const numOfPositions = moveDownByNSpaces ?? 1 + await moveCardVertically({ trelloClient, cardId, numOfPositions }) + + return { message: 'Card successfully moved down' } +} + +export const moveCardUp: bp.Integration['actions']['moveCardUp'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { cardId, moveUpByNSpaces } = props.input + const numOfPositions = -(moveUpByNSpaces ?? 1) + await moveCardVertically({ trelloClient, cardId, numOfPositions }) + + return { message: 'Card successfully moved up' } +} diff --git a/integrations/trello/src/actions/card-comment-actions.ts b/integrations/trello/src/actions/card-comment-actions.ts new file mode 100644 index 00000000000..351d9ce87bf --- /dev/null +++ b/integrations/trello/src/actions/card-comment-actions.ts @@ -0,0 +1,12 @@ +import { printActionTriggeredMsg, getTools } from './helpers' +import * as bp from '.botpress' + +export const addCardComment: bp.Integration['actions']['addCardComment'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { cardId, commentBody } = props.input + const newCommentId = await trelloClient.addCardComment({ cardId, commentBody }) + + return { message: 'Comment successfully added to the card', newCommentId } +} diff --git a/integrations/trello/src/actions/helpers.ts b/integrations/trello/src/actions/helpers.ts new file mode 100644 index 00000000000..ad5e09609ce --- /dev/null +++ b/integrations/trello/src/actions/helpers.ts @@ -0,0 +1,33 @@ +import { TrelloClient } from '../trello-api/trello-client' +import * as bp from '.botpress' + +export const printActionTriggeredMsg = ({ type, ctx, logger, input }: bp.AnyActionProps) => { + logger.forBot().debug(`Running action "${type}" [bot id: ${ctx.botId}]`, { input }) +} + +type ResolvedFactoryTools unknown>> = { + readonly [K in keyof T]: ReturnType +} & {} // <-- Empty object used for improving readability in IDE type previews + +const _createToolFactory = unknown>>(toolBuilders: T) => { + return (props: bp.CommonHandlerProps) => { + // Of all the options I tested, Proxy had the best average + // performance, especially as more "toolBuilders" are added. + return new Proxy(toolBuilders, { + get(target, tool: string) { + const builder = target[tool] + if (!builder) { + // Sanity check, should never actually be thrown + throw new Error(`No tool builder found for key: ${String(tool)}`) + } + return builder(props) + }, + }) as ResolvedFactoryTools + } +} + +export const getTools = _createToolFactory({ + trelloClient({ ctx }: bp.CommonHandlerProps) { + return new TrelloClient({ ctx }) + }, +}) diff --git a/integrations/trello/src/actions/implementations/addCardComment.ts b/integrations/trello/src/actions/implementations/addCardComment.ts deleted file mode 100644 index 8ca375335a3..00000000000 --- a/integrations/trello/src/actions/implementations/addCardComment.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const addCardComment = wrapAction( - { actionName: 'addCardComment' }, - async ({ trelloClient }, { cardId, commentBody }) => { - const newCommentId = await trelloClient.addCardComment({ cardId, commentBody }) - - return { message: 'Comment successfully added to the card', newCommentId } - } -) diff --git a/integrations/trello/src/actions/implementations/createCard.ts b/integrations/trello/src/actions/implementations/createCard.ts deleted file mode 100644 index caf329cb12e..00000000000 --- a/integrations/trello/src/actions/implementations/createCard.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const createCard = wrapAction( - { actionName: 'createCard' }, - async ({ trelloClient }, { listId, cardName, cardBody, members, labels, dueDate }) => { - const newCard = await trelloClient.createCard({ - card: { name: cardName, description: cardBody ?? '', listId, memberIds: members, labelIds: labels, dueDate }, - }) - - return { message: `Card created successfully. Card ID: ${newCard.id}`, newCardId: newCard.id } - } -) diff --git a/integrations/trello/src/actions/implementations/getAllBoardMembers.ts b/integrations/trello/src/actions/implementations/getAllBoardMembers.ts deleted file mode 100644 index 7e84339ca63..00000000000 --- a/integrations/trello/src/actions/implementations/getAllBoardMembers.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getAllBoardMembers = wrapAction( - { actionName: 'getAllBoardMembers' }, - async ({ trelloClient }, { boardId }) => { - const members = await trelloClient.getBoardMembers({ boardId }) - return { members } - } -) diff --git a/integrations/trello/src/actions/implementations/getAllBoards.ts b/integrations/trello/src/actions/implementations/getAllBoards.ts deleted file mode 100644 index ccf3dc31bb8..00000000000 --- a/integrations/trello/src/actions/implementations/getAllBoards.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getAllBoards = wrapAction({ actionName: 'getAllBoards' }, async ({ trelloClient }) => { - const boards = await trelloClient.getAllBoards() - return { boards } -}) diff --git a/integrations/trello/src/actions/implementations/getAllCardMembers.ts b/integrations/trello/src/actions/implementations/getAllCardMembers.ts deleted file mode 100644 index f5b609e1b5a..00000000000 --- a/integrations/trello/src/actions/implementations/getAllCardMembers.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getAllCardMembers = wrapAction( - { actionName: 'getAllCardMembers' }, - async ({ trelloClient }, { cardId }) => ({ - members: await trelloClient.getCardMembers({ cardId }), - }) -) diff --git a/integrations/trello/src/actions/implementations/getBoardById.ts b/integrations/trello/src/actions/implementations/getBoardById.ts deleted file mode 100644 index f9e88cbf06c..00000000000 --- a/integrations/trello/src/actions/implementations/getBoardById.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getBoardById = wrapAction({ actionName: 'getBoardById' }, async ({ trelloClient }, { boardId }) => { - const board = await trelloClient.getBoardById({ boardId }) - return { board } -}) diff --git a/integrations/trello/src/actions/implementations/getBoardMembersByDisplayName.ts b/integrations/trello/src/actions/implementations/getBoardMembersByDisplayName.ts deleted file mode 100644 index 369a2940b31..00000000000 --- a/integrations/trello/src/actions/implementations/getBoardMembersByDisplayName.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { nameCompare } from 'src/string-utils' -import { wrapAction } from '../action-wrapper' - -export const getBoardMembersByDisplayName = wrapAction( - { actionName: 'getBoardMembersByDisplayName' }, - async ({ trelloClient }, { boardId, displayName }) => { - const members = await trelloClient.getBoardMembers({ boardId }) - const matchingMembers = members.filter((m) => nameCompare(m.fullName, displayName)) - - return { members: matchingMembers } - } -) diff --git a/integrations/trello/src/actions/implementations/getBoardsByDisplayName.ts b/integrations/trello/src/actions/implementations/getBoardsByDisplayName.ts deleted file mode 100644 index 94135e5d59a..00000000000 --- a/integrations/trello/src/actions/implementations/getBoardsByDisplayName.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { nameCompare } from 'src/string-utils' -import { wrapAction } from '../action-wrapper' - -export const getBoardsByDisplayName = wrapAction( - { actionName: 'getBoardsByDisplayName' }, - async ({ trelloClient }, { boardName }) => { - const boards = await trelloClient.getAllBoards() - const matchingBoards = boards.filter((b) => nameCompare(b.name, boardName)) - - return { boards: matchingBoards } - } -) diff --git a/integrations/trello/src/actions/implementations/getCardById.ts b/integrations/trello/src/actions/implementations/getCardById.ts deleted file mode 100644 index 5fdef6ff88f..00000000000 --- a/integrations/trello/src/actions/implementations/getCardById.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getCardById = wrapAction({ actionName: 'getCardById' }, async ({ trelloClient }, { cardId }) => { - const card = await trelloClient.getCardById({ cardId }) - return { card } -}) diff --git a/integrations/trello/src/actions/implementations/getCardsByDisplayName.ts b/integrations/trello/src/actions/implementations/getCardsByDisplayName.ts deleted file mode 100644 index d4131c6bb5e..00000000000 --- a/integrations/trello/src/actions/implementations/getCardsByDisplayName.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { nameCompare } from 'src/string-utils' -import { wrapAction } from '../action-wrapper' - -export const getCardsByDisplayName = wrapAction( - { actionName: 'getCardsByDisplayName' }, - async ({ trelloClient }, { listId, cardName }) => { - const cards = await trelloClient.getCardsInList({ listId }) - const matchingCards = cards.filter((c) => nameCompare(c.name, cardName)) - - return { cards: matchingCards } - } -) diff --git a/integrations/trello/src/actions/implementations/getCardsInList.ts b/integrations/trello/src/actions/implementations/getCardsInList.ts deleted file mode 100644 index 8eb2126bc51..00000000000 --- a/integrations/trello/src/actions/implementations/getCardsInList.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getCardsInList = wrapAction({ actionName: 'getCardsInList' }, async ({ trelloClient }, { listId }) => { - const matchingCards = await trelloClient.getCardsInList({ listId }) - - return { cards: matchingCards } -}) diff --git a/integrations/trello/src/actions/implementations/getListById.ts b/integrations/trello/src/actions/implementations/getListById.ts deleted file mode 100644 index f55ff3d17d8..00000000000 --- a/integrations/trello/src/actions/implementations/getListById.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getListById = wrapAction({ actionName: 'getListById' }, async ({ trelloClient }, { listId }) => { - const list = await trelloClient.getListById({ listId }) - - return { list } -}) diff --git a/integrations/trello/src/actions/implementations/getListsByDisplayName.ts b/integrations/trello/src/actions/implementations/getListsByDisplayName.ts deleted file mode 100644 index e3212488e80..00000000000 --- a/integrations/trello/src/actions/implementations/getListsByDisplayName.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { nameCompare } from 'src/string-utils' -import { wrapAction } from '../action-wrapper' - -export const getListsByDisplayName = wrapAction( - { actionName: 'getListsByDisplayName' }, - async ({ trelloClient }, { boardId, listName }) => { - const lists = await trelloClient.getListsInBoard({ boardId }) - const matchingLists = lists.filter((l) => nameCompare(l.name, listName)) - - return { lists: matchingLists } - } -) diff --git a/integrations/trello/src/actions/implementations/getListsInBoard.ts b/integrations/trello/src/actions/implementations/getListsInBoard.ts deleted file mode 100644 index bd3f265f5b3..00000000000 --- a/integrations/trello/src/actions/implementations/getListsInBoard.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getListsInBoard = wrapAction({ actionName: 'getListsInBoard' }, async ({ trelloClient }, { boardId }) => { - const matchingLists = await trelloClient.getListsInBoard({ boardId }) - - return { lists: matchingLists } -}) diff --git a/integrations/trello/src/actions/implementations/getMemberByIdOrUsername.ts b/integrations/trello/src/actions/implementations/getMemberByIdOrUsername.ts deleted file mode 100644 index a98cb1adba5..00000000000 --- a/integrations/trello/src/actions/implementations/getMemberByIdOrUsername.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const getMemberByIdOrUsername = wrapAction( - { actionName: 'getMemberByIdOrUsername' }, - async ({ trelloClient }, { memberIdOrUsername }) => { - const member = await trelloClient.getMemberByIdOrUsername({ memberId: memberIdOrUsername }) - - return { member } - } -) diff --git a/integrations/trello/src/actions/implementations/interfaces/boardList.ts b/integrations/trello/src/actions/implementations/interfaces/boardList.ts deleted file mode 100644 index e1b31c07d40..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/boardList.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { wrapAction } from '../../action-wrapper' - -export const boardList = wrapAction({ actionName: 'boardList' }, async ({ trelloClient }) => ({ - items: await trelloClient.getAllBoards(), - meta: {}, -})) diff --git a/integrations/trello/src/actions/implementations/interfaces/boardMemberList.ts b/integrations/trello/src/actions/implementations/interfaces/boardMemberList.ts deleted file mode 100644 index 7943178a59b..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/boardMemberList.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const boardMemberList = wrapAction( - { actionName: 'boardMemberList' }, - async ({ trelloClient }, { nextToken: boardId }) => { - if (!boardId) { - throw new sdk.RuntimeError('Board ID is required: make sure the nextToken parameter contains the board ID') - } - - const items = await trelloClient.getBoardMembers({ boardId }) - return { items, meta: {} } - } -) diff --git a/integrations/trello/src/actions/implementations/interfaces/boardMemberRead.ts b/integrations/trello/src/actions/implementations/interfaces/boardMemberRead.ts deleted file mode 100644 index ab3a49ae8d1..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/boardMemberRead.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const boardMemberRead = wrapAction( - { actionName: 'boardMemberRead' }, - async ({ trelloClient }, { id: boardMemberId }) => { - if (!boardMemberId) { - throw new sdk.RuntimeError('Member ID is required: make sure the id parameter contains the member ID') - } - - const item = await trelloClient.getMemberByIdOrUsername({ memberId: boardMemberId }) - return { item, meta: {} } - } -) diff --git a/integrations/trello/src/actions/implementations/interfaces/boardRead.ts b/integrations/trello/src/actions/implementations/interfaces/boardRead.ts deleted file mode 100644 index d8b7e2e7b85..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/boardRead.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const boardRead = wrapAction({ actionName: 'boardRead' }, async ({ trelloClient }, { id: boardId }) => { - if (!boardId) { - throw new sdk.RuntimeError('Board ID is required: make sure the id parameter contains the board ID') - } - - const item = await trelloClient.getBoardById({ boardId }) - return { item, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/interfaces/cardCreate.ts b/integrations/trello/src/actions/implementations/interfaces/cardCreate.ts deleted file mode 100644 index 6a566d554ec..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/cardCreate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { wrapAction } from '../../action-wrapper' - -export const cardCreate = wrapAction({ actionName: 'cardCreate' }, async ({ trelloClient }, { item }) => { - const newCard = await trelloClient.createCard({ - card: item, - }) - - return { item: newCard, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/interfaces/cardDelete.ts b/integrations/trello/src/actions/implementations/interfaces/cardDelete.ts deleted file mode 100644 index 6e519ee678a..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/cardDelete.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const cardDelete = wrapAction({ actionName: 'cardDelete' }, async ({ trelloClient }, { id: cardId }) => { - if (!cardId) { - throw new sdk.RuntimeError('Card ID is required: make sure the id parameter contains the card ID') - } - - // This effectively archives the card (soft deletion): - const item = await trelloClient.updateCard({ partialCard: { id: cardId, isClosed: true } }) - - return { item, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/interfaces/cardList.ts b/integrations/trello/src/actions/implementations/interfaces/cardList.ts deleted file mode 100644 index fb308f34cef..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/cardList.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const cardList = wrapAction({ actionName: 'cardList' }, async ({ trelloClient }, { nextToken: listId }) => { - if (!listId) { - throw new sdk.RuntimeError('List ID is required: make sure the nextToken parameter contains the list ID') - } - - const items = await trelloClient.getCardsInList({ listId }) - return { items, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/interfaces/cardMemberList.ts b/integrations/trello/src/actions/implementations/interfaces/cardMemberList.ts deleted file mode 100644 index a6a17ced9ab..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/cardMemberList.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const cardMemberList = wrapAction( - { actionName: 'cardMemberList' }, - async ({ trelloClient }, { nextToken: cardId }) => { - if (!cardId) { - throw new sdk.RuntimeError('Card ID is required: make sure the nextToken parameter contains the card ID') - } - - const items = await trelloClient.getCardMembers({ cardId }) - return { items, meta: {} } - } -) diff --git a/integrations/trello/src/actions/implementations/interfaces/cardMemberRead.ts b/integrations/trello/src/actions/implementations/interfaces/cardMemberRead.ts deleted file mode 100644 index d818d8151d4..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/cardMemberRead.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const cardMemberRead = wrapAction( - { actionName: 'cardMemberRead' }, - async ({ trelloClient }, { id: cardMemberId }) => { - if (!cardMemberId) { - throw new sdk.RuntimeError('Member ID is required: make sure the id parameter contains the member ID') - } - - const item = await trelloClient.getMemberByIdOrUsername({ memberId: cardMemberId }) - return { item, meta: {} } - } -) diff --git a/integrations/trello/src/actions/implementations/interfaces/cardRead.ts b/integrations/trello/src/actions/implementations/interfaces/cardRead.ts deleted file mode 100644 index 99590501d29..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/cardRead.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const cardRead = wrapAction({ actionName: 'cardRead' }, async ({ trelloClient }, { id: cardId }) => { - if (!cardId) { - throw new sdk.RuntimeError('Card ID is required: make sure the id parameter contains the card ID') - } - - const item = await trelloClient.getCardById({ cardId }) - return { item, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/interfaces/cardUpdate.ts b/integrations/trello/src/actions/implementations/interfaces/cardUpdate.ts deleted file mode 100644 index 205cbe382ab..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/cardUpdate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { wrapAction } from '../../action-wrapper' - -export const cardUpdate = wrapAction({ actionName: 'cardUpdate' }, async ({ trelloClient }, { item }) => { - const newCard = await trelloClient.updateCard({ - partialCard: item, - }) - - return { item: newCard, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/interfaces/listList.ts b/integrations/trello/src/actions/implementations/interfaces/listList.ts deleted file mode 100644 index 6b362a112a0..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/listList.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const listList = wrapAction({ actionName: 'listList' }, async ({ trelloClient }, { nextToken: boardId }) => { - if (!boardId) { - throw new sdk.RuntimeError('Board ID is required: make sure the nextToken parameter contains the board ID') - } - - const items = await trelloClient.getListsInBoard({ boardId }) - return { items, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/interfaces/listRead.ts b/integrations/trello/src/actions/implementations/interfaces/listRead.ts deleted file mode 100644 index f04307b8cf9..00000000000 --- a/integrations/trello/src/actions/implementations/interfaces/listRead.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { wrapAction } from '../../action-wrapper' - -export const listRead = wrapAction({ actionName: 'listRead' }, async ({ trelloClient }, { id: listId }) => { - if (!listId) { - throw new sdk.RuntimeError('List ID is required: make sure the id parameter contains the list ID') - } - - const item = await trelloClient.getListById({ listId }) - return { item, meta: {} } -}) diff --git a/integrations/trello/src/actions/implementations/moveCardDown.ts b/integrations/trello/src/actions/implementations/moveCardDown.ts deleted file mode 100644 index 26f2ee80b96..00000000000 --- a/integrations/trello/src/actions/implementations/moveCardDown.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { wrapAction } from '../action-wrapper' -import { moveCardVertically } from './shared/move-card-vertically' - -export const moveCardDown = wrapAction( - { actionName: 'moveCardDown' }, - async ({ trelloClient }, { cardId, moveDownByNSpaces }) => { - const nbPositions = -(moveDownByNSpaces ?? 1) - await moveCardVertically({ trelloClient, cardId, nbPositions }) - - return { message: 'Card successfully moved down' } - } -) diff --git a/integrations/trello/src/actions/implementations/moveCardToList.ts b/integrations/trello/src/actions/implementations/moveCardToList.ts deleted file mode 100644 index ce0a3f613ed..00000000000 --- a/integrations/trello/src/actions/implementations/moveCardToList.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const moveCardToList = wrapAction( - { actionName: 'moveCardToList' }, - async ({ trelloClient }, { cardId, newListId }) => { - const card = await trelloClient.getCardById({ cardId }) - const newList = await trelloClient.getListById({ listId: newListId }) - - await trelloClient.updateCard({ partialCard: { id: card.id, listId: newList.id } }) - - return { message: 'Card successfully moved to the new list' } - } -) diff --git a/integrations/trello/src/actions/implementations/moveCardUp.ts b/integrations/trello/src/actions/implementations/moveCardUp.ts deleted file mode 100644 index e7186ea6f7e..00000000000 --- a/integrations/trello/src/actions/implementations/moveCardUp.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { wrapAction } from '../action-wrapper' -import { moveCardVertically } from './shared/move-card-vertically' - -export const moveCardUp = wrapAction( - { actionName: 'moveCardUp' }, - async ({ trelloClient }, { cardId, moveUpByNSpaces }) => { - const nbPositions = moveUpByNSpaces ?? 1 - await moveCardVertically({ trelloClient, cardId, nbPositions }) - - return { message: 'Card successfully moved up' } - } -) diff --git a/integrations/trello/src/actions/implementations/shared/move-card-vertically.ts b/integrations/trello/src/actions/implementations/shared/move-card-vertically.ts deleted file mode 100644 index 87550745de5..00000000000 --- a/integrations/trello/src/actions/implementations/shared/move-card-vertically.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as sdk from '@botpress/sdk' -import { Card } from 'definitions/schemas' -import { TrelloClient } from 'src/trello-api/trello-client' - -export const moveCardVertically = async ({ - trelloClient, - cardId, - nbPositions, -}: { - trelloClient: TrelloClient - cardId: string - nbPositions: number -}): Promise => { - if (nbPositions === 0) { - return - } - - const card = await trelloClient.getCardById({ cardId }) - const cardsInList = await trelloClient.getCardsInList({ listId: card.listId }) - const cardIndex = cardsInList.findIndex((c) => c.id === cardId) - - const updatedCard = _moveCard(cardsInList, card, cardIndex, nbPositions) - - await trelloClient.updateCard({ - partialCard: updatedCard, - }) -} - -const _moveCard = (cardsInList: Card[], card: Card, cardIndex: number, nbPositions: number): Card => { - const newIndex = _calculateNewIndex(cardIndex, nbPositions, cardsInList.length) - const cardAtNewPosition = _getCardAtNewPosition(cardsInList, newIndex) - - return { - ...card, - verticalPosition: cardAtNewPosition.verticalPosition + Math.sign(nbPositions), - } -} - -const _calculateNewIndex = (cardIndex: number, nbPositions: number, listLength: number): number => { - const newIndex = cardIndex + nbPositions - - if (newIndex < 0 || newIndex >= listLength) { - throw new sdk.RuntimeError( - `Impossible to move the card by ${nbPositions} positions, as it would put the card out of bounds` - ) - } - - return newIndex -} - -const _getCardAtNewPosition = (cardsInList: Card[], newIndex: number): Card => { - const cardAtNewPosition = cardsInList[newIndex] - - if (!cardAtNewPosition) { - throw new sdk.RuntimeError('Card to swap with must exist') - } - - return cardAtNewPosition -} diff --git a/integrations/trello/src/actions/implementations/updateCard.ts b/integrations/trello/src/actions/implementations/updateCard.ts deleted file mode 100644 index 1302077161c..00000000000 --- a/integrations/trello/src/actions/implementations/updateCard.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { wrapAction } from '../action-wrapper' - -export const updateCard = wrapAction( - { actionName: 'updateCard' }, - async ( - { trelloClient }, - { - bodyText, - cardId, - closedState, - completeState, - dueDate, - labelsToAdd, - labelsToRemove, - membersToAdd, - membersToRemove, - name, - } - ) => { - const card = await trelloClient.getCardById({ cardId }) - await trelloClient.updateCard({ - partialCard: { - id: cardId, - name, - description: bodyText, - isClosed: closedState === 'archived', - isCompleted: completeState === 'complete', - dueDate, - labelIds: card.labelIds.concat(labelsToAdd ?? []).filter((labelId) => !labelsToRemove?.includes(labelId)), - memberIds: card.memberIds.concat(membersToAdd ?? []).filter((memberId) => !membersToRemove?.includes(memberId)), - }, - }) - - return { message: 'Card updated successfully.' } - } -) diff --git a/integrations/trello/src/actions/index.ts b/integrations/trello/src/actions/index.ts index ea07fe35d31..cff5e1b7f7b 100644 --- a/integrations/trello/src/actions/index.ts +++ b/integrations/trello/src/actions/index.ts @@ -1,35 +1,23 @@ -import { addCardComment } from './implementations/addCardComment' -import { createCard } from './implementations/createCard' -import { getAllBoardMembers } from './implementations/getAllBoardMembers' -import { getAllBoards } from './implementations/getAllBoards' -import { getAllCardMembers } from './implementations/getAllCardMembers' -import { getBoardById } from './implementations/getBoardById' -import { getBoardMembersByDisplayName } from './implementations/getBoardMembersByDisplayName' -import { getBoardsByDisplayName } from './implementations/getBoardsByDisplayName' -import { getCardById } from './implementations/getCardById' -import { getCardsByDisplayName } from './implementations/getCardsByDisplayName' -import { getCardsInList } from './implementations/getCardsInList' -import { getListById } from './implementations/getListById' -import { getListsByDisplayName } from './implementations/getListsByDisplayName' -import { getListsInBoard } from './implementations/getListsInBoard' -import { getMemberByIdOrUsername } from './implementations/getMemberByIdOrUsername' -import { boardList } from './implementations/interfaces/boardList' -import { boardMemberList } from './implementations/interfaces/boardMemberList' -import { boardMemberRead } from './implementations/interfaces/boardMemberRead' -import { boardRead } from './implementations/interfaces/boardRead' -import { cardCreate } from './implementations/interfaces/cardCreate' -import { cardDelete } from './implementations/interfaces/cardDelete' -import { cardList } from './implementations/interfaces/cardList' -import { cardMemberList } from './implementations/interfaces/cardMemberList' -import { cardMemberRead } from './implementations/interfaces/cardMemberRead' -import { cardRead } from './implementations/interfaces/cardRead' -import { cardUpdate } from './implementations/interfaces/cardUpdate' -import { listList } from './implementations/interfaces/listList' -import { listRead } from './implementations/interfaces/listRead' -import { moveCardDown } from './implementations/moveCardDown' -import { moveCardToList } from './implementations/moveCardToList' -import { moveCardUp } from './implementations/moveCardUp' -import { updateCard } from './implementations/updateCard' +import { getAllBoards, getBoardsByDisplayName, getBoardById } from './board-actions' +import { + getCardsInList, + getCardsByDisplayName, + getCardById, + createCard, + updateCard, + moveCardToList, + moveCardUp, + moveCardDown, + deleteCard, +} from './card-actions' +import { addCardComment } from './card-comment-actions' +import { getListsInBoard, getListsByDisplayName, getListById } from './list-actions' +import { + getAllBoardMembers, + getAllCardMembers, + getMemberByIdOrUsername, + getBoardMembersByDisplayName, +} from './member-actions' import * as bp from '.botpress' export const actions = { @@ -47,6 +35,7 @@ export const actions = { getCardsInList, createCard, updateCard, + deleteCard, addCardComment, moveCardUp, moveCardDown, @@ -56,21 +45,4 @@ export const actions = { getBoardMembersByDisplayName, getAllBoardMembers, getAllCardMembers, - // === Interface Board Actions === - boardList, - boardRead, - // === Interface List Actions === - listList, - listRead, - // === Interface Card Actions === - cardList, - cardRead, - cardCreate, - cardUpdate, - cardDelete, - // === Interface Member Actions === - boardMemberList, - boardMemberRead, - cardMemberList, - cardMemberRead, } as const satisfies bp.IntegrationProps['actions'] diff --git a/integrations/trello/src/actions/list-actions.ts b/integrations/trello/src/actions/list-actions.ts new file mode 100644 index 00000000000..d607dff75b8 --- /dev/null +++ b/integrations/trello/src/actions/list-actions.ts @@ -0,0 +1,34 @@ +import { nameCompare } from '../string-utils' +import { printActionTriggeredMsg, getTools } from './helpers' +import * as bp from '.botpress' + +export const getListsInBoard: bp.Integration['actions']['getListsInBoard'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { boardId } = props.input + const matchingLists = await trelloClient.getListsInBoard({ boardId }) + + return { lists: matchingLists } +} + +export const getListsByDisplayName: bp.Integration['actions']['getListsByDisplayName'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { boardId, listName } = props.input + const lists = await trelloClient.getListsInBoard({ boardId }) + const matchingLists = lists.filter((l) => nameCompare(l.name, listName)) + + return { lists: matchingLists } +} + +export const getListById: bp.Integration['actions']['getListById'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { listId } = props.input + const list = await trelloClient.getListById({ listId }) + + return { list } +} diff --git a/integrations/trello/src/actions/member-actions.ts b/integrations/trello/src/actions/member-actions.ts new file mode 100644 index 00000000000..9b4363c67d2 --- /dev/null +++ b/integrations/trello/src/actions/member-actions.ts @@ -0,0 +1,45 @@ +import { nameCompare } from '../string-utils' +import { printActionTriggeredMsg, getTools } from './helpers' +import * as bp from '.botpress' + +export const getAllBoardMembers: bp.Integration['actions']['getAllBoardMembers'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { boardId } = props.input + const members = await trelloClient.getBoardMembers({ boardId }) + return { members } +} + +export const getAllCardMembers: bp.Integration['actions']['getAllCardMembers'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { cardId } = props.input + return { + members: await trelloClient.getCardMembers({ cardId }), + } +} + +export const getBoardMembersByDisplayName: bp.Integration['actions']['getBoardMembersByDisplayName'] = async ( + props +) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { boardId, displayName } = props.input + const members = await trelloClient.getBoardMembers({ boardId }) + const matchingMembers = members.filter((m) => nameCompare(m.fullName, displayName)) + + return { members: matchingMembers } +} + +export const getMemberByIdOrUsername: bp.Integration['actions']['getMemberByIdOrUsername'] = async (props) => { + printActionTriggeredMsg(props) + const { trelloClient } = getTools(props) + + const { memberIdOrUsername } = props.input + const member = await trelloClient.getMemberByIdOrUsername({ memberId: memberIdOrUsername }) + + return { member } +} diff --git a/integrations/trello/src/actions/move-card-helpers.ts b/integrations/trello/src/actions/move-card-helpers.ts new file mode 100644 index 00000000000..b402e27e23e --- /dev/null +++ b/integrations/trello/src/actions/move-card-helpers.ts @@ -0,0 +1,59 @@ +import * as sdk from '@botpress/sdk' +import { Card } from 'definitions/schemas' +import { TrelloClient, CardPosition } from '../trello-api' + +export const moveCardVertically = async ({ + trelloClient, + cardId, + numOfPositions, +}: { + trelloClient: TrelloClient + cardId: string + numOfPositions: number +}): Promise => { + if (numOfPositions === 0) { + return + } + + const card = await trelloClient.getCardById({ cardId }) + const cardsInList = await trelloClient.getCardsInList({ listId: card.listId }) + cardsInList.sort((a, b) => a.verticalPosition - b.verticalPosition) + + const newPosition = _evaluateNewPosition(cardsInList, cardId, numOfPositions) + + await trelloClient.updateCard({ + partialCard: { + id: cardId, + verticalPosition: newPosition, + }, + }) +} + +const _evaluateNewPosition = (cardsInList: Card[], cardIdToMove: string, numOfPositions: number): CardPosition => { + const cardIndex = cardsInList.findIndex((c) => c.id === cardIdToMove) + if (cardIndex === -1) { + throw new sdk.RuntimeError(`Card with id ${cardIdToMove} not found in the target list of cards`) + } + + const newIndex = cardIndex + numOfPositions + + if (newIndex <= 0) { + return 'top' + } + + if (newIndex >= cardsInList.length - 1) { + return 'bottom' + } + + const sibling = cardsInList[newIndex] + const otherSibling = cardsInList[newIndex + Math.sign(numOfPositions)] + + if (!sibling || !otherSibling) { + // Sanity check, should never actually be called + throw new sdk.RuntimeError('Card must have a sibling card on each side to determine new position') + } + + // This is supposed to be a float value. For reference, check the "pos" property in the "Update a Card" request + // parameters: https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-put-request + return (sibling.verticalPosition + otherSibling.verticalPosition) / 2 +} diff --git a/integrations/trello/src/index.ts b/integrations/trello/src/index.ts index 9948cceb7b0..dba6fceb516 100644 --- a/integrations/trello/src/index.ts +++ b/integrations/trello/src/index.ts @@ -1,20 +1,24 @@ -import { sentry as sentryHelpers } from '@botpress/sdk-addons' -import * as bp from '../.botpress' +import { posthogHelper } from '@botpress/common' +import { INTEGRATION_NAME, INTEGRATION_VERSION } from 'integration.definition' import { actions } from './actions' import { channels } from './channels/publisher-dispatcher' import { register, unregister } from './setup' import { handler } from './webhook-events' +import * as bp from '.botpress' -const integration = new bp.Integration({ +const integrationConfig: bp.IntegrationProps = { register, unregister, actions, channels, handler, -}) +} -export default sentryHelpers.wrapIntegration(integration, { - dsn: bp.secrets.SENTRY_DSN, - environment: bp.secrets.SENTRY_ENVIRONMENT, - release: bp.secrets.SENTRY_RELEASE, -}) +export default posthogHelper.wrapIntegration( + { + integrationName: INTEGRATION_NAME, + key: bp.secrets.POSTHOG_KEY, + integrationVersion: INTEGRATION_VERSION, + }, + integrationConfig +) diff --git a/integrations/trello/src/trello-api/index.ts b/integrations/trello/src/trello-api/index.ts new file mode 100644 index 00000000000..0f734dd61ee --- /dev/null +++ b/integrations/trello/src/trello-api/index.ts @@ -0,0 +1,2 @@ +export { TrelloClient } from './trello-client' +export * from './types' diff --git a/integrations/trello/src/trello-api/mapping/request-mapping.ts b/integrations/trello/src/trello-api/mapping/request-mapping.ts index 71397bf6867..3d93cd9943f 100644 --- a/integrations/trello/src/trello-api/mapping/request-mapping.ts +++ b/integrations/trello/src/trello-api/mapping/request-mapping.ts @@ -1,8 +1,8 @@ -import type { Card } from 'definitions/schemas' -import { UpdateCard, CreateCard } from 'trello.js/out/api/parameters' +import { Parameters } from 'trello.js' +import { UpdateCardPayload, CreateCardPayload } from '../types' export namespace RequestMapping { - export const mapUpdateCard = (card: Pick & Partial): UpdateCard => + export const mapUpdateCard = (card: UpdateCardPayload): Parameters.UpdateCard => _keepOnlySetProperties({ id: card.id, name: card.name, @@ -16,7 +16,7 @@ export namespace RequestMapping { idMembers: card.memberIds, }) - export const mapCreateCard = (card: Pick & Partial): CreateCard => + export const mapCreateCard = (card: CreateCardPayload): Parameters.CreateCard => _keepOnlySetProperties({ name: card.name, desc: card.description, @@ -24,6 +24,8 @@ export namespace RequestMapping { due: card.dueDate, idLabels: card.labelIds, idMembers: card.memberIds, + pos: card.verticalPosition, + dueComplete: card.isCompleted, }) } diff --git a/integrations/trello/src/trello-api/trello-client.ts b/integrations/trello/src/trello-api/trello-client.ts index 1830a722ba2..e4c5c3d818f 100644 --- a/integrations/trello/src/trello-api/trello-client.ts +++ b/integrations/trello/src/trello-api/trello-client.ts @@ -11,6 +11,7 @@ import { import { TrelloClient as TrelloJs, type Models as TrelloJsModels } from 'trello.js' import { RequestMapping } from './mapping/request-mapping' import { ResponseMapping } from './mapping/response-mapping' +import { UpdateCardPayload, CreateCardPayload } from './types' import * as bp from '.botpress' const _useHandleCaughtError = (message: string) => { @@ -90,11 +91,7 @@ export class TrelloClient { return ResponseMapping.mapCard(card) } - public async createCard({ - card, - }: { - card: Pick & Partial - }): Promise { + public async createCard({ card }: { card: CreateCardPayload }): Promise { const newCard = await this._trelloJs.cards .createCard(RequestMapping.mapCreateCard(card)) .catch(_useHandleCaughtError('Failed to create card')) @@ -102,7 +99,7 @@ export class TrelloClient { return ResponseMapping.mapCard(newCard) } - public async updateCard({ partialCard }: { partialCard: Pick & Partial }): Promise { + public async updateCard({ partialCard }: { partialCard: UpdateCardPayload }): Promise { const updatedCard = await this._trelloJs.cards .updateCard(RequestMapping.mapUpdateCard(partialCard)) .catch(_useHandleCaughtError('Failed to update card')) @@ -110,6 +107,17 @@ export class TrelloClient { return ResponseMapping.mapCard(updatedCard) } + /** Hard deletes a Trello card. + * + * @remark For soft deletion use "updateCard" with "isClosed" as true */ + public async deleteCard(cardId: Card['id']): Promise { + await this._trelloJs.cards + .deleteCard({ + id: cardId, + }) + .catch(_useHandleCaughtError('Failed to delete card')) + } + public async getListById({ listId }: { listId: List['id'] }): Promise { const list = await this._trelloJs.lists .getList({ diff --git a/integrations/trello/src/trello-api/types.ts b/integrations/trello/src/trello-api/types.ts new file mode 100644 index 00000000000..2553b9b75e3 --- /dev/null +++ b/integrations/trello/src/trello-api/types.ts @@ -0,0 +1,12 @@ +import type { Card } from 'definitions/schemas' +import { Merge } from 'src/types' + +export type CardPosition = number | 'top' | 'bottom' + +export type CreateCardPayload = Pick & Omit, 'id' | 'isClosed'> +export type UpdateCardPayload = Merge< + Pick & Partial, + { + verticalPosition?: CardPosition + } +> diff --git a/integrations/trello/src/types.ts b/integrations/trello/src/types.ts new file mode 100644 index 00000000000..a2fa8f98ee2 --- /dev/null +++ b/integrations/trello/src/types.ts @@ -0,0 +1,3 @@ +export type Merge = Omit & B + +export type Result = { success: true; data: T } | { success: false; error: E } diff --git a/integrations/trello/src/utils.ts b/integrations/trello/src/utils.ts new file mode 100644 index 00000000000..06831e838e7 --- /dev/null +++ b/integrations/trello/src/utils.ts @@ -0,0 +1,26 @@ +import { Result } from './types' + +export const safeParseJson = (json: string): Result => { + try { + return { + success: true, + data: JSON.parse(json), + } as const + } catch (thrown: unknown) { + return { + success: false, + error: thrown instanceof Error ? thrown : new Error(String(thrown)), + } as const + } +} + +export const safeParseRequestBody = (body: string | undefined): Result => { + if (!body?.trim()) { + return { + success: false, + error: new Error('Request body is empty'), + } + } + + return safeParseJson(body) +} diff --git a/integrations/trello/src/webhook-events/channel-handlers/comment-channel-handler.ts b/integrations/trello/src/webhook-events/channel-handlers/comment-channel-handler.ts new file mode 100644 index 00000000000..6c0c57aa799 --- /dev/null +++ b/integrations/trello/src/webhook-events/channel-handlers/comment-channel-handler.ts @@ -0,0 +1,74 @@ +import { CommentAddedWebhook } from '../schemas/card-comment-webhook-schemas' +import * as bp from '.botpress' + +type Conversation = Awaited>['conversation'] +type User = Awaited>['user'] + +export const processInboundCommentChannelMessage = async ( + client: bp.HandlerProps['client'], + webhookEvent: CommentAddedWebhook +): Promise => { + const conversation = await _getOrCreateConversation(client, webhookEvent.data) + const user = await _getOrCreateUser(client, webhookEvent.memberCreator) + + const comment = _extractCommentData(webhookEvent) + if (_checkIfMessageWasSentByOurselvesAndShouldBeIgnored(conversation, comment)) { + return + } + + await _createMessage(client, conversation, user, comment) +} + +const _extractCommentData = (event: CommentAddedWebhook) => + ({ + id: event.id, + text: event.data.text, + }) as const +type CardComment = ReturnType + +const _getOrCreateConversation = async (client: bp.HandlerProps['client'], eventData: CommentAddedWebhook['data']) => { + const { conversation } = await client.getOrCreateConversation({ + channel: 'cardComments', + tags: { + listId: eventData.list.id, + listName: eventData.list.name, + cardId: eventData.card.id, + cardName: eventData.card.name, + }, + }) + + return conversation +} + +const _getOrCreateUser = async ( + client: bp.HandlerProps['client'], + memberCreator: CommentAddedWebhook['memberCreator'] +) => { + const { user } = await client.getOrCreateUser({ + tags: { + userId: memberCreator.id, + }, + name: memberCreator.fullName, + pictureUrl: `${memberCreator.avatarUrl}/50.png`, + }) + + return user +} + +const _checkIfMessageWasSentByOurselvesAndShouldBeIgnored = (conversation: Conversation, comment: CardComment) => + conversation.tags.lastCommentId === comment.id + +const _createMessage = async ( + client: bp.HandlerProps['client'], + conversation: Conversation, + user: User, + comment: CardComment +) => { + await client.createMessage({ + conversationId: conversation.id, + userId: user.id, + type: 'text', + payload: { text: comment.text }, + tags: { commentId: comment.id }, + }) +} diff --git a/integrations/trello/src/webhook-events/event-handlers/card-attachment-event-handlers.ts b/integrations/trello/src/webhook-events/event-handlers/card-attachment-event-handlers.ts new file mode 100644 index 00000000000..2758b9c9370 --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/card-attachment-event-handlers.ts @@ -0,0 +1,37 @@ +import { TrelloEventType } from 'definitions/events' +import { CardAttachmentAddedWebhook, CardAttachmentRemovedWebhook } from '../schemas/card-attachment-webhook-schemas' +import { extractCommonEventData, extractIdAndName } from './helpers' +import * as bp from '.botpress' + +export const handleAttachmentAddedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.ATTACHMENT_ADDED_TO_CARD, + webhookEvent: CardAttachmentAddedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + list: extractIdAndName(webhookEvent.data.list), + card: extractIdAndName(webhookEvent.data.card), + attachment: webhookEvent.data.attachment, + }, + }) +} + +export const handleAttachmentRemovedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.ATTACHMENT_REMOVED_FROM_CARD, + webhookEvent: CardAttachmentRemovedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + attachment: webhookEvent.data.attachment, + }, + }) +} diff --git a/integrations/trello/src/webhook-events/event-handlers/card-comment-event-handlers.ts b/integrations/trello/src/webhook-events/event-handlers/card-comment-event-handlers.ts new file mode 100644 index 00000000000..c98f8fce25c --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/card-comment-event-handlers.ts @@ -0,0 +1,74 @@ +import { TrelloEventType } from 'definitions/events' +import { processInboundCommentChannelMessage } from '../channel-handlers/comment-channel-handler' +import { + CommentAddedWebhook, + CommentDeletedWebhook, + CommentUpdatedWebhook, +} from '../schemas/card-comment-webhook-schemas' +import { extractCommonEventData, extractIdAndName } from './helpers' +import * as bp from '.botpress' + +export const handleCommentAddedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CARD_COMMENT_CREATED, + webhookEvent: CommentAddedWebhook +) => { + const result = await Promise.allSettled([ + processInboundCommentChannelMessage(props.client, webhookEvent), + props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + list: extractIdAndName(webhookEvent.data.list), + card: extractIdAndName(webhookEvent.data.card), + comment: { + id: webhookEvent.id, + text: webhookEvent.data.text, + }, + }, + }), + ]) + + return result[1].status === 'fulfilled' ? result[1].value : null +} + +export const handleCommentUpdatedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CARD_COMMENT_UPDATED, + webhookEvent: CommentUpdatedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + comment: { + id: webhookEvent.data.action.id, + text: webhookEvent.data.action.text, + }, + old: { + text: webhookEvent.data.old.text, + }, + }, + }) +} + +export const handleCommentDeletedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CARD_COMMENT_DELETED, + webhookEvent: CommentDeletedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + comment: { + id: webhookEvent.data.action.id, + }, + }, + }) +} diff --git a/integrations/trello/src/webhook-events/event-handlers/card-event-handlers.ts b/integrations/trello/src/webhook-events/event-handlers/card-event-handlers.ts new file mode 100644 index 00000000000..cf9dcc7f297 --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/card-event-handlers.ts @@ -0,0 +1,78 @@ +import { TrelloEventType } from 'definitions/events' +import { + CardCreatedWebhook, + CardDeletedWebhook, + CardUpdatedWebhook, + CardVotesUpdatedWebhook, +} from '../schemas/card-webhook-schemas' +import { extractCommonEventData, extractIdAndName, extractIdAndNameIfExists } from './helpers' +import * as bp from '.botpress' + +export const handleCardCreatedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CARD_CREATED, + webhookEvent: CardCreatedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + list: extractIdAndName(webhookEvent.data.list), + card: extractIdAndName(webhookEvent.data.card), + }, + }) +} + +export const handleCardUpdatedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CARD_UPDATED, + webhookEvent: CardUpdatedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: webhookEvent.data.card, + old: webhookEvent.data.old, + list: extractIdAndNameIfExists(webhookEvent.data.list), + listBefore: extractIdAndNameIfExists(webhookEvent.data.listBefore), + listAfter: extractIdAndNameIfExists(webhookEvent.data.listAfter), + }, + }) +} + +export const handleCardDeletedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CARD_DELETED, + webhookEvent: CardDeletedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + list: extractIdAndName(webhookEvent.data.list), + card: { + id: webhookEvent.data.card.id, + }, + }, + }) +} + +export const handleCardVotesUpdatedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CARD_VOTES_UPDATED, + webhookEvent: CardVotesUpdatedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + voted: webhookEvent.data.voted, + }, + }) +} diff --git a/integrations/trello/src/webhook-events/event-handlers/card-label-event-handlers.ts b/integrations/trello/src/webhook-events/event-handlers/card-label-event-handlers.ts new file mode 100644 index 00000000000..8c643227de4 --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/card-label-event-handlers.ts @@ -0,0 +1,36 @@ +import { TrelloEventType } from 'definitions/events' +import { CardLabelAddedWebhook, CardLabelRemovedWebhook } from '../schemas/card-label-webhook-schemas' +import { extractCommonEventData, extractIdAndName } from './helpers' +import * as bp from '.botpress' + +const _handleLabelChangedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.LABEL_ADDED_TO_CARD | TrelloEventType.LABEL_REMOVED_FROM_CARD, + webhookEvent: CardLabelAddedWebhook | CardLabelRemovedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + label: webhookEvent.data.label, + }, + }) +} + +export const handleLabelAddedToCardEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.LABEL_ADDED_TO_CARD, + webhookEvent: CardLabelAddedWebhook +) => { + return await _handleLabelChangedEvent(props, eventType, webhookEvent) +} + +export const handleLabelRemovedFromCardEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.LABEL_REMOVED_FROM_CARD, + webhookEvent: CardLabelRemovedWebhook +) => { + return await _handleLabelChangedEvent(props, eventType, webhookEvent) +} diff --git a/integrations/trello/src/webhook-events/event-handlers/checklist-event-handlers.ts b/integrations/trello/src/webhook-events/event-handlers/checklist-event-handlers.ts new file mode 100644 index 00000000000..c92c971837c --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/checklist-event-handlers.ts @@ -0,0 +1,110 @@ +import { TrelloEventType } from 'definitions/events' +import { + ChecklistAddedToCardWebhook, + ChecklistItemCreatedWebhook, + ChecklistItemDeletedWebhook, + ChecklistItemStatusUpdatedWebhook, + ChecklistItemUpdatedWebhook, +} from '../schemas/checklist-webhook-schemas' +import { extractCommonEventData, extractIdAndName } from './helpers' +import * as bp from '.botpress' + +const _extractCommonChecklistItemPayload = ( + webhookEvent: ChecklistItemCreatedWebhook | ChecklistItemDeletedWebhook | ChecklistItemStatusUpdatedWebhook +) => ({ + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + checklist: webhookEvent.data.checklist, + checklistItem: { + id: webhookEvent.data.checkItem.id, + name: webhookEvent.data.checkItem.name, + isCompleted: webhookEvent.data.checkItem.state === 'complete', + textData: webhookEvent.data.checkItem.textData, + }, +}) + +export const handleChecklistAddedToCardEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CHECKLIST_ADDED_TO_CARD, + webhookEvent: ChecklistAddedToCardWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + checklist: webhookEvent.data.checklist, + }, + }) +} + +export const handleChecklistItemCreatedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CHECKLIST_ITEM_CREATED, + webhookEvent: ChecklistItemCreatedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: _extractCommonChecklistItemPayload(webhookEvent), + }) +} + +const _mapOldChecklistItemData = (oldData: ChecklistItemUpdatedWebhook['data']['old']) => { + const { name, state, textData, dueReminder, due } = oldData + return { + name, + isCompleted: state !== undefined ? state === 'complete' : undefined, + textData, + dueDate: due, + dueDateReminder: dueReminder, + } +} + +export const handleChecklistItemUpdatedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CHECKLIST_ITEM_UPDATED, + webhookEvent: ChecklistItemUpdatedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + checklist: webhookEvent.data.checklist, + checklistItem: { + id: webhookEvent.data.checkItem.id, + name: webhookEvent.data.checkItem.name, + isCompleted: webhookEvent.data.checkItem.state === 'complete', + textData: webhookEvent.data.checkItem.textData, + dueDate: webhookEvent.data.checkItem.due, + dueDateReminder: webhookEvent.data.checkItem.dueReminder, + }, + old: _mapOldChecklistItemData(webhookEvent.data.old), + }, + }) +} + +export const handleChecklistItemDeletedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CHECKLIST_ITEM_DELETED, + webhookEvent: ChecklistItemDeletedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: _extractCommonChecklistItemPayload(webhookEvent), + }) +} + +export const handleChecklistItemStatusUpdatedEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.CHECKLIST_ITEM_STATUS_UPDATED, + webhookEvent: ChecklistItemStatusUpdatedWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: _extractCommonChecklistItemPayload(webhookEvent), + }) +} diff --git a/integrations/trello/src/webhook-events/event-handlers/helpers.ts b/integrations/trello/src/webhook-events/event-handlers/helpers.ts new file mode 100644 index 00000000000..9f921fd0c7b --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/helpers.ts @@ -0,0 +1,36 @@ +import { CommonEventData } from 'definitions/events' +import { TrelloWebhook } from '../schemas/common' + +export const extractIdAndName = (obj: { id: string; name: string }) => { + return { + id: obj.id, + name: obj.name, + } +} + +export const extractIdAndNameIfExists = (obj: { id: string; name: string } | undefined) => { + return obj ? extractIdAndName(obj) : undefined +} + +const _extractActorData = (webhookEvent: TrelloWebhook): CommonEventData['actor'] => { + if (webhookEvent.appCreator !== null) { + return { + id: webhookEvent.appCreator.id, + type: 'app' as const, + } + } else { + return { + id: webhookEvent.memberCreator.id, + type: 'member' as const, + name: webhookEvent.memberCreator.fullName, + } + } +} + +export const extractCommonEventData = (webhookEvent: TrelloWebhook): CommonEventData => { + return { + eventId: webhookEvent.id, + actor: _extractActorData(webhookEvent), + dateCreated: webhookEvent.date.toISOString(), + } +} diff --git a/integrations/trello/src/webhook-events/event-handlers/index.ts b/integrations/trello/src/webhook-events/event-handlers/index.ts new file mode 100644 index 00000000000..ecf7247dffa --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/index.ts @@ -0,0 +1,53 @@ +import { TrelloEventType } from 'definitions/events' +import { WebhookEventPayload } from '../schemas' +import * as attachmentHandlers from './card-attachment-event-handlers' +import * as commentHandlers from './card-comment-event-handlers' +import * as cardHandlers from './card-event-handlers' +import * as labelHandlers from './card-label-event-handlers' +import * as checklistHandlers from './checklist-event-handlers' +import * as memberHandlers from './member-event-handlers' +import * as bp from '.botpress' + +export const dispatchIntegrationEvent = async (props: bp.HandlerProps, eventPayload: WebhookEventPayload) => { + const eventType = eventPayload.action.type + switch (eventType) { + case TrelloEventType.CARD_CREATED: + return cardHandlers.handleCardCreatedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CARD_UPDATED: + return cardHandlers.handleCardUpdatedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CARD_DELETED: + return cardHandlers.handleCardDeletedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CARD_VOTES_UPDATED: + return cardHandlers.handleCardVotesUpdatedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CARD_COMMENT_CREATED: + return commentHandlers.handleCommentAddedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CARD_COMMENT_UPDATED: + return commentHandlers.handleCommentUpdatedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CARD_COMMENT_DELETED: + return commentHandlers.handleCommentDeletedEvent(props, eventType, eventPayload.action) + case TrelloEventType.LABEL_ADDED_TO_CARD: + return labelHandlers.handleLabelAddedToCardEvent(props, eventType, eventPayload.action) + case TrelloEventType.LABEL_REMOVED_FROM_CARD: + return labelHandlers.handleLabelRemovedFromCardEvent(props, eventType, eventPayload.action) + case TrelloEventType.ATTACHMENT_ADDED_TO_CARD: + return attachmentHandlers.handleAttachmentAddedEvent(props, eventType, eventPayload.action) + case TrelloEventType.ATTACHMENT_REMOVED_FROM_CARD: + return attachmentHandlers.handleAttachmentRemovedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CHECKLIST_ADDED_TO_CARD: + return checklistHandlers.handleChecklistAddedToCardEvent(props, eventType, eventPayload.action) + case TrelloEventType.CHECKLIST_ITEM_CREATED: + return checklistHandlers.handleChecklistItemCreatedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CHECKLIST_ITEM_UPDATED: + return checklistHandlers.handleChecklistItemUpdatedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CHECKLIST_ITEM_DELETED: + return checklistHandlers.handleChecklistItemDeletedEvent(props, eventType, eventPayload.action) + case TrelloEventType.CHECKLIST_ITEM_STATUS_UPDATED: + return checklistHandlers.handleChecklistItemStatusUpdatedEvent(props, eventType, eventPayload.action) + case TrelloEventType.MEMBER_ADDED_TO_CARD: + return memberHandlers.handleMemberAddedToCardEvent(props, eventType, eventPayload.action) + case TrelloEventType.MEMBER_REMOVED_FROM_CARD: + return memberHandlers.handleMemberRemovedFromCardEvent(props, eventType, eventPayload.action) + default: + return null + } +} diff --git a/integrations/trello/src/webhook-events/event-handlers/member-event-handlers.ts b/integrations/trello/src/webhook-events/event-handlers/member-event-handlers.ts new file mode 100644 index 00000000000..9d96fc69132 --- /dev/null +++ b/integrations/trello/src/webhook-events/event-handlers/member-event-handlers.ts @@ -0,0 +1,39 @@ +import { TrelloEventType } from 'definitions/events' +import { MemberAddedToCardWebhook, MemberRemovedFromCardWebhook } from '../schemas/member-webhook-schemas' +import { extractCommonEventData, extractIdAndName } from './helpers' +import * as bp from '.botpress' + +export const handleMemberAddedToCardEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.MEMBER_ADDED_TO_CARD, + webhookEvent: MemberAddedToCardWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + member: extractIdAndName(webhookEvent.data.member), + }, + }) +} + +export const handleMemberRemovedFromCardEvent = async ( + props: bp.HandlerProps, + eventType: TrelloEventType.MEMBER_REMOVED_FROM_CARD, + webhookEvent: MemberRemovedFromCardWebhook +) => { + return await props.client.createEvent({ + type: eventType, + payload: { + ...extractCommonEventData(webhookEvent), + board: extractIdAndName(webhookEvent.data.board), + card: extractIdAndName(webhookEvent.data.card), + member: { + ...extractIdAndName(webhookEvent.data.member), + deactivated: webhookEvent.data.deactivated, + }, + }, + }) +} diff --git a/integrations/trello/src/webhook-events/handler-dispatcher.ts b/integrations/trello/src/webhook-events/handler-dispatcher.ts index 11c31cd702c..456c22dedb7 100644 --- a/integrations/trello/src/webhook-events/handler-dispatcher.ts +++ b/integrations/trello/src/webhook-events/handler-dispatcher.ts @@ -1,98 +1,67 @@ -import { default as sdk, z } from '@botpress/sdk' -import { - events, - type AllSupportedEvents, - commentCardEventSchema, - type GenericWebhookEvent, - genericWebhookEventSchema, - TRELLO_EVENTS, -} from 'definitions/events' -import { CardCommentHandler } from './handlers/card-comment' +import { TrelloEventType } from 'definitions/events' +import { Result } from '../types' +import { safeParseRequestBody } from '../utils' +import { dispatchIntegrationEvent } from './event-handlers' +import { fallbackEventPayloadSchema, WebhookEventPayload, webhookEventPayloadSchema } from './schemas' import * as bp from '.botpress' -export const handler = async ({ req, client, ctx }: bp.HandlerProps) => { - if (!_isBodyPresent({ req })) { +export const handler = async (props: bp.HandlerProps): Promise => { + const payloadResult = _parseWebhookPayload(props) + if (!payloadResult.success) { + const { error } = payloadResult + props.logger.forBot().error(error.message, error) return } - const parsedWebhookEvent = _parseWebhookEvent({ req }) - await _ensureWebhookIsAuthenticated({ parsedWebhookEvent, ctx, client }) - await _handleWebhookEvent({ parsedWebhookEvent, client }) -} - -const _isBodyPresent = ({ req }: { req: sdk.Request }) => !!req.body?.length - -const _parseWebhookEvent = ({ req }: { req: sdk.Request }) => { - const body = JSON.parse(req.body as string) - const { success, error, data } = genericWebhookEventSchema.passthrough().safeParse(body) - - if (!success) { - throw new sdk.RuntimeError('Invalid webhook event body', error) + if (!(await _verifyWebhookSignature(props, payloadResult.data))) { + props.logger.forBot().error('The provided webhook payload failed its signature validation') + return } - return { ...data, action: { ...data.action, type: data.action.type as AllSupportedEvents } } + await dispatchIntegrationEvent(props, payloadResult.data) } -const _ensureWebhookIsAuthenticated = async ({ - parsedWebhookEvent, - ctx, - client, -}: { - parsedWebhookEvent: GenericWebhookEvent - ctx: bp.Context - client: bp.Client -}) => { - const { state } = await client.getState({ - type: 'integration', - name: 'webhookState', - id: ctx.integrationId, - }) +const _isSupportedEventType = (type: string) => Object.values(TrelloEventType).includes(type) - if (parsedWebhookEvent?.webhook.id !== state.payload.trelloWebhookId) { - throw new sdk.RuntimeError('Webhook request is not properly authenticated') - } -} +const _parseWebhookPayload = (props: bp.HandlerProps): Result => { + const result = safeParseRequestBody(props.req.body) + if (!result.success) return result -const _handleWebhookEvent = async (props: { parsedWebhookEvent: GenericWebhookEvent; client: bp.Client }) => { - await Promise.allSettled([_handleCardComments(props), _publishEventToBotpress(props)]) -} + const payloadResult = webhookEventPayloadSchema.safeParse(result.data) + if (payloadResult.success) return payloadResult -const _handleCardComments = async ({ - parsedWebhookEvent, - client, -}: { - parsedWebhookEvent: GenericWebhookEvent - client: bp.Client -}) => { - if (!parsedWebhookEvent || parsedWebhookEvent.action.type !== TRELLO_EVENTS.commentCard) { - return + // Checks for payloads that don't match supported events, or if a supported + // event has a data structure that doesn't match the configured event schema + const fallbackPayloadResult = fallbackEventPayloadSchema.safeParse(result.data) + if (!fallbackPayloadResult.success) { + return { + success: false, + error: new Error(`The webhook payload has an unexpected format -> ${fallbackPayloadResult.error.message}`), + } } - const cardCreationEvent = commentCardEventSchema.parse(parsedWebhookEvent) - await CardCommentHandler.handleEvent(client, cardCreationEvent) -} + const eventType = fallbackPayloadResult.data.action.type + if (_isSupportedEventType(eventType)) { + return { + success: false, + error: new Error( + `The event data for the supported event type '${eventType}' has an unexpected format -> ${payloadResult.error.message}` + ), + } + } -const _publishEventToBotpress = async ({ - parsedWebhookEvent, - client, -}: { - parsedWebhookEvent: GenericWebhookEvent - client: bp.Client -}) => { - if (!parsedWebhookEvent || !Reflect.has(TRELLO_EVENTS, parsedWebhookEvent.action.type)) { - return + return { + success: false, + error: new Error(`Unsupported Trello event type: '${eventType}'`), } +} - const eventSchema = genericWebhookEventSchema.merge( - z.object({ - action: genericWebhookEventSchema.shape.action.merge( - z.object({ - data: events[parsedWebhookEvent.action.type].schema, - }) - ), - }) - ) - const validatedData = eventSchema.passthrough().parse(parsedWebhookEvent).action.data +const _verifyWebhookSignature = async ({ client, ctx }: bp.HandlerProps, eventPayload: WebhookEventPayload) => { + const { state } = await client.getState({ + type: 'integration', + name: 'webhook', + id: ctx.integrationId, + }) - await client.createEvent({ type: parsedWebhookEvent.action.type, payload: validatedData }) + return eventPayload.webhook.id === state.payload.trelloWebhookId } diff --git a/integrations/trello/src/webhook-events/handlers/card-comment.ts b/integrations/trello/src/webhook-events/handlers/card-comment.ts deleted file mode 100644 index 0e1347c5e4f..00000000000 --- a/integrations/trello/src/webhook-events/handlers/card-comment.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { type CommentCardEvent } from 'definitions/events' -import * as bp from '.botpress' - -type TrelloMessageData = { - cardId: string - cardName: string - listId: string - listName: string - messageId: string - messageText: string - messageAuthorId: string - messageAuthorName: string - messageAuthorAvatar: string -} - -export namespace CardCommentHandler { - export const handleEvent = async ( - client: bp.HandlerProps['client'], - cardCommentEvent: CommentCardEvent - ): Promise => { - const messageData = _extractMessageDataFromEvent(cardCommentEvent) - const conversation = await _getOrCreateConversation(client, messageData) - const user = await _getOrCreateUser(client, messageData) - - if (_checkIfMessageWasSentByOurselvesAndShouldBeIgnored(conversation, messageData)) { - return - } - - await _createMessage(client, user, conversation, messageData) - } - - const _extractMessageDataFromEvent = (cardCommentEvent: CommentCardEvent): TrelloMessageData => - ({ - cardId: cardCommentEvent.action.data.card.id, - cardName: cardCommentEvent.action.data.card.name, - listId: cardCommentEvent.action.data.list?.id ?? '', - listName: cardCommentEvent.action.data.list?.name ?? '', - messageId: cardCommentEvent.action.id, - messageText: cardCommentEvent.action.data.text, - messageAuthorId: cardCommentEvent.action.memberCreator.id, - messageAuthorName: cardCommentEvent.action.memberCreator.fullName, - messageAuthorAvatar: cardCommentEvent.action.memberCreator.avatarUrl + '/50.png', - }) as const satisfies TrelloMessageData - - const _getOrCreateConversation = async (client: bp.HandlerProps['client'], messageData: TrelloMessageData) => { - const { conversation } = await client.getOrCreateConversation({ - channel: 'cardComments', - tags: { - cardId: messageData.cardId, - cardName: messageData.cardName, - listId: messageData.listId, - listName: messageData.listName, - }, - }) - - return conversation - } - - const _getOrCreateUser = async (client: bp.HandlerProps['client'], messageData: TrelloMessageData) => { - const { user } = await client.getOrCreateUser({ - tags: { - userId: messageData.messageAuthorId, - }, - name: messageData.messageAuthorName, - pictureUrl: messageData.messageAuthorAvatar, - }) - - return user - } - - const _checkIfMessageWasSentByOurselvesAndShouldBeIgnored = ( - conversation: Awaited>['conversation'], - messageData: TrelloMessageData - ) => conversation.tags.lastCommentId === messageData.messageId - - const _createMessage = async ( - client: bp.HandlerProps['client'], - user: Awaited>['user'], - conversation: Awaited>['conversation'], - messageData: TrelloMessageData - ) => { - await client.createMessage({ - tags: { - commentId: messageData.messageId, - }, - type: 'text', - userId: user.id, - conversationId: conversation.id, - payload: { text: messageData.messageText }, - }) - } -} diff --git a/integrations/trello/src/webhook-events/schemas/card-attachment-webhook-schemas.ts b/integrations/trello/src/webhook-events/schemas/card-attachment-webhook-schemas.ts new file mode 100644 index 00000000000..75111f49d13 --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/card-attachment-webhook-schemas.ts @@ -0,0 +1,35 @@ +import { z } from '@botpress/sdk' +import { TrelloEventType } from 'definitions/events' +import { pickIdAndName } from 'definitions/events/common' +import { boardSchema, cardSchema, listSchema, trelloIdSchema } from 'definitions/schemas' +import { trelloWebhookSchema } from './common' + +export const cardAttachmentAddedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.ATTACHMENT_ADDED_TO_CARD), + data: z.object({ + board: pickIdAndName(boardSchema), + list: pickIdAndName(listSchema), + card: pickIdAndName(cardSchema), + attachment: z.object({ + id: trelloIdSchema, + name: z.string(), + url: z.string().url(), + previewUrl: z.string().url().optional(), + previewUrl2x: z.string().url().optional(), + }), + }), +}) +export type CardAttachmentAddedWebhook = z.infer + +export const cardAttachmentRemovedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.ATTACHMENT_REMOVED_FROM_CARD), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + attachment: z.object({ + id: trelloIdSchema, + name: z.string(), + }), + }), +}) +export type CardAttachmentRemovedWebhook = z.infer diff --git a/integrations/trello/src/webhook-events/schemas/card-comment-webhook-schemas.ts b/integrations/trello/src/webhook-events/schemas/card-comment-webhook-schemas.ts new file mode 100644 index 00000000000..df9864b8479 --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/card-comment-webhook-schemas.ts @@ -0,0 +1,48 @@ +/** NOTE: The "brands" are only for documentation purposes, since Trello + * is very inconsistent with the data structures for the comment events */ +import { z } from '@botpress/sdk' +import { TrelloEventType } from 'definitions/events' +import { pickIdAndName } from 'definitions/events/common' +import { boardSchema, cardSchema, listSchema, trelloIdSchema } from 'definitions/schemas' +import { trelloWebhookSchema } from './common' + +export const commentAddedWebhookSchema = trelloWebhookSchema.extend({ + /** @remark This is only the comment ID for the comment added event */ + id: trelloIdSchema.brand('EventID').brand('CommentID'), + type: z.literal(TrelloEventType.CARD_COMMENT_CREATED), + data: z.object({ + board: pickIdAndName(boardSchema), + list: pickIdAndName(listSchema), + card: pickIdAndName(cardSchema), + text: z.string().brand('CommentText'), + }), +}) +export type CommentAddedWebhook = z.infer + +export const commentUpdatedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CARD_COMMENT_UPDATED), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + action: z.object({ + id: trelloIdSchema.brand('CommentID'), + text: z.string().brand('NewCommentText'), + }), + old: z.object({ + text: z.string().brand('OldCommentText'), + }), + }), +}) +export type CommentUpdatedWebhook = z.infer + +export const commentDeletedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CARD_COMMENT_DELETED), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + action: z.object({ + id: trelloIdSchema.brand('CommentID'), + }), + }), +}) +export type CommentDeletedWebhook = z.infer diff --git a/integrations/trello/src/webhook-events/schemas/card-label-webhook-schemas.ts b/integrations/trello/src/webhook-events/schemas/card-label-webhook-schemas.ts new file mode 100644 index 00000000000..d0cda2b6544 --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/card-label-webhook-schemas.ts @@ -0,0 +1,26 @@ +import { z } from '@botpress/sdk' +import { TrelloEventType } from 'definitions/events' +import { labelSchema } from 'definitions/events/card-label-events' +import { pickIdAndName } from 'definitions/events/common' +import { boardSchema, cardSchema } from 'definitions/schemas' +import { trelloWebhookSchema } from './common' + +export const cardLabelAddedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.LABEL_ADDED_TO_CARD), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + label: labelSchema, + }), +}) +export type CardLabelAddedWebhook = z.infer + +export const cardLabelRemovedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.LABEL_REMOVED_FROM_CARD), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + label: labelSchema, + }), +}) +export type CardLabelRemovedWebhook = z.infer diff --git a/integrations/trello/src/webhook-events/schemas/card-webhook-schemas.ts b/integrations/trello/src/webhook-events/schemas/card-webhook-schemas.ts new file mode 100644 index 00000000000..390bedbd1ad --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/card-webhook-schemas.ts @@ -0,0 +1,69 @@ +import { z } from '@botpress/sdk' +import { dueReminderSchema, pickIdAndName, TrelloEventType } from 'definitions/events/common' +import { boardSchema, cardSchema, listSchema, trelloIdSchema } from 'definitions/schemas' +import { trelloWebhookSchema } from './common' + +const _basicListSchema = pickIdAndName(listSchema) +const _basicCardSchema = pickIdAndName(cardSchema) + +export const cardCreatedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CARD_CREATED), + data: z.object({ + board: pickIdAndName(boardSchema), + list: _basicListSchema, + card: _basicCardSchema, + }), +}) +export type CardCreatedWebhook = z.infer + +const _baseCardUpdateDataSchema = _basicCardSchema + .extend({ + // These properties are only included if their values were modified + desc: z.string(), + idList: trelloIdSchema, + idLabels: z.array(trelloIdSchema), + pos: z.number(), + start: z.string().datetime().nullable(), + due: z.string().datetime().nullable(), + dueReminder: dueReminderSchema.nullable(), + dueComplete: z.boolean(), + closed: z.boolean(), + }) + .passthrough() + .partial() + +export const cardUpdatedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CARD_UPDATED), + data: z.object({ + board: pickIdAndName(boardSchema), + // Seemed to only be excluded/optional when the card is moved between lists + list: _basicListSchema.optional(), + card: _baseCardUpdateDataSchema.required({ id: true, name: true }), + old: _baseCardUpdateDataSchema.omit({ id: true }), + // Only included if the card was moved between lists + listBefore: _basicListSchema.optional(), + // Only included if the card was moved between lists + listAfter: _basicListSchema.optional(), + }), +}) +export type CardUpdatedWebhook = z.infer + +export const cardDeletedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CARD_DELETED), + data: z.object({ + board: pickIdAndName(boardSchema), + list: _basicListSchema, + card: _basicCardSchema.pick({ id: true }), + }), +}) +export type CardDeletedWebhook = z.infer + +export const cardVotesUpdatedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CARD_VOTES_UPDATED), + data: z.object({ + board: pickIdAndName(boardSchema), + card: _basicCardSchema, + voted: z.boolean(), + }), +}) +export type CardVotesUpdatedWebhook = z.infer diff --git a/integrations/trello/src/webhook-events/schemas/checklist-webhook-schemas.ts b/integrations/trello/src/webhook-events/schemas/checklist-webhook-schemas.ts new file mode 100644 index 00000000000..66228c98a43 --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/checklist-webhook-schemas.ts @@ -0,0 +1,80 @@ +import { z } from '@botpress/sdk' +import { TrelloEventType } from 'definitions/events' +import { checklistSchema } from 'definitions/events/checklist-events' +import { dueReminderSchema, pickIdAndName } from 'definitions/events/common' +import { boardSchema, cardSchema, trelloIdSchema } from 'definitions/schemas' +import { trelloWebhookSchema } from './common' + +const _checklistItemCompletionStateSchema = z.union([z.literal('complete'), z.literal('incomplete')]) + +const _basicChecklistItemSchema = z.object({ + id: trelloIdSchema, + name: z.string(), + state: _checklistItemCompletionStateSchema, + textData: z.object({ + emoji: z.object({}), + }), +}) + +export const checklistAddedToCardWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CHECKLIST_ADDED_TO_CARD), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + checklist: checklistSchema, + }), +}) +export type ChecklistAddedToCardWebhook = z.infer + +export const checklistItemCreatedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CHECKLIST_ITEM_CREATED), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + checklist: checklistSchema, + checkItem: _basicChecklistItemSchema, + }), +}) +export type ChecklistItemCreatedWebhook = z.infer + +export const checklistItemUpdatedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CHECKLIST_ITEM_UPDATED), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + checklist: checklistSchema, + checkItem: _basicChecklistItemSchema.extend({ + dueReminder: dueReminderSchema.optional(), + // Technically optional, if I include the "updateCheckItemDue" event type. Otherwise, it isn't included in "CHECKLIST_ITEM_UPDATED" event + due: z.string().datetime().optional(), + }), + old: _basicChecklistItemSchema.omit({ id: true }).partial().extend({ + dueReminder: dueReminderSchema.optional(), + // Technically optional, if I include the "updateCheckItemDue" event type. Otherwise, it isn't included in "CHECKLIST_ITEM_UPDATED" event + due: z.string().datetime().optional(), + }), + }), +}) +export type ChecklistItemUpdatedWebhook = z.infer + +export const checklistItemDeletedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CHECKLIST_ITEM_DELETED), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + checklist: checklistSchema, + checkItem: _basicChecklistItemSchema, + }), +}) +export type ChecklistItemDeletedWebhook = z.infer + +export const checklistItemStatusUpdatedWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.CHECKLIST_ITEM_STATUS_UPDATED), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + checklist: checklistSchema, + checkItem: _basicChecklistItemSchema, + }), +}) +export type ChecklistItemStatusUpdatedWebhook = z.infer diff --git a/integrations/trello/src/webhook-events/schemas/common.ts b/integrations/trello/src/webhook-events/schemas/common.ts new file mode 100644 index 00000000000..33261363bcf --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/common.ts @@ -0,0 +1,41 @@ +import { z } from '@botpress/sdk' +import { TrelloEventType } from 'definitions/events' +import { trelloIdSchema } from 'definitions/schemas' + +export const trelloWebhookSchema = z.object({ + /** Action ID (aka Event ID) */ + id: trelloIdSchema, + /** Event Triggered Date */ + date: z.coerce.date(), + type: z.nativeEnum(TrelloEventType), + data: z.any(), + /** The Trello app that triggered the event. (e.g. Via http request using an API key & token) + * + * @remark This field is `null` when the event was not triggered by an app. */ + appCreator: z + .object({ + /** Some internal Trello identifier for the app that triggered the event. + * + * @remark At the time of writing (2026-01-15), this field cannot be linked back to any + * specific API key or token, as Trello does not expose an endpoint for that purpose. + * Also, through testing each of the endpoints, I have not been able to find it within + * other endpoint responses. */ + id: trelloIdSchema, + }) + .nullable(), + /** Member who initiated the action, or the member + * who created the app that initiated the action + * (if "appCreator" is not null). + * + * @remark This still appears to be present even if + * the action was initiated via an API key & token. */ + memberCreator: z.object({ + id: trelloIdSchema, + fullName: z.string(), + username: z.string(), + initials: z.string(), + avatarHash: z.string(), + avatarUrl: z.string(), + }), +}) +export type TrelloWebhook = z.infer diff --git a/integrations/trello/src/webhook-events/schemas/index.ts b/integrations/trello/src/webhook-events/schemas/index.ts new file mode 100644 index 00000000000..e3eb4b0d75c --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/index.ts @@ -0,0 +1,68 @@ +import { z } from '@botpress/sdk' +import { trelloIdSchema } from 'definitions/schemas' +import { cardAttachmentAddedWebhookSchema, cardAttachmentRemovedWebhookSchema } from './card-attachment-webhook-schemas' +import { + commentAddedWebhookSchema, + commentDeletedWebhookSchema, + commentUpdatedWebhookSchema, +} from './card-comment-webhook-schemas' +import { cardLabelAddedWebhookSchema, cardLabelRemovedWebhookSchema } from './card-label-webhook-schemas' +import { + cardCreatedWebhookSchema, + cardDeletedWebhookSchema, + cardUpdatedWebhookSchema, + cardVotesUpdatedWebhookSchema, +} from './card-webhook-schemas' +import { + checklistAddedToCardWebhookSchema, + checklistItemCreatedWebhookSchema, + checklistItemDeletedWebhookSchema, + checklistItemStatusUpdatedWebhookSchema, + checklistItemUpdatedWebhookSchema, +} from './checklist-webhook-schemas' +import { trelloWebhookSchema } from './common' +import { memberAddedToCardWebhookSchema, memberRemovedFromCardWebhookSchema } from './member-webhook-schemas' + +const _webhookDetailsSchema = z.object({ + id: trelloIdSchema, + idModel: trelloIdSchema, + active: z.boolean(), + consecutiveFailures: z.number().min(0), +}) + +export const webhookEventPayloadSchema = z.object({ + action: z.union([ + // ---- Card Events ---- + cardCreatedWebhookSchema, + cardUpdatedWebhookSchema, + cardDeletedWebhookSchema, + cardVotesUpdatedWebhookSchema, + // ---- Card Comment Events ---- + commentAddedWebhookSchema, + commentUpdatedWebhookSchema, + commentDeletedWebhookSchema, + // ---- Card Label Events ---- + cardLabelAddedWebhookSchema, + cardLabelRemovedWebhookSchema, + // ---- Card Attachment Events ---- + cardAttachmentAddedWebhookSchema, + cardAttachmentRemovedWebhookSchema, + // ---- Checklist Events ---- + checklistAddedToCardWebhookSchema, + checklistItemCreatedWebhookSchema, + checklistItemUpdatedWebhookSchema, + checklistItemDeletedWebhookSchema, + checklistItemStatusUpdatedWebhookSchema, + // ---- Member Events ---- + memberAddedToCardWebhookSchema, + memberRemovedFromCardWebhookSchema, + ]), + webhook: _webhookDetailsSchema, +}) +export type WebhookEventPayload = z.infer + +/** Fallback schema for unsupported event types */ +export const fallbackEventPayloadSchema = z.object({ + action: trelloWebhookSchema.extend({ type: z.string() }), + webhook: _webhookDetailsSchema, +}) diff --git a/integrations/trello/src/webhook-events/schemas/member-webhook-schemas.ts b/integrations/trello/src/webhook-events/schemas/member-webhook-schemas.ts new file mode 100644 index 00000000000..5a127deacaa --- /dev/null +++ b/integrations/trello/src/webhook-events/schemas/member-webhook-schemas.ts @@ -0,0 +1,27 @@ +import { z } from '@botpress/sdk' +import { TrelloEventType } from 'definitions/events' +import { pickIdAndName } from 'definitions/events/common' +import { eventMemberSchema } from 'definitions/events/member-events' +import { boardSchema, cardSchema } from 'definitions/schemas' +import { trelloWebhookSchema } from './common' + +export const memberAddedToCardWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.MEMBER_ADDED_TO_CARD), + data: z.object({ + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + member: eventMemberSchema, + }), +}) +export type MemberAddedToCardWebhook = z.infer + +export const memberRemovedFromCardWebhookSchema = trelloWebhookSchema.extend({ + type: z.literal(TrelloEventType.MEMBER_REMOVED_FROM_CARD), + data: z.object({ + deactivated: z.boolean(), + board: pickIdAndName(boardSchema), + card: pickIdAndName(cardSchema), + member: eventMemberSchema, + }), +}) +export type MemberRemovedFromCardWebhook = z.infer diff --git a/integrations/trello/src/webhook-lifecycle-utils.ts b/integrations/trello/src/webhook-lifecycle-utils.ts index 59ab415433c..b9322be3aa3 100644 --- a/integrations/trello/src/webhook-lifecycle-utils.ts +++ b/integrations/trello/src/webhook-lifecycle-utils.ts @@ -1,13 +1,13 @@ import { Webhook } from 'definitions/schemas' import { WebhookIdState } from 'definitions/states' -import { integrationName } from '../package.json' +import { INTEGRATION_NAME } from 'integration.definition' import { TrelloClient } from './trello-api/trello-client' import * as bp from '.botpress' const _setWebhookId = async ({ ctx, client }: bp.CommonHandlerProps, webhookId: WebhookIdState): Promise => { await client.setState({ type: 'integration', - name: 'webhookState', + name: 'webhook', id: ctx.integrationId, payload: { trelloWebhookId: webhookId, @@ -27,7 +27,7 @@ const _registerWebhook = async ( logger.forBot().info('Registering Trello webhook...') const newWebhook = await trelloClient.createWebhook({ - description: integrationName + ctx.integrationId, + description: INTEGRATION_NAME + ctx.integrationId, url: webhookUrl, modelId, }) diff --git a/packages/cli/package.json b/packages/cli/package.json index ff42dd094db..368a796c396 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/cli", - "version": "5.4.1", + "version": "5.4.2", "description": "Botpress CLI", "scripts": { "build": "pnpm run build:types && pnpm run bundle && pnpm run template:gen", @@ -27,8 +27,8 @@ "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.7.0", "@botpress/chat": "0.5.4", - "@botpress/client": "1.29.0", - "@botpress/sdk": "5.3.0", + "@botpress/client": "1.30.0", + "@botpress/sdk": "5.3.1", "@bpinternal/const": "^0.1.0", "@bpinternal/tunnel": "^0.1.1", "@bpinternal/verel": "^0.2.0", diff --git a/packages/cli/templates/empty-bot/package.json b/packages/cli/templates/empty-bot/package.json index 43cb860605e..9474025eb41 100644 --- a/packages/cli/templates/empty-bot/package.json +++ b/packages/cli/templates/empty-bot/package.json @@ -5,8 +5,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.29.0", - "@botpress/sdk": "5.3.0" + "@botpress/client": "1.30.0", + "@botpress/sdk": "5.3.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/empty-integration/package.json b/packages/cli/templates/empty-integration/package.json index 1bad78af503..6b28e985081 100644 --- a/packages/cli/templates/empty-integration/package.json +++ b/packages/cli/templates/empty-integration/package.json @@ -6,8 +6,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.29.0", - "@botpress/sdk": "5.3.0" + "@botpress/client": "1.30.0", + "@botpress/sdk": "5.3.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/empty-plugin/package.json b/packages/cli/templates/empty-plugin/package.json index 8fc3e8e0e00..b3dcf0f2dfe 100644 --- a/packages/cli/templates/empty-plugin/package.json +++ b/packages/cli/templates/empty-plugin/package.json @@ -6,7 +6,7 @@ }, "private": true, "dependencies": { - "@botpress/sdk": "5.3.0" + "@botpress/sdk": "5.3.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/hello-world/package.json b/packages/cli/templates/hello-world/package.json index cab4048b05c..83af59a0f12 100644 --- a/packages/cli/templates/hello-world/package.json +++ b/packages/cli/templates/hello-world/package.json @@ -6,8 +6,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.29.0", - "@botpress/sdk": "5.3.0" + "@botpress/client": "1.30.0", + "@botpress/sdk": "5.3.1" }, "devDependencies": { "@types/node": "^22.16.4", diff --git a/packages/cli/templates/webhook-message/package.json b/packages/cli/templates/webhook-message/package.json index 25ec09da92e..d64f112c797 100644 --- a/packages/cli/templates/webhook-message/package.json +++ b/packages/cli/templates/webhook-message/package.json @@ -6,8 +6,8 @@ }, "private": true, "dependencies": { - "@botpress/client": "1.29.0", - "@botpress/sdk": "5.3.0", + "@botpress/client": "1.30.0", + "@botpress/sdk": "5.3.1", "axios": "^1.6.8" }, "devDependencies": { diff --git a/packages/client/openapi.ts b/packages/client/openapi.ts index 3326c563d28..529f1f1f8d2 100644 --- a/packages/client/openapi.ts +++ b/packages/client/openapi.ts @@ -1,4 +1,4 @@ -import { runtimeApi, adminApi, filesApi, tablesApi, api as publicApi } from '@botpress/api' +import { runtimeApi, adminApi, filesApi, tablesApi, api as publicApi, billingApi } from '@botpress/api' const options = { generator: 'opapi', @@ -11,3 +11,4 @@ void runtimeApi.exportClient('./src/gen/runtime', options) void adminApi.exportClient('./src/gen/admin', options) void filesApi.exportClient('./src/gen/files', options) void tablesApi.exportClient('./src/gen/tables', options) +void billingApi.exportClient('./src/gen/billing', options) diff --git a/packages/client/package.json b/packages/client/package.json index 4377590e5de..e395defde0b 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/client", - "version": "1.29.0", + "version": "1.30.0", "description": "Botpress Client", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/client/src/billing/index.ts b/packages/client/src/billing/index.ts new file mode 100644 index 00000000000..885d94a1c77 --- /dev/null +++ b/packages/client/src/billing/index.ts @@ -0,0 +1,43 @@ +import axiosRetry from 'axios-retry' +import * as common from '../common' +import * as gen from '../gen/billing' +import * as types from '../types' + +type IClient = common.types.Simplify +export type Operation = common.types.Operation +export type ClientInputs = common.types.Inputs +export type ClientOutputs = common.types.Outputs + +export type ClientProps = common.types.CommonClientProps & { + workspaceId: string + token: string +} + +export class Client extends gen.Client { + public readonly config: Readonly + + public constructor(clientProps: ClientProps) { + const clientConfig = common.config.getClientConfig(clientProps) + const axiosInstance = common.axios.createAxiosInstance(clientConfig) + + super(axiosInstance, { + toApiError: common.errors.toApiError, + }) + + if (clientProps.retry) { + axiosRetry(axiosInstance, clientProps.retry) + } + + this.config = clientConfig + } + + public get list() { + type ListInputs = common.types.ListInputs + return { + listInvoices: (props: ListInputs['listInvoices']) => + new common.listing.AsyncCollection(({ nextToken }) => + this.listInvoices({ nextToken, ...props }).then((r) => ({ ...r, items: r.invoices })) + ), + } + } +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index f942dea90cf..2a118fa3e7d 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -2,6 +2,7 @@ export * as axios from 'axios' export * as axiosRetry from 'axios-retry' export * as runtime from './runtime' export * as admin from './admin' +export * as billing from './billing' export * as files from './files' export * as tables from './tables' export * from './public' diff --git a/packages/cognitive/package.json b/packages/cognitive/package.json index 96b4634b270..8bd6a6e829c 100644 --- a/packages/cognitive/package.json +++ b/packages/cognitive/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/cognitive", - "version": "0.3.6", + "version": "0.3.7", "description": "Wrapper around the Botpress Client to call LLMs", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/llmz/package.json b/packages/llmz/package.json index f4ba0ac1215..0beb322a871 100644 --- a/packages/llmz/package.json +++ b/packages/llmz/package.json @@ -2,7 +2,7 @@ "name": "llmz", "type": "module", "description": "LLMz - An LLM-native Typescript VM built on top of Zui", - "version": "0.0.44", + "version": "0.0.45", "types": "./dist/index.d.ts", "main": "./dist/index.cjs", "module": "./dist/index.js", @@ -71,8 +71,8 @@ "tsx": "^4.19.2" }, "peerDependencies": { - "@botpress/client": "1.29.0", - "@botpress/cognitive": "0.3.6", + "@botpress/client": "1.30.0", + "@botpress/cognitive": "0.3.7", "@bpinternal/thicktoken": "^1.0.5", "@bpinternal/zui": "^1.3.2" }, diff --git a/packages/sdk/package.json b/packages/sdk/package.json index c4e44443548..75db1941683 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/sdk", - "version": "5.3.0", + "version": "5.3.1", "description": "Botpress SDK", "main": "./dist/index.cjs", "module": "./dist/index.mjs", @@ -20,7 +20,7 @@ "author": "", "license": "MIT", "dependencies": { - "@botpress/client": "1.29.0", + "@botpress/client": "1.30.0", "browser-or-node": "^2.1.1", "semver": "^7.3.8" }, diff --git a/packages/vai/package.json b/packages/vai/package.json index 36ca12efe47..7004a95c503 100644 --- a/packages/vai/package.json +++ b/packages/vai/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/vai", - "version": "0.0.9", + "version": "0.0.10", "description": "Vitest AI (vai) – a vitest extension for testing with LLMs", "types": "./dist/index.d.ts", "exports": { @@ -40,7 +40,7 @@ "tsup": "^8.0.2" }, "peerDependencies": { - "@botpress/client": "1.29.0", + "@botpress/client": "1.30.0", "@bpinternal/thicktoken": "^1.0.1", "@bpinternal/zui": "^1.3.2", "lodash": "^4.17.21", diff --git a/packages/zai/package.json b/packages/zai/package.json index fc77664663e..007f5bb7318 100644 --- a/packages/zai/package.json +++ b/packages/zai/package.json @@ -1,7 +1,7 @@ { "name": "@botpress/zai", "description": "Zui AI (zai) – An LLM utility library written on top of Zui and the Botpress API", - "version": "2.5.9", + "version": "2.5.10", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { @@ -32,7 +32,7 @@ "author": "", "license": "ISC", "dependencies": { - "@botpress/cognitive": "0.3.6", + "@botpress/cognitive": "0.3.7", "json5": "^2.2.3", "jsonrepair": "^3.10.0", "lodash-es": "^4.17.21", diff --git a/plugins/conversation-insights/package.json b/plugins/conversation-insights/package.json index 70a60f7fd24..74d1a234681 100644 --- a/plugins/conversation-insights/package.json +++ b/plugins/conversation-insights/package.json @@ -7,7 +7,7 @@ }, "private": true, "dependencies": { - "@botpress/cognitive": "0.3.6", + "@botpress/cognitive": "0.3.7", "@botpress/sdk": "workspace:*", "browser-or-node": "^2.1.1", "jsonrepair": "^3.10.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e7f384e33b..d96437c97d5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1913,24 +1913,6 @@ importers: '@botpress/cli': specifier: workspace:* version: link:../../packages/cli - '@botpresshub/creatable': - specifier: workspace:* - version: link:../../interfaces/creatable - '@botpresshub/deletable': - specifier: workspace:* - version: link:../../interfaces/deletable - '@botpresshub/listable': - specifier: workspace:* - version: link:../../interfaces/listable - '@botpresshub/readable': - specifier: workspace:* - version: link:../../interfaces/readable - '@botpresshub/updatable': - specifier: workspace:* - version: link:../../interfaces/updatable - '@sentry/cli': - specifier: ^2.39.1 - version: 2.39.1 integrations/twilio: dependencies: @@ -2498,10 +2480,10 @@ importers: specifier: 0.5.4 version: link:../chat-client '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../client '@botpress/sdk': - specifier: 5.3.0 + specifier: 5.3.1 version: link:../sdk '@bpinternal/const': specifier: ^0.1.0 @@ -2622,10 +2604,10 @@ importers: packages/cli/templates/empty-bot: dependencies: '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../../../client '@botpress/sdk': - specifier: 5.3.0 + specifier: 5.3.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2638,10 +2620,10 @@ importers: packages/cli/templates/empty-integration: dependencies: '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../../../client '@botpress/sdk': - specifier: 5.3.0 + specifier: 5.3.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2654,7 +2636,7 @@ importers: packages/cli/templates/empty-plugin: dependencies: '@botpress/sdk': - specifier: 5.3.0 + specifier: 5.3.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2667,10 +2649,10 @@ importers: packages/cli/templates/hello-world: dependencies: '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../../../client '@botpress/sdk': - specifier: 5.3.0 + specifier: 5.3.1 version: link:../../../sdk devDependencies: '@types/node': @@ -2683,10 +2665,10 @@ importers: packages/cli/templates/webhook-message: dependencies: '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../../../client '@botpress/sdk': - specifier: 5.3.0 + specifier: 5.3.1 version: link:../../../sdk axios: specifier: ^1.6.8 @@ -2837,10 +2819,10 @@ importers: specifier: ^7.26.3 version: 7.26.9 '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../client '@botpress/cognitive': - specifier: 0.3.6 + specifier: 0.3.7 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^1.0.5 @@ -2943,7 +2925,7 @@ importers: packages/sdk: dependencies: '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../client '@bpinternal/zui': specifier: ^1.3.2 @@ -2980,7 +2962,7 @@ importers: packages/vai: dependencies: '@botpress/client': - specifier: 1.29.0 + specifier: 1.30.0 version: link:../client '@bpinternal/thicktoken': specifier: ^1.0.1 @@ -3026,7 +3008,7 @@ importers: packages/zai: dependencies: '@botpress/cognitive': - specifier: 0.3.6 + specifier: 0.3.7 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^1.0.0 @@ -3145,7 +3127,7 @@ importers: plugins/conversation-insights: dependencies: '@botpress/cognitive': - specifier: 0.3.6 + specifier: 0.3.7 version: link:../../packages/cognitive '@botpress/sdk': specifier: workspace:* @@ -3374,7 +3356,7 @@ packages: resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} '@asamuzakjp/css-color@3.2.0': - resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==, tarball: https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz} + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} '@aws-crypto/ie11-detection@3.0.0': resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} @@ -3703,7 +3685,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==, tarball: https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz} + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} engines: {node: '>=6.9.0'} '@babel/core@7.26.9': @@ -3711,7 +3693,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==, tarball: https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz} + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} engines: {node: '>=6.9.0'} '@babel/generator@7.26.9': @@ -3723,7 +3705,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==, tarball: https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz} + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.27.1': @@ -3735,7 +3717,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==, tarball: https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz} + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} '@babel/helper-create-class-features-plugin@7.27.1': @@ -3745,7 +3727,7 @@ packages: '@babel/core': ^7.0.0 '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, tarball: https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz} + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} '@babel/helper-member-expression-to-functions@7.27.1': @@ -3773,7 +3755,7 @@ packages: '@babel/core': ^7.0.0 '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==, tarball: https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz} + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -3817,7 +3799,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, tarball: https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz} + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} '@babel/helpers@7.26.9': @@ -3825,7 +3807,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/helpers@7.28.3': - resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==, tarball: https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz} + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} engines: {node: '>=6.9.0'} '@babel/parser@7.26.9': @@ -3839,7 +3821,7 @@ packages: hasBin: true '@babel/parser@7.28.3': - resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==, tarball: https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz} + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} engines: {node: '>=6.0.0'} hasBin: true @@ -3965,7 +3947,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/traverse@7.28.3': - resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==, tarball: https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz} + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} engines: {node: '>=6.9.0'} '@babel/types@7.26.9': @@ -3977,7 +3959,7 @@ packages: engines: {node: '>=6.9.0'} '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==, tarball: https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz} + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} engines: {node: '>=6.9.0'} '@bcherny/json-schema-ref-parser@10.0.5-fork': @@ -4061,31 +4043,31 @@ packages: engines: {node: '>=12'} '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==, tarball: https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz} + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==, tarball: https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz} + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} engines: {node: '>=18'} peerDependencies: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==, tarball: https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz} + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} engines: {node: '>=18'} peerDependencies: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==, tarball: https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz} + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} peerDependencies: '@csstools/css-tokenizer': ^3.0.4 '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==, tarball: https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz} + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} '@dabh/diagnostics@2.0.3': @@ -4104,577 +4086,577 @@ packages: engines: {node: '>=20.11.0'} '@esbuild/aix-ppc64@0.19.12': - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz} + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz} + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==, tarball: https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz} + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.19.12': - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz} + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz} + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz} + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==, tarball: https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz} + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.19.12': - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz} + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz} + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz} + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==, tarball: https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz} + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.19.12': - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz} + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz} + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz} + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==, tarball: https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz} + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.19.12': - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz} + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz} + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz} + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==, tarball: https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz} + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.19.12': - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz} + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz} + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz} + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==, tarball: https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz} + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.19.12': - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz} + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz} + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz} + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==, tarball: https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz} + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.19.12': - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz} + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz} + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==, tarball: https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz} + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.19.12': - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz} + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz} + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz} + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz} + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.19.12': - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz} + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz} + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz} + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==, tarball: https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz} + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.19.12': - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz} + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz} + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz} + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==, tarball: https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz} + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.19.12': - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz} + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz} + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz} + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==, tarball: https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz} + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.19.12': - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz} + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz} + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz} + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==, tarball: https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz} + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.19.12': - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz} + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz} + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz} + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==, tarball: https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz} + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.19.12': - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz} + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz} + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz} + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==, tarball: https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz} + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.19.12': - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz} + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz} + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz} + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==, tarball: https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz} + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.19.12': - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz} + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz} + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz} + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==, tarball: https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz} + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==, tarball: https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz} + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.19.12': - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz} + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz} + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==, tarball: https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz} + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz} + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz} + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.19.12': - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz} + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz} + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz} + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==, tarball: https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz} + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==, tarball: https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz} + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] '@esbuild/sunos-x64@0.19.12': - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz} + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz} + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz} + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==, tarball: https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz} + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.19.12': - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz} + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz} + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz} + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==, tarball: https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz} + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.19.12': - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz} + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz} + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz} + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==, tarball: https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz} + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.19.12': - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz} + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz} + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz} + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} engines: {node: '>=18'} cpu: [x64] os: [win32] '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==, tarball: https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz} + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -5019,7 +5001,7 @@ packages: engines: {node: '>=18'} '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': - resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==, tarball: https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz} + resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -5161,42 +5143,42 @@ packages: resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} '@oxlint/darwin-arm64@1.14.0': - resolution: {integrity: sha512-rcTw0QWeOc6IeVp+Up7WtcwdS9l4j7TOq4tihF0Ud/fl+VUVdvDCPuZ9QTnLXJhwMXiyQRWdxRyI6XBwf80ncQ==, tarball: https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-1.14.0.tgz} + resolution: {integrity: sha512-rcTw0QWeOc6IeVp+Up7WtcwdS9l4j7TOq4tihF0Ud/fl+VUVdvDCPuZ9QTnLXJhwMXiyQRWdxRyI6XBwf80ncQ==} cpu: [arm64] os: [darwin] '@oxlint/darwin-x64@1.14.0': - resolution: {integrity: sha512-TWFSEmyl2/DN4HoXNwQl0y/y3EXFJDctfv5MiDtVOV1GJKX80cGSIxMxXb08Q3CCWqteqEijmfSMo5TG8X1H/A==, tarball: https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-1.14.0.tgz} + resolution: {integrity: sha512-TWFSEmyl2/DN4HoXNwQl0y/y3EXFJDctfv5MiDtVOV1GJKX80cGSIxMxXb08Q3CCWqteqEijmfSMo5TG8X1H/A==} cpu: [x64] os: [darwin] '@oxlint/linux-arm64-gnu@1.14.0': - resolution: {integrity: sha512-N1FqdKfwhVWPpMElv8qlGqdEefTbDYaRVhdGWOjs/2f7FESa5vX0cvA7ToqzkoXyXZI5DqByWiPML33njK30Kg==, tarball: https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-1.14.0.tgz} + resolution: {integrity: sha512-N1FqdKfwhVWPpMElv8qlGqdEefTbDYaRVhdGWOjs/2f7FESa5vX0cvA7ToqzkoXyXZI5DqByWiPML33njK30Kg==} cpu: [arm64] os: [linux] '@oxlint/linux-arm64-musl@1.14.0': - resolution: {integrity: sha512-v/BPuiateLBb7Gz1STb69EWjkgKdlPQ1NM56z+QQur21ly2hiMkBX2n0zEhqfu9PQVRUizu6AlsYuzcPY/zsIQ==, tarball: https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-1.14.0.tgz} + resolution: {integrity: sha512-v/BPuiateLBb7Gz1STb69EWjkgKdlPQ1NM56z+QQur21ly2hiMkBX2n0zEhqfu9PQVRUizu6AlsYuzcPY/zsIQ==} cpu: [arm64] os: [linux] '@oxlint/linux-x64-gnu@1.14.0': - resolution: {integrity: sha512-gUTp8KIrSYt97dn+tRRC3LKnH4xlHKCwrPwiDcGmLbCxojuN9/H5mnIhPKEfwNuZNdoKGS/ABuq3neVyvRCRtQ==, tarball: https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-1.14.0.tgz} + resolution: {integrity: sha512-gUTp8KIrSYt97dn+tRRC3LKnH4xlHKCwrPwiDcGmLbCxojuN9/H5mnIhPKEfwNuZNdoKGS/ABuq3neVyvRCRtQ==} cpu: [x64] os: [linux] '@oxlint/linux-x64-musl@1.14.0': - resolution: {integrity: sha512-DpN6cW2HPjYXeENG0JBbmubO8LtfKt6qJqEMBw9gUevbyBaX+k+Jn7sYgh6S23wGOkzmTNphBsf/7ulj4nIVYA==, tarball: https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-1.14.0.tgz} + resolution: {integrity: sha512-DpN6cW2HPjYXeENG0JBbmubO8LtfKt6qJqEMBw9gUevbyBaX+k+Jn7sYgh6S23wGOkzmTNphBsf/7ulj4nIVYA==} cpu: [x64] os: [linux] '@oxlint/win32-arm64@1.14.0': - resolution: {integrity: sha512-oXxJksnUTUMgJ0NvjKS1mrCXAy1ttPgIVacRSlxQ+1XHy+aJDMM7I8fsCtoKoEcTIpPaD98eqUqlLYs0H2MGjA==, tarball: https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-1.14.0.tgz} + resolution: {integrity: sha512-oXxJksnUTUMgJ0NvjKS1mrCXAy1ttPgIVacRSlxQ+1XHy+aJDMM7I8fsCtoKoEcTIpPaD98eqUqlLYs0H2MGjA==} cpu: [arm64] os: [win32] '@oxlint/win32-x64@1.14.0': - resolution: {integrity: sha512-iRYy2rhTQKFztyx0jtNMRBnFpzsRwFdjWQ7sKKzJpmbijA3Tw3DCqlGT7QRgoVRF0+X/ccNGvvsrgMohPVfLeQ==, tarball: https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.14.0.tgz} + resolution: {integrity: sha512-iRYy2rhTQKFztyx0jtNMRBnFpzsRwFdjWQ7sKKzJpmbijA3Tw3DCqlGT7QRgoVRF0+X/ccNGvvsrgMohPVfLeQ==} cpu: [x64] os: [win32] @@ -5205,7 +5187,7 @@ packages: engines: {node: '>= 10.0.0'} '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} '@pkgr/core@0.2.9': @@ -5397,92 +5379,92 @@ packages: '@redis/client': ^1.0.0 '@rollup/rollup-android-arm-eabi@4.24.2': - resolution: {integrity: sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz} + resolution: {integrity: sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.24.2': - resolution: {integrity: sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==, tarball: https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz} + resolution: {integrity: sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.24.2': - resolution: {integrity: sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz} + resolution: {integrity: sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.24.2': - resolution: {integrity: sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==, tarball: https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz} + resolution: {integrity: sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.24.2': - resolution: {integrity: sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz} + resolution: {integrity: sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.24.2': - resolution: {integrity: sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==, tarball: https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz} + resolution: {integrity: sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.24.2': - resolution: {integrity: sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz} + resolution: {integrity: sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm-musleabihf@4.24.2': - resolution: {integrity: sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz} + resolution: {integrity: sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==} cpu: [arm] os: [linux] '@rollup/rollup-linux-arm64-gnu@4.24.2': - resolution: {integrity: sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz} + resolution: {integrity: sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-arm64-musl@4.24.2': - resolution: {integrity: sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz} + resolution: {integrity: sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==} cpu: [arm64] os: [linux] '@rollup/rollup-linux-powerpc64le-gnu@4.24.2': - resolution: {integrity: sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz} + resolution: {integrity: sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==} cpu: [ppc64] os: [linux] '@rollup/rollup-linux-riscv64-gnu@4.24.2': - resolution: {integrity: sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz} + resolution: {integrity: sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==} cpu: [riscv64] os: [linux] '@rollup/rollup-linux-s390x-gnu@4.24.2': - resolution: {integrity: sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz} + resolution: {integrity: sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==} cpu: [s390x] os: [linux] '@rollup/rollup-linux-x64-gnu@4.24.2': - resolution: {integrity: sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz} + resolution: {integrity: sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==} cpu: [x64] os: [linux] '@rollup/rollup-linux-x64-musl@4.24.2': - resolution: {integrity: sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==, tarball: https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.2.tgz} + resolution: {integrity: sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==} cpu: [x64] os: [linux] '@rollup/rollup-win32-arm64-msvc@4.24.2': - resolution: {integrity: sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz} + resolution: {integrity: sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.24.2': - resolution: {integrity: sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz} + resolution: {integrity: sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-msvc@4.24.2': - resolution: {integrity: sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==, tarball: https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz} + resolution: {integrity: sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==} cpu: [x64] os: [win32] @@ -5535,42 +5517,42 @@ packages: engines: {node: '>=8'} '@sentry/cli-darwin@2.39.1': - resolution: {integrity: sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ==, tarball: https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.39.1.tgz} + resolution: {integrity: sha512-kiNGNSAkg46LNGatfNH5tfsmI/kCAaPA62KQuFZloZiemTNzhy9/6NJP8HZ/GxGs8GDMxic6wNrV9CkVEgFLJQ==} engines: {node: '>=10'} os: [darwin] '@sentry/cli-linux-arm64@2.39.1': - resolution: {integrity: sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw==, tarball: https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.39.1.tgz} + resolution: {integrity: sha512-5VbVJDatolDrWOgaffsEM7znjs0cR8bHt9Bq0mStM3tBolgAeSDHE89NgHggfZR+DJ2VWOy4vgCwkObrUD6NQw==} engines: {node: '>=10'} cpu: [arm64] os: [linux, freebsd] '@sentry/cli-linux-arm@2.39.1': - resolution: {integrity: sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ==, tarball: https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.39.1.tgz} + resolution: {integrity: sha512-DkENbxyRxUrfLnJLXTA4s5UL/GoctU5Cm4ER1eB7XN7p9WsamFJd/yf2KpltkjEyiTuplv0yAbdjl1KX3vKmEQ==} engines: {node: '>=10'} cpu: [arm] os: [linux, freebsd] '@sentry/cli-linux-i686@2.39.1': - resolution: {integrity: sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg==, tarball: https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.39.1.tgz} + resolution: {integrity: sha512-pXWVoKXCRrY7N8vc9H7mETiV9ZCz+zSnX65JQCzZxgYrayQPJTc+NPRnZTdYdk5RlAupXaFicBI2GwOCRqVRkg==} engines: {node: '>=10'} cpu: [x86, ia32] os: [linux, freebsd] '@sentry/cli-linux-x64@2.39.1': - resolution: {integrity: sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA==, tarball: https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.39.1.tgz} + resolution: {integrity: sha512-IwayNZy+it7FWG4M9LayyUmG1a/8kT9+/IEm67sT5+7dkMIMcpmHDqL8rWcPojOXuTKaOBBjkVdNMBTXy0mXlA==} engines: {node: '>=10'} cpu: [x64] os: [linux, freebsd] '@sentry/cli-win32-i686@2.39.1': - resolution: {integrity: sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q==, tarball: https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.39.1.tgz} + resolution: {integrity: sha512-NglnNoqHSmE+Dz/wHeIVRnV2bLMx7tIn3IQ8vXGO5HWA2f8zYJGktbkLq1Lg23PaQmeZLPGlja3gBQfZYSG10Q==} engines: {node: '>=10'} cpu: [x86, ia32] os: [win32] '@sentry/cli-win32-x64@2.39.1': - resolution: {integrity: sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==, tarball: https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.39.1.tgz} + resolution: {integrity: sha512-xv0R2CMf/X1Fte3cMWie1NXuHmUyQPDBfCyIt6k6RPFPxAYUgcqgMPznYwVMwWEA1W43PaOkSn3d8ZylsDaETw==} engines: {node: '>=10'} cpu: [x64] os: [win32] @@ -6886,7 +6868,7 @@ packages: hasBin: true browserslist@4.25.4: - resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==, tarball: https://registry.npmjs.org/browserslist/-/browserslist-4.25.4.tgz} + resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -6996,7 +6978,7 @@ packages: resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==} caniuse-lite@1.0.30001739: - resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==, tarball: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz} + resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==} capital-case@1.0.4: resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} @@ -7059,7 +7041,7 @@ packages: engines: {node: '>=20.18.1'} chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==, tarball: https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz} + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} chokidar@4.0.3: @@ -7236,7 +7218,7 @@ packages: engines: {node: '>= 6'} cssstyle@4.6.0: - resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==, tarball: https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz} + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} csstype@3.1.3: @@ -7258,7 +7240,7 @@ packages: engines: {node: '>= 12'} data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==, tarball: https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz} + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} data-view-buffer@1.0.2: @@ -7274,7 +7256,7 @@ packages: engines: {node: '>= 0.4'} dayjs@1.11.19: - resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==, tarball: https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz} + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -7315,7 +7297,7 @@ packages: engines: {node: '>=10'} decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==, tarball: https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz} + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} @@ -7520,7 +7502,7 @@ packages: resolution: {integrity: sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g==} electron-to-chromium@1.5.214: - resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==, tarball: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.214.tgz} + resolution: {integrity: sha512-TpvUNdha+X3ybfU78NoQatKvQEm1oq3lf2QbnmCEdw+Bd9RuIAY+hJTvq1avzHM0f7EJfnH3vbCnbzKzisc/9Q==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -8088,7 +8070,7 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz} + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -8373,7 +8355,7 @@ packages: engines: {node: '>=8'} html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==, tarball: https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz} + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} html-escaper@2.0.2: @@ -8418,7 +8400,7 @@ packages: engines: {node: '>= 6'} https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, tarball: https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz} + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} human-signals@2.1.0: @@ -8656,7 +8638,7 @@ packages: engines: {node: '>=0.10.0'} is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==, tarball: https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz} + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} @@ -8949,7 +8931,7 @@ packages: engines: {node: '>=12.0.0'} jsdom@24.1.3: - resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==, tarball: https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz} + resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==} engines: {node: '>=18'} peerDependencies: canvas: ^2.11.2 @@ -9042,7 +9024,7 @@ packages: hasBin: true jsonpath-plus@6.0.1: - resolution: {integrity: sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==, tarball: https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-6.0.1.tgz} + resolution: {integrity: sha512-EvGovdvau6FyLexFH2OeXfIITlgIbgZoAZe3usiySeaIDm5QS+A10DKNpaPBBqqRSZr2HN6HVNXxtwUAr2apEw==} engines: {node: '>=10.0.0'} jsonpath-plus@7.1.0: @@ -9754,7 +9736,7 @@ packages: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} nwsapi@2.2.23: - resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==, tarball: https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz} + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} oauth-sign@0.9.0: resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} @@ -10037,7 +10019,7 @@ packages: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} pg-cloudflare@1.2.7: - resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==, tarball: https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz} + resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} pg-connection-string@2.7.0: resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} @@ -10471,10 +10453,10 @@ packages: hasBin: true rrweb-cssom@0.7.1: - resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==, tarball: https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz} + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==, tarball: https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz} + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} rsa-pem-from-mod-exp@0.8.6: resolution: {integrity: sha512-c5ouQkOvGHF1qomUUDJGFcXsomeSO2gbEs6hVhMAtlkE1CuaZase/WzoaKFG/EZQuNmq6pw/EMCeEnDvOgCJYQ==} @@ -10528,14 +10510,14 @@ packages: resolution: {integrity: sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==} saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==, tarball: https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz} + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} scmp@2.1.0: - resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==, tarball: https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz} + resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==} deprecated: Just use Node.js's crypto.timingSafeEqual() seek-bzip@1.0.6: @@ -10605,32 +10587,32 @@ packages: engines: {node: '>=8'} sherif-darwin-arm64@1.0.1: - resolution: {integrity: sha512-eDYzUO6ksjYZF6swtoJ5F4AVa4og+sqrs0H7gGw57FoiMHEe5+4tD4M2ojTbVAaeeFtRS0gMbVZFGkhSAshQMw==, tarball: https://registry.npmjs.org/sherif-darwin-arm64/-/sherif-darwin-arm64-1.0.1.tgz} + resolution: {integrity: sha512-eDYzUO6ksjYZF6swtoJ5F4AVa4og+sqrs0H7gGw57FoiMHEe5+4tD4M2ojTbVAaeeFtRS0gMbVZFGkhSAshQMw==} cpu: [arm64] os: [darwin] sherif-darwin-x64@1.0.1: - resolution: {integrity: sha512-lnNZTck5F2eZoB1nZFwixfFQe/DXt4HuOMNCTG+Iyzq8w7kdSXUr/IIPYk2p88L+d4AtPWApmKE6mpmQaDzOVg==, tarball: https://registry.npmjs.org/sherif-darwin-x64/-/sherif-darwin-x64-1.0.1.tgz} + resolution: {integrity: sha512-lnNZTck5F2eZoB1nZFwixfFQe/DXt4HuOMNCTG+Iyzq8w7kdSXUr/IIPYk2p88L+d4AtPWApmKE6mpmQaDzOVg==} cpu: [x64] os: [darwin] sherif-linux-arm64@1.0.1: - resolution: {integrity: sha512-/fj3rJoSb26FA5wRlZpauLFaFeWCe9usIQAE+e9IEaC2751IU9I0d+nE7pmGmuOwTCR1/kPOlrDGfV4ucpP/ng==, tarball: https://registry.npmjs.org/sherif-linux-arm64/-/sherif-linux-arm64-1.0.1.tgz} + resolution: {integrity: sha512-/fj3rJoSb26FA5wRlZpauLFaFeWCe9usIQAE+e9IEaC2751IU9I0d+nE7pmGmuOwTCR1/kPOlrDGfV4ucpP/ng==} cpu: [arm64] os: [linux] sherif-linux-x64@1.0.1: - resolution: {integrity: sha512-I07H4VAPM0+N8rpIgxV47vBSKViJ3F/EiaUi4DnlhySivGg/6oOacHFOfzKeNPMaMK+3usMEmU+UVm6vEvtPYA==, tarball: https://registry.npmjs.org/sherif-linux-x64/-/sherif-linux-x64-1.0.1.tgz} + resolution: {integrity: sha512-I07H4VAPM0+N8rpIgxV47vBSKViJ3F/EiaUi4DnlhySivGg/6oOacHFOfzKeNPMaMK+3usMEmU+UVm6vEvtPYA==} cpu: [x64] os: [linux] sherif-windows-arm64@1.0.1: - resolution: {integrity: sha512-UfIYXDya60VmAcYuuKe6bwnBWiJHA6lof97rG2+EN8f3LiGyEx95/3qUdUoOn7qB5pl3xaeazMGcpc1O4FpExg==, tarball: https://registry.npmjs.org/sherif-windows-arm64/-/sherif-windows-arm64-1.0.1.tgz} + resolution: {integrity: sha512-UfIYXDya60VmAcYuuKe6bwnBWiJHA6lof97rG2+EN8f3LiGyEx95/3qUdUoOn7qB5pl3xaeazMGcpc1O4FpExg==} cpu: [arm64] os: [win32] sherif-windows-x64@1.0.1: - resolution: {integrity: sha512-ep+acRpTeIhU3BB7GL1NdedD9ubhcIO1lwOJ9uf3rOrmKIlbKd55LgmVRfl/Spy96qcCGx6izdyypMhDOoWa/Q==, tarball: https://registry.npmjs.org/sherif-windows-x64/-/sherif-windows-x64-1.0.1.tgz} + resolution: {integrity: sha512-ep+acRpTeIhU3BB7GL1NdedD9ubhcIO1lwOJ9uf3rOrmKIlbKd55LgmVRfl/Spy96qcCGx6izdyypMhDOoWa/Q==} cpu: [x64] os: [win32] @@ -10918,7 +10900,7 @@ packages: resolution: {integrity: sha512-CxU2/GyZXzzRxFlimtpTkEcrmCXj0SN6ZaqAO5v/fEFWVdPuukw5bWskw2CG7XQ26N+LWEmCUxT3YlE+YJFpgQ==} symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==, tarball: https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz} + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} synckit@0.11.11: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} @@ -11012,7 +10994,7 @@ packages: engines: {node: '>=0.8'} tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==, tarball: https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz} + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} tough-cookie@6.0.0: @@ -11026,7 +11008,7 @@ packages: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==, tarball: https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz} + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} tree-kill@1.2.2: @@ -11151,32 +11133,32 @@ packages: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} turbo-darwin-64@2.3.3: - resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==, tarball: https://registry.npmjs.org/turbo-darwin-64/-/turbo-darwin-64-2.3.3.tgz} + resolution: {integrity: sha512-bxX82xe6du/3rPmm4aCC5RdEilIN99VUld4HkFQuw+mvFg6darNBuQxyWSHZTtc25XgYjQrjsV05888w1grpaA==} cpu: [x64] os: [darwin] turbo-darwin-arm64@2.3.3: - resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==, tarball: https://registry.npmjs.org/turbo-darwin-arm64/-/turbo-darwin-arm64-2.3.3.tgz} + resolution: {integrity: sha512-DYbQwa3NsAuWkCUYVzfOUBbSUBVQzH5HWUFy2Kgi3fGjIWVZOFk86ss+xsWu//rlEAfYwEmopigsPYSmW4X15A==} cpu: [arm64] os: [darwin] turbo-linux-64@2.3.3: - resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==, tarball: https://registry.npmjs.org/turbo-linux-64/-/turbo-linux-64-2.3.3.tgz} + resolution: {integrity: sha512-eHj9OIB0dFaP6BxB88jSuaCLsOQSYWBgmhy2ErCu6D2GG6xW3b6e2UWHl/1Ho9FsTg4uVgo4DB9wGsKa5erjUA==} cpu: [x64] os: [linux] turbo-linux-arm64@2.3.3: - resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==, tarball: https://registry.npmjs.org/turbo-linux-arm64/-/turbo-linux-arm64-2.3.3.tgz} + resolution: {integrity: sha512-NmDE/NjZoDj1UWBhMtOPmqFLEBKhzGS61KObfrDEbXvU3lekwHeoPvAMfcovzswzch+kN2DrtbNIlz+/rp8OCg==} cpu: [arm64] os: [linux] turbo-windows-64@2.3.3: - resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==, tarball: https://registry.npmjs.org/turbo-windows-64/-/turbo-windows-64-2.3.3.tgz} + resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==} cpu: [x64] os: [win32] turbo-windows-arm64@2.3.3: - resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==, tarball: https://registry.npmjs.org/turbo-windows-arm64/-/turbo-windows-arm64-2.3.3.tgz} + resolution: {integrity: sha512-dW4ZK1r6XLPNYLIKjC4o87HxYidtRRcBeo/hZ9Wng2XM/MqqYkAyzJXJGgRMsc0MMEN9z4+ZIfnSNBrA0b08ag==} cpu: [arm64] os: [win32] @@ -11191,7 +11173,7 @@ packages: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} twilio@5.11.2: - resolution: {integrity: sha512-+pl0sbdj50UGtlhENGTmSnEsKeo4vBkHM62UUiysV+4amxQBmhNX3i3NGJVE+7CFqACzMkgoDTB3tjBthcHyyQ==, tarball: https://registry.npmjs.org/twilio/-/twilio-5.11.2.tgz} + resolution: {integrity: sha512-+pl0sbdj50UGtlhENGTmSnEsKeo4vBkHM62UUiysV+4amxQBmhNX3i3NGJVE+7CFqACzMkgoDTB3tjBthcHyyQ==} engines: {node: '>=14.0'} type-check@0.4.0: @@ -11270,7 +11252,7 @@ packages: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==, tarball: https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz} + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} engines: {node: '>=0.8.0'} hasBin: true @@ -11343,7 +11325,7 @@ packages: engines: {node: '>= 4.0.0'} universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==, tarball: https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz} + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} universalify@2.0.1: @@ -11364,7 +11346,7 @@ packages: browserslist: '>= 4.21.0' update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, tarball: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz} + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -11549,7 +11531,7 @@ packages: optional: true w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==, tarball: https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz} + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} walker@1.0.8: @@ -11569,7 +11551,7 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, tarball: https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz} + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} whatsapp-api-js@5.3.0: @@ -11589,7 +11571,7 @@ packages: engines: {node: '>=18'} whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==, tarball: https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz} + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} whatwg-url@5.0.0: @@ -11711,15 +11693,15 @@ packages: engines: {node: '>=18'} xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==, tarball: https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz} + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} xmlbuilder@13.0.2: - resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==, tarball: https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz} + resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==} engines: {node: '>=6.0'} xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==, tarball: https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz} + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} diff --git a/scripts/upload-sandbox-scripts.ts b/scripts/upload-sandbox-scripts.ts index 9403b90240d..529a0287735 100644 --- a/scripts/upload-sandbox-scripts.ts +++ b/scripts/upload-sandbox-scripts.ts @@ -18,6 +18,27 @@ function readIntegrationDefinition(integrationPath: string): any { return JSON.parse(readCmdResult.stdout.toString()) } +const _fallbackProfileArgs = () => ({ + apiUrl: undefined, + workspaceId: undefined, + token: undefined, +}) +function getProfileArgs(): any { + const activeProfileCmdResult = spawnSync('pnpm', ['exec', 'bp', 'profiles', 'active', '--json']) + if (activeProfileCmdResult.status !== 0) { + console.debug( + `Failed to get active profile: ${activeProfileCmdResult.error?.message || activeProfileCmdResult.stderr.toString() || 'Unknown error'}` + ) + return _fallbackProfileArgs() + } + + try { + return JSON.parse(activeProfileCmdResult.stdout.toString()) + } catch { + return _fallbackProfileArgs() + } +} + function parseArgs(): Args { return process.argv.slice(2).reduce>((acc, arg) => { const [key, value] = arg.split('=') @@ -98,14 +119,20 @@ async function uploadScripts({ } const args = parseArgs() -const apiUrl = args.apiUrl || process.env.BP_API_URL || DEFAULT_API_URL -const token = args.token || process.env.BP_TOKEN -const workspaceId = args.workspaceId || process.env.BP_WORKSPACE_ID +const profileArgs = getProfileArgs() +const apiUrl = args.apiUrl || profileArgs.apiUrl || process.env.BP_API_URL || DEFAULT_API_URL +const token = args.token || profileArgs.token || process.env.BP_TOKEN +const workspaceId = args.workspaceId || profileArgs.workspaceId || process.env.BP_WORKSPACE_ID const userEmail = args.userEmail const integrationId = args.integrationId || process.env.BP_INTEGRATION_ID const integrationPath = args.integrationPath if (!userEmail || !token || !workspaceId) { - console.error('Missing required arguments: userEmail, token, workspaceId') + const missingArgs: string[] = [ + !userEmail ? 'userEmail' : null, + !token ? 'token' : null, + !workspaceId ? 'workspaceId' : null, + ].filter((value) => value !== null) + console.error(`Missing required arguments: ${missingArgs.join(', ')}`) console.error( 'Usage: pnpm run ts-node -T upload-sandbox-scripts.ts --userEmail= --token= --workspaceId= [--apiUrl=] [--integrationId=] [--integrationPath=]\n' + 'integrationId, apiUrl, token, and workspaceId can also be set in the environment variables BP_INTEGRATION_ID, BP_API_URL, BP_TOKEN, BP_WORKSPACE_ID'