diff --git a/.github/workflows/run-checks.yml b/.github/workflows/run-checks.yml index 9c32a9ccb99..9becf153a48 100644 --- a/.github/workflows/run-checks.yml +++ b/.github/workflows/run-checks.yml @@ -9,7 +9,7 @@ permissions: jobs: run-checks: runs-on: depot-ubuntu-22.04-8 - timeout-minutes: 10 + timeout-minutes: 15 env: NODE_OPTIONS: '--max_old_space_size=8192' steps: diff --git a/integrations/salesforce/integration.definition.ts b/integrations/salesforce/integration.definition.ts index 3f7c3172379..5978c9a3e12 100644 --- a/integrations/salesforce/integration.definition.ts +++ b/integrations/salesforce/integration.definition.ts @@ -4,7 +4,7 @@ import { actionDefinitions } from './definitions' export default new IntegrationDefinition({ name: 'salesforce', title: 'Salesforce', - version: '1.0.0', + version: '1.0.1', readme: 'hub.md', icon: 'icon.svg', description: 'Salesforce integration allows you to create, search, update and delete a variety of Salesforce objects', @@ -51,4 +51,8 @@ export default new IntegrationDefinition({ }, }, actions: actionDefinitions, + attributes: { + category: 'CRM & Sales', + repo: 'botpress', + }, }) diff --git a/integrations/stripe/hub.md b/integrations/stripe/hub.md index 8846eb4d2f3..d7c8ab30479 100644 --- a/integrations/stripe/hub.md +++ b/integrations/stripe/hub.md @@ -2,25 +2,26 @@ Connect your Botpress chatbot with Stripe, a popular online payment platform tha ## Setup and Configuration -To set up the integration, you will need to provide your Stripe `apiKey`. This can be obtained from the Stripe Dashboard. You can use the `Secret Key` or create a `Restricted Key`. Once the integration is set up, you can use the built-in actions to manage your Stripe data. +The Stripe integration supports two authentication modes: -### Prerequisites +- **OAuth (recommended)** — authorize Botpress on your Stripe account through Stripe's hosted consent screen. No keys to copy or rotate. +- **API Key (manual)** — paste a Stripe Secret Key (`sk_live_…` / `sk_test_…`) or a Restricted Key. -Before enabling the Botpress Stripe Integration, please ensure that you have the following: +### Prerequisites - A Botpress cloud account. -- `apiKey` generated from Stripe. +- A Stripe account. ### Enable Integration -To enable the Stripe integration in Botpress, follow these steps: - -1. Access your Botpress admin panel. -2. Navigate to the “Integrations” section. -3. Locate the Stripe integration and click on “Enable” or “Configure.” -4. Provide the required `apiKey`. +1. Open your bot in the Botpress dashboard and navigate to the "Integrations" section. +2. Locate the Stripe integration and click on "Configure". +3. Click the configuration link to open the setup wizard. +4. Choose **Connect with OAuth** to be redirected to Stripe's authorization page, or choose **Use a Stripe API Key** to paste a key from the [Stripe Dashboard](https://dashboard.stripe.com/apikeys). 5. Save the configuration. +The integration will create a webhook endpoint on your Stripe account on first use. The webhook signing secret is captured automatically and used to verify all incoming events. + ## Usage Once the integration is enabled, you can start using Stripe features from your Botpress chatbot. The integration offers several actions for interacting with Stripe, such as `createPaymentLink` , `createSubsLink` (For generate Subscription Payment Link), `listPaymentLinks` (IDs and URLs), `listProductPrices` (If price has the "recurring" property, the product is of type subscription.), `findPaymentLink` (By URL, return ID), and `deactivatePaymentLink` (By ID). And actions for Customers, `listCustomers` (Optional filter by e-mail), `searchCustomers` (By e-mail, name or/and phone), `createCustomer` and `createOrRetrieveCustomer` (If the user already exists, his email has already been registered, get it. If there are multiple users with the same email, return an array of them. If it does not exist, it creates it). diff --git a/integrations/stripe/integration.definition.ts b/integrations/stripe/integration.definition.ts index e095f837062..a68f6630c4c 100644 --- a/integrations/stripe/integration.definition.ts +++ b/integrations/stripe/integration.definition.ts @@ -34,19 +34,17 @@ import { export default new IntegrationDefinition({ name: 'stripe', - version: '0.5.6', + version: '0.6.0', title: 'Stripe', readme: 'hub.md', icon: 'icon.svg', description: 'Manage payments, subscriptions, and customers seamlessly. Execute workflows on charge failures and subscription updates.', configuration: { + identifier: { + linkTemplateScript: 'linkTemplate.vrl', + }, schema: z.object({ - apiKey: z - .string() - .min(1) - .describe('The secret key or a restricted key from your Stripe account') - .title('API Key'), apiVersion: z .string() .optional() @@ -109,6 +107,39 @@ export default new IntegrationDefinition({ }, channels: {}, states: { + oAuthCredentials: { + type: 'integration', + schema: z.object({ + accessToken: z.string().secret().title('Access Token').describe('The OAuth access token'), + refreshToken: z.string().secret().title('Refresh Token').describe('The rotating OAuth refresh token'), + expiresAt: z + .string() + .datetime() + .title('Access Token Expires At') + .describe('The timestamp of when the access token expires'), + refreshExpiresAt: z + .string() + .datetime() + .title('Refresh Token Expires At') + .describe('The timestamp of when the refresh token expires'), + scopes: z.array(z.string()).title('Scopes').describe('The scopes granted to the token'), + stripeUserId: z + .string() + .title('Stripe Account ID') + .describe('The Stripe account ID (acct_xxx) the token was issued for'), + livemode: z.boolean().title('Live Mode').describe('Whether the token operates against Stripe live mode'), + }), + }, + manualCredentials: { + type: 'integration', + schema: z.object({ + apiKey: z + .string() + .secret() + .title('Stripe API Key') + .describe('The secret key or a restricted key from your Stripe account'), + }), + }, stripeIntegrationInfo: { type: 'integration', schema: z.object({ @@ -116,9 +147,23 @@ export default new IntegrationDefinition({ .string() .title('Stripe Webhook ID') .describe('The unique identifier for the Stripe webhook.'), + stripeWebhookSecret: z + .string() + .secret() + .optional() + .title('Stripe Webhook Signing Secret') + .describe('The signing secret returned by Stripe when the webhook endpoint was created.'), }), }, }, + secrets: { + CLIENT_ID: { + description: 'The client ID of the Stripe OAuth app.', + }, + CLIENT_SECRET: { + description: 'The Stripe secret API key (sk_live_/sk_test_) used to authenticate to the OAuth token endpoint.', + }, + }, actions: { createPaymentLink: { title: 'Create Payment Link', diff --git a/integrations/stripe/linkTemplate.vrl b/integrations/stripe/linkTemplate.vrl new file mode 100644 index 00000000000..23372049f7a --- /dev/null +++ b/integrations/stripe/linkTemplate.vrl @@ -0,0 +1,4 @@ +webhookId = to_string!(.webhookId) +webhookUrl = to_string!(.webhookUrl) + +"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}" diff --git a/integrations/stripe/src/actions/create-customer.ts b/integrations/stripe/src/actions/create-customer.ts index 1076dd14456..94053d1ec88 100644 --- a/integrations/stripe/src/actions/create-customer.ts +++ b/integrations/stripe/src/actions/create-customer.ts @@ -1,10 +1,10 @@ -import { getClient } from '../client' import { createCustomerInputSchema } from '../misc/custom-schemas' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const createCustomer: IntegrationProps['actions']['createCustomer'] = async ({ ctx, logger, input }) => { +export const createCustomer: IntegrationProps['actions']['createCustomer'] = async ({ ctx, client, logger, input }) => { const validatedInput = createCustomerInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { const params = { @@ -15,7 +15,7 @@ export const createCustomer: IntegrationProps['actions']['createCustomer'] = asy payment_method: validatedInput.paymentMethodId, address: validatedInput.address ? JSON.parse(validatedInput.address) : undefined, } - const customer = await StripeClient.createCustomer(params) + const customer = await stripeClient.createCustomer(params) response = { customer, diff --git a/integrations/stripe/src/actions/create-or-retrieve-customer.ts b/integrations/stripe/src/actions/create-or-retrieve-customer.ts index 8c47d00d34c..2375ade7aaf 100644 --- a/integrations/stripe/src/actions/create-or-retrieve-customer.ts +++ b/integrations/stripe/src/actions/create-or-retrieve-customer.ts @@ -1,17 +1,18 @@ -import { getClient } from '../client' import { createOrRetrieveCustomerInputSchema } from '../misc/custom-schemas' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' export const createOrRetrieveCustomer: IntegrationProps['actions']['createOrRetrieveCustomer'] = async ({ ctx, + client, logger, input, }) => { const validatedInput = createOrRetrieveCustomerInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { - const customers = await StripeClient.searchCustomers(validatedInput.email) + const customers = await stripeClient.searchCustomers(validatedInput.email) let customer if (customers.length === 0) { const params = { @@ -22,7 +23,7 @@ export const createOrRetrieveCustomer: IntegrationProps['actions']['createOrRetr payment_method: validatedInput.paymentMethodId, address: validatedInput.address ? JSON.parse(validatedInput.address) : undefined, } - customer = await StripeClient.createCustomer(params) + customer = await stripeClient.createCustomer(params) response = { customer } } else { response = customers.length === 1 ? { customer: customers[0] } : { customers } diff --git a/integrations/stripe/src/actions/create-paymentlink.ts b/integrations/stripe/src/actions/create-paymentlink.ts index c8f35264ac4..5a8642b92e0 100644 --- a/integrations/stripe/src/actions/create-paymentlink.ts +++ b/integrations/stripe/src/actions/create-paymentlink.ts @@ -1,35 +1,35 @@ -import { getClient } from '../client' import { createPaymentLinkInputSchema } from '../misc/custom-schemas' -import type { ProductBasic, StripeClient } from '../misc/stripe-client' +import type { ProductBasic } from '../misc/stripe-client' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -const findOrCreateProduct = async (StripeClient: StripeClient, productName: string) => { - const products = await StripeClient.listAllProductsBasic() +const findOrCreateProduct = async (stripeClient: StripeClient, productName: string) => { + const products = await stripeClient.listAllProductsBasic() let product = products.find((p: ProductBasic) => p.name === productName) if (!product) { - product = await StripeClient.createProduct(productName) + product = await stripeClient.createProduct(productName) } return product } const findOrCreatePrice = async ( - StripeClient: StripeClient, + stripeClient: StripeClient, productId: string, unitAmount: number, currency: string ) => { - const prices = await StripeClient.listPrices(productId) + const prices = await stripeClient.listPrices(productId) if (!unitAmount) { - return prices.data[0] || (await StripeClient.createPrice(productId, 0, currency)) + return prices.data[0] || (await stripeClient.createPrice(productId, 0, currency)) } let price = prices.data.find((p) => p.unit_amount === unitAmount && p.currency === currency) if (!price) { - price = await StripeClient.createPrice(productId, unitAmount, currency) + price = await stripeClient.createPrice(productId, unitAmount, currency) } return price @@ -53,13 +53,18 @@ const buildLineItem = ( } } -export const createPaymentLink: IntegrationProps['actions']['createPaymentLink'] = async ({ ctx, logger, input }) => { +export const createPaymentLink: IntegrationProps['actions']['createPaymentLink'] = async ({ + ctx, + client, + logger, + input, +}) => { const validatedInput = createPaymentLinkInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) try { - const product = await findOrCreateProduct(StripeClient, validatedInput.productName) - const price = await findOrCreatePrice(StripeClient, product.id, validatedInput.unit_amount, validatedInput.currency) + const product = await findOrCreateProduct(stripeClient, validatedInput.productName) + const price = await findOrCreatePrice(stripeClient, product.id, validatedInput.unit_amount, validatedInput.currency) const lineItem = buildLineItem( price.id, @@ -69,7 +74,7 @@ export const createPaymentLink: IntegrationProps['actions']['createPaymentLink'] validatedInput.adjustableQuantityMinimum ) - const paymentLink = await StripeClient.createPaymentLink(lineItem) + const paymentLink = await stripeClient.createPaymentLink(lineItem) logger.forBot().info(`Successful - Create Payment Link - ${paymentLink.id}`) diff --git a/integrations/stripe/src/actions/create-subslink.ts b/integrations/stripe/src/actions/create-subslink.ts index e0fcc3ccef5..5d4d69c247b 100644 --- a/integrations/stripe/src/actions/create-subslink.ts +++ b/integrations/stripe/src/actions/create-subslink.ts @@ -1,21 +1,21 @@ import Stripe from 'stripe' -import { getClient } from '../client' import { createSubsLinkInputSchema } from '../misc/custom-schemas' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = async ({ ctx, logger, input }) => { +export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = async ({ ctx, client, logger, input }) => { const validatedInput = createSubsLinkInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { - const products = await StripeClient.listAllProductsBasic() + const products = await stripeClient.listAllProductsBasic() let product = products.find((p) => p.name === validatedInput.productName) if (!product) { - product = await StripeClient.createProduct(validatedInput.productName) + product = await stripeClient.createProduct(validatedInput.productName) } - const prices = await StripeClient.listPrices(product.id) + const prices = await stripeClient.listPrices(product.id) let price if (!validatedInput.unit_amount) { price = prices.data.find((p) => p.recurring) @@ -33,7 +33,7 @@ export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = asy : 'month' if (!price) { - price = await StripeClient.createSubsPrice(product.id, validatedInput.unit_amount, validatedInput.currency, { + price = await stripeClient.createSubsPrice(product.id, validatedInput.unit_amount, validatedInput.currency, { interval, }) } @@ -55,7 +55,7 @@ export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = asy } : undefined - const paymentLink = await StripeClient.createSubsLink(lineItem, subscriptionData) + const paymentLink = await stripeClient.createSubsLink(lineItem, subscriptionData) response = { id: paymentLink.id, diff --git a/integrations/stripe/src/actions/deactivate-paymentlink.ts b/integrations/stripe/src/actions/deactivate-paymentlink.ts index 0513dad72fc..f9cf87c990c 100644 --- a/integrations/stripe/src/actions/deactivate-paymentlink.ts +++ b/integrations/stripe/src/actions/deactivate-paymentlink.ts @@ -1,17 +1,18 @@ -import { getClient } from '../client' import { deactivatePaymentLinkInputSchema } from '../misc/custom-schemas' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' export const deactivatePaymentLink: IntegrationProps['actions']['deactivatePaymentLink'] = async ({ ctx, + client, logger, input, }) => { const validatedInput = deactivatePaymentLinkInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { - const paymentLink = await StripeClient.deactivatePaymentLink(validatedInput.id) + const paymentLink = await stripeClient.deactivatePaymentLink(validatedInput.id) response = { id: paymentLink.id, diff --git a/integrations/stripe/src/actions/find-paymentlink.ts b/integrations/stripe/src/actions/find-paymentlink.ts index 45eb1b868bb..24ae276f24d 100644 --- a/integrations/stripe/src/actions/find-paymentlink.ts +++ b/integrations/stripe/src/actions/find-paymentlink.ts @@ -1,13 +1,18 @@ -import { getClient } from '../client' import { findPaymentLinkInputSchema } from '../misc/custom-schemas' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const findPaymentLink: IntegrationProps['actions']['findPaymentLink'] = async ({ ctx, logger, input }) => { +export const findPaymentLink: IntegrationProps['actions']['findPaymentLink'] = async ({ + ctx, + client, + logger, + input, +}) => { const validatedInput = findPaymentLinkInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response = {} try { - const paymentLinks = await StripeClient.listAllPaymentLinksBasic() + const paymentLinks = await stripeClient.listAllPaymentLinksBasic() const paymentLink = paymentLinks.find((link) => link.url === validatedInput.url) if (paymentLink) { diff --git a/integrations/stripe/src/actions/list-customers.ts b/integrations/stripe/src/actions/list-customers.ts index 34418a65199..13e99959c1f 100644 --- a/integrations/stripe/src/actions/list-customers.ts +++ b/integrations/stripe/src/actions/list-customers.ts @@ -1,14 +1,14 @@ import type { Customer } from 'src/misc/custom-types' -import { getClient } from '../client' import { listCustomersInputSchema } from '../misc/custom-schemas' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const listCustomers: IntegrationProps['actions']['listCustomers'] = async ({ ctx, logger, input }) => { +export const listCustomers: IntegrationProps['actions']['listCustomers'] = async ({ ctx, client, logger, input }) => { const validatedInput = listCustomersInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { - const customers = await StripeClient.listAllCustomerBasic(validatedInput.email || undefined) + const customers = await stripeClient.listAllCustomerBasic(validatedInput.email || undefined) const customerByEmails: Record = {} diff --git a/integrations/stripe/src/actions/list-paymentlinks.ts b/integrations/stripe/src/actions/list-paymentlinks.ts index 760fc68eaa1..5aa376a7f08 100644 --- a/integrations/stripe/src/actions/list-paymentlinks.ts +++ b/integrations/stripe/src/actions/list-paymentlinks.ts @@ -1,11 +1,11 @@ -import { getClient } from '../client' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const listPaymentLinks: IntegrationProps['actions']['listPaymentLinks'] = async ({ ctx, logger }) => { - const StripeClient = getClient(ctx.configuration) +export const listPaymentLinks: IntegrationProps['actions']['listPaymentLinks'] = async ({ ctx, client, logger }) => { + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { - const paymentLinks = await StripeClient.listAllPaymentLinksBasic() + const paymentLinks = await stripeClient.listAllPaymentLinksBasic() response = { paymentLinks, diff --git a/integrations/stripe/src/actions/list-product-prices.ts b/integrations/stripe/src/actions/list-product-prices.ts index a5e6d88d4e7..5321210dba6 100644 --- a/integrations/stripe/src/actions/list-product-prices.ts +++ b/integrations/stripe/src/actions/list-product-prices.ts @@ -1,12 +1,12 @@ import type { Product } from 'src/misc/custom-types' -import { getClient } from '../client' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const listProductPrices: IntegrationProps['actions']['listProductPrices'] = async ({ ctx, logger }) => { - const StripeClient = getClient(ctx.configuration) +export const listProductPrices: IntegrationProps['actions']['listProductPrices'] = async ({ ctx, client, logger }) => { + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { - const prices = await StripeClient.listAllPricesBasic(undefined, true) + const prices = await stripeClient.listAllPricesBasic(undefined, true) prices.map((price) => { return { diff --git a/integrations/stripe/src/actions/retrieve-customer.ts b/integrations/stripe/src/actions/retrieve-customer.ts index 6307ca0ab1b..51920881546 100644 --- a/integrations/stripe/src/actions/retrieve-customer.ts +++ b/integrations/stripe/src/actions/retrieve-customer.ts @@ -1,17 +1,18 @@ -import { getClient } from '../client' import { retrieveCustomerByIdInputSchema } from '../misc/custom-schemas' +import { StripeClient } from '../stripe-api/stripe-client' import * as bp from '.botpress' export const retrieveCustomerById: bp.IntegrationProps['actions']['retrieveCustomerById'] = async ({ ctx, + client, logger, input, }) => { const validatedInput = retrieveCustomerByIdInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) try { - const customer = await StripeClient.retrieveCustomer(validatedInput.id) + const customer = await stripeClient.retrieveCustomer(validatedInput.id) if (customer.deleted) { logger.forBot().info(`Customer not found - Retrieve Customer - ${validatedInput.id}`) return {} diff --git a/integrations/stripe/src/actions/search-customers.ts b/integrations/stripe/src/actions/search-customers.ts index 2b028b1a0be..2d591f324fe 100644 --- a/integrations/stripe/src/actions/search-customers.ts +++ b/integrations/stripe/src/actions/search-customers.ts @@ -1,13 +1,18 @@ -import { getClient } from '../client' import { searchCustomersInputSchema } from '../misc/custom-schemas' import type { IntegrationProps } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const searchCustomers: IntegrationProps['actions']['searchCustomers'] = async ({ ctx, logger, input }) => { +export const searchCustomers: IntegrationProps['actions']['searchCustomers'] = async ({ + ctx, + client, + logger, + input, +}) => { const validatedInput = searchCustomersInputSchema.parse(input) - const StripeClient = getClient(ctx.configuration) + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) let response try { - const customers = await StripeClient.searchCustomers( + const customers = await stripeClient.searchCustomers( validatedInput.email, validatedInput.name, validatedInput.phone diff --git a/integrations/stripe/src/index.ts b/integrations/stripe/src/index.ts index e1bb471cdef..ddc2d752305 100644 --- a/integrations/stripe/src/index.ts +++ b/integrations/stripe/src/index.ts @@ -1,4 +1,6 @@ +import { isOAuthWizardUrl } from '@botpress/common/src/oauth-wizard' import actions from './actions' +import { oauthWizardHandler } from './oauth-wizard' import { register, unregister, handler } from './setup' import * as bp from '.botpress' @@ -6,6 +8,11 @@ export default new bp.Integration({ register, unregister, actions, - handler, channels: {}, + handler: async (props) => { + if (isOAuthWizardUrl(props.req.path)) { + return await oauthWizardHandler(props) + } + return await handler(props) + }, }) diff --git a/integrations/stripe/src/misc/stripe-client.ts b/integrations/stripe/src/misc/stripe-client.ts index 9c13cf56100..0515919fb51 100644 --- a/integrations/stripe/src/misc/stripe-client.ts +++ b/integrations/stripe/src/misc/stripe-client.ts @@ -33,57 +33,3 @@ export type WebhookBasic = { id: string url: string } - -export type StripeClient = { - createProduct(name: string): Promise - - listProducts(startingAfter?: string): Promise> - - listAllProductsBasic(startingAfter?: string): Promise - - createPrice(product: string, unit_amount: number, currency: string): Promise - - createSubsPrice( - product: string, - unit_amount: number, - currency: string, - recurring: Stripe.PriceCreateParams.Recurring - ): Promise - - listPrices(productId?: string, isExpand?: boolean, startingAfter?: string): Promise> - - listAllPricesBasic(productId?: string, isExpand?: boolean, startingAfter?: string): Promise - - createPaymentLink(lineItem: Stripe.PaymentLinkCreateParams.LineItem): Promise - - listPaymentLink(startingAfter?: string): Promise> - - listAllPaymentLinksBasic(startingAfter?: string): Promise - - createSubsLink( - lineItem: Stripe.PaymentLinkCreateParams.LineItem, - subscriptionData: Stripe.PaymentLinkCreateParams.SubscriptionData | undefined - ): Promise - - deactivatePaymentLink(paymentLinkId: string): Promise - - listCustomer(email?: string, startingAfter?: string): Promise> - - listAllCustomerBasic(email?: string, startingAfter?: string): Promise - - searchCustomers(email?: string, name?: string, phone?: string): Promise - - createCustomer(params: Stripe.CustomerCreateParams): Promise - - createWebhook(webhookData: Stripe.WebhookEndpointCreateParams): Promise - - listWebhooks(startingAfter?: string): Promise> - - listAllWebhooksBasic(startingAfter?: string): Promise - - createOrRetrieveWebhookId(webhookData: Stripe.WebhookEndpointCreateParams): Promise - - deleteWebhook(webhookId: string): Promise - - retrieveCustomer(id: string): Promise -} diff --git a/integrations/stripe/src/oauth-wizard/index.ts b/integrations/stripe/src/oauth-wizard/index.ts new file mode 100644 index 00000000000..95a2a8bd3ac --- /dev/null +++ b/integrations/stripe/src/oauth-wizard/index.ts @@ -0,0 +1,24 @@ +import { generateRedirection } from '@botpress/common/src/html-dialogs' +import { isOAuthWizardUrl, getInterstitialUrl } from '@botpress/common/src/oauth-wizard' +import * as wizard from './wizard' +import * as bp from '.botpress' + +export const oauthWizardHandler: bp.IntegrationProps['handler'] = async (props) => { + const { req, logger } = props + + if (!isOAuthWizardUrl(req.path)) { + return { + status: 404, + body: 'Invalid OAuth wizard endpoint', + } + } + + try { + return await wizard.handler(props) + } catch (thrown: unknown) { + const error = thrown instanceof Error ? thrown : Error(String(thrown)) + const errorMessage = 'OAuth wizard error: ' + error.message + logger.forBot().error(errorMessage) + return generateRedirection(getInterstitialUrl(false, errorMessage)) + } +} diff --git a/integrations/stripe/src/oauth-wizard/wizard.ts b/integrations/stripe/src/oauth-wizard/wizard.ts new file mode 100644 index 00000000000..6c2cad80650 --- /dev/null +++ b/integrations/stripe/src/oauth-wizard/wizard.ts @@ -0,0 +1,115 @@ +import * as oauthWizard from '@botpress/common/src/oauth-wizard' +import { Response, z } from '@botpress/sdk' +import { StripeOAuthClient } from '../stripe-api/stripe-oauth-client' +import * as bp from '.botpress' + +type WizardHandler = oauthWizard.WizardStepHandler + +const _getRedirectUri = () => `${process.env.BP_WEBHOOK_URL}/oauth/wizard/oauth-callback` + +const _buildStripeAuthorizeUrl = ({ webhookId }: { webhookId: string }): string => { + const params = new URLSearchParams({ + client_id: bp.secrets.CLIENT_ID, + redirect_uri: _getRedirectUri(), + response_type: 'code', + state: webhookId, + }) + return `https://marketplace.stripe.com/oauth/v2/authorize?${params.toString()}` +} + +const _manualCredentialsSchema = z.object({ + apiKey: z + .string() + .secret() + .min(1) + .title('Stripe API Key') + .describe('Your Stripe Secret Key (sk_live_/sk_test_) or a Restricted Key'), +}) + +const _manualCredentialsForm = { + pageTitle: 'Stripe API Key', + htmlOrMarkdownPageContents: + 'Enter a Stripe Secret Key (or Restricted Key). You can create one at https://dashboard.stripe.com/apikeys.', + schema: _manualCredentialsSchema, + nextStepId: 'save-manual-credentials', +} + +export const handler = async (props: bp.HandlerProps): Promise => { + const wizard = new oauthWizard.OAuthWizardBuilder(props) + .addStep({ id: 'start', handler: _startHandler }) + .addStep({ id: 'route-choice', handler: _routeChoiceHandler }) + .addStep({ id: 'oauth-redirect', handler: _oauthRedirectHandler }) + .addStep({ id: 'oauth-callback', handler: _oauthCallbackHandler }) + .addStep({ id: 'get-manual-credentials', handler: _getManualCredentialsHandler }) + .addStep({ id: 'save-manual-credentials', handler: _saveManualCredentialsHandler }) + .build() + + return await wizard.handleRequest() +} + +const _startHandler: WizardHandler = ({ responses }) => { + return responses.displayChoices({ + pageTitle: 'Stripe Integration Setup', + htmlOrMarkdownPageContents: 'Choose how you would like to configure your Stripe integration:', + choices: [ + { label: 'Connect with OAuth', value: 'oauth' }, + { label: 'Use a Stripe API Key', value: 'manual' }, + ], + nextStepId: 'route-choice', + }) +} + +const _routeChoiceHandler: WizardHandler = ({ selectedChoice, responses }) => { + switch (selectedChoice) { + case 'manual': + return responses.redirectToStep('get-manual-credentials') + case 'oauth': + default: + return responses.redirectToStep('oauth-redirect') + } +} + +const _oauthRedirectHandler: WizardHandler = async ({ ctx, responses }) => { + return responses.redirectToExternalUrl(_buildStripeAuthorizeUrl({ webhookId: ctx.webhookId })) +} + +const _oauthCallbackHandler: WizardHandler = async ({ ctx, client, logger, responses, query }) => { + const code = query.get('code') + if (!code) { + return responses.endWizard({ success: false, errorMessage: 'Stripe did not return an authorization code' }) + } + + const state = query.get('state') + if (!state || state !== ctx.webhookId) { + return responses.endWizard({ success: false, errorMessage: 'Invalid OAuth state parameter' }) + } + + const oauth = new StripeOAuthClient({ client, ctx, logger }) + await oauth.requestShortLivedCredentials.fromAuthorizationCode(code) + + return responses.endWizard({ success: true }) +} + +const _getManualCredentialsHandler: WizardHandler = ({ responses }) => { + return responses.displayForm(_manualCredentialsForm) +} + +const _saveManualCredentialsHandler: WizardHandler = async ({ ctx, client, logger, formValues, responses }) => { + if (!formValues) { + return responses.redirectToStep('get-manual-credentials') + } + + const parsed = _manualCredentialsSchema.safeParse(formValues) + if (!parsed.success) { + return responses.displayForm({ + ..._manualCredentialsForm, + errors: parsed.error, + previousValues: formValues as z.input, + }) + } + + const oauth = new StripeOAuthClient({ client, ctx, logger }) + await oauth.saveManualApiKey(parsed.data.apiKey) + + return responses.endWizard({ success: true }) +} diff --git a/integrations/stripe/src/setup/handler.ts b/integrations/stripe/src/setup/handler.ts index b11e0386664..4449bfede16 100644 --- a/integrations/stripe/src/setup/handler.ts +++ b/integrations/stripe/src/setup/handler.ts @@ -8,13 +8,93 @@ import { fireSubscriptionScheduleUpdated } from 'src/events/subscription-schedul import { fireSubscriptionUpdated } from 'src/events/subscription-updated' import Stripe from 'stripe' import type { Handler } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' +import { ENABLED_EVENTS } from './register' -export const handler: Handler = async ({ req, client, logger }) => { +// Stand-in Stripe instance only used for webhook signature verification (no API calls). +// @ts-ignore +const _signatureVerifier = new Stripe('placeholder', { apiVersion: '2023-08-16' }) + +const _recreateWebhookWithSecret = async ({ + client, + ctx, + logger, + priorWebhookId, +}: { + client: Parameters[0]['client'] + ctx: Parameters[0]['ctx'] + logger: Parameters[0]['logger'] + priorWebhookId?: string +}) => { + if (!process.env.BP_WEBHOOK_URL) { + throw new Error('BP_WEBHOOK_URL is not configured') + } + + const stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) + const { id: stripeWebhookId, secret: stripeWebhookSecret } = await stripeClient.createWebhookEndpointWithSecret({ + url: `${process.env.BP_WEBHOOK_URL}/${ctx.webhookId}`, + enabled_events: ENABLED_EVENTS, + }) + + await client.setState({ + type: 'integration', + id: ctx.integrationId, + name: 'stripeIntegrationInfo', + payload: { stripeWebhookId, stripeWebhookSecret }, + }) + + if (priorWebhookId) { + try { + await stripeClient.deleteWebhook(priorWebhookId) + } catch (error) { + logger.forBot().warn(`Failed to delete legacy Stripe webhook ${priorWebhookId}`, error) + } + } +} + +export const handler: Handler = async ({ req, client, ctx, logger }) => { if (!req.body) { - console.warn('Handler received an empty body') - return + logger.forBot().warn('Stripe webhook handler received an empty body') + return { status: 400, body: 'Empty body' } + } + + const sigHeader = req.headers['stripe-signature'] + if (!sigHeader) { + logger.forBot().warn('Stripe webhook missing stripe-signature header') + return { status: 400, body: 'Missing stripe-signature header' } + } + + const integrationInfo = await client + .getState({ type: 'integration', id: ctx.integrationId, name: 'stripeIntegrationInfo' }) + .then(({ state }) => state.payload) + .catch(() => undefined) + const webhookSecret = integrationInfo?.stripeWebhookSecret + + if (!webhookSecret) { + try { + await _recreateWebhookWithSecret({ + client, + ctx, + logger, + priorWebhookId: integrationInfo?.stripeWebhookId, + }) + logger.forBot().warn('Recreated Stripe webhook because the stored signing secret was missing') + return { status: 202, body: 'Stripe webhook recreated; delivery skipped because it cannot be verified' } + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + logger.forBot().error(`No Stripe webhook signing secret found and migration failed: ${message}`) + return { status: 500, body: 'Webhook signing secret not configured' } + } + } + + let stripeEvent: Stripe.Event + try { + stripeEvent = _signatureVerifier.webhooks.constructEvent(req.body, sigHeader, webhookSecret) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + logger.forBot().warn(`Stripe webhook signature verification failed: ${message}`) + return { status: 400, body: 'Invalid signature' } } - const stripeEvent = JSON.parse(req.body) as Stripe.Event switch (stripeEvent.type) { case 'charge.failed': @@ -42,6 +122,8 @@ export const handler: Handler = async ({ req, client, logger }) => { await fireSubscriptionScheduleUpdated({ stripeEvent, client, logger }) break default: - console.warn(`Unhandled event type ${stripeEvent.type}`) + logger.forBot().warn(`Unhandled Stripe event type ${stripeEvent.type}`) } + + return } diff --git a/integrations/stripe/src/setup/register.ts b/integrations/stripe/src/setup/register.ts index e0506b6320a..592a33ed319 100644 --- a/integrations/stripe/src/setup/register.ts +++ b/integrations/stripe/src/setup/register.ts @@ -1,36 +1,63 @@ +import * as sdk from '@botpress/sdk' import Stripe from 'stripe' -import { getClient } from '../client' import type { RegisterFunction } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' -export const register: RegisterFunction = async ({ ctx, client, webhookUrl, logger }) => { - const StripeClient = getClient(ctx.configuration) +export const ENABLED_EVENTS: Stripe.WebhookEndpointCreateParams.EnabledEvent[] = [ + 'charge.failed', + 'customer.subscription.deleted', + 'customer.subscription.updated', + 'customer.subscription.created', + 'invoice.payment_failed', + 'payment_intent.payment_failed', + 'subscription_schedule.created', + 'subscription_schedule.updated', +] - const webhookData: Stripe.WebhookEndpointCreateParams = { - url: webhookUrl, - enabled_events: [ - 'charge.failed', - 'customer.subscription.deleted', - 'customer.subscription.updated', - 'customer.subscription.created', - 'invoice.payment_failed', - 'payment_intent.payment_failed', - ], +export const register: RegisterFunction = async ({ ctx, client, webhookUrl, logger }) => { + let stripeClient: StripeClient + try { + stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + throw new sdk.RuntimeError(`Failed to load Stripe credentials. Re-run the setup wizard. (${message})`) } - let stripeWebhookId + let accountId: string try { - stripeWebhookId = await StripeClient.createOrRetrieveWebhookId(webhookData) + const account = await stripeClient.retrieveAccount() + accountId = account.id } catch (error) { - logger.forBot().warn('error creating the integration in Stripe', error) - return + const message = error instanceof Error ? error.message : String(error) + throw new sdk.RuntimeError(`Failed to connect to Stripe. (${message})`) } + await client.configureIntegration({ identifier: accountId }) + + const prior = await client + .getState({ type: 'integration', id: ctx.integrationId, name: 'stripeIntegrationInfo' }) + .then(({ state }) => state.payload) + .catch(() => undefined) + + const { id: stripeWebhookId, secret: stripeWebhookSecret } = await stripeClient.createWebhookEndpointWithSecret({ + url: webhookUrl, + enabled_events: ENABLED_EVENTS, + }) + await client.setState({ type: 'integration', id: ctx.integrationId, name: 'stripeIntegrationInfo', - payload: { - stripeWebhookId, - }, + payload: { stripeWebhookId, stripeWebhookSecret }, }) + + if (prior?.stripeWebhookId && prior.stripeWebhookId !== stripeWebhookId) { + try { + await stripeClient.deleteWebhook(prior.stripeWebhookId) + } catch (error) { + logger.forBot().warn(`Failed to delete prior Stripe webhook ${prior.stripeWebhookId}`, error) + } + } + + logger.forBot().info('Connection to Stripe successful') } diff --git a/integrations/stripe/src/setup/unregister.ts b/integrations/stripe/src/setup/unregister.ts index 3f422a30f64..f4fbe9fb4e5 100644 --- a/integrations/stripe/src/setup/unregister.ts +++ b/integrations/stripe/src/setup/unregister.ts @@ -1,17 +1,24 @@ -import { getClient } from '../client' import type { UnregisterFunction } from '../misc/types' +import { StripeClient } from '../stripe-api/stripe-client' export const unregister: UnregisterFunction = async ({ ctx, client, logger }) => { - const StripeClient = getClient(ctx.configuration) - const stateStripeIntegrationInfo = await client.getState({ - id: ctx.integrationId, - name: 'stripeIntegrationInfo', - type: 'integration', - }) - const { state } = stateStripeIntegrationInfo - const { stripeWebhookId } = state.payload + let stripeClient: StripeClient + try { + stripeClient = await StripeClient.createFromStates({ client, ctx, logger }) + } catch { + logger.forBot().warn('No Stripe credentials available; skipping webhook teardown') + return + } + + const stateStripeIntegrationInfo = await client + .getState({ id: ctx.integrationId, name: 'stripeIntegrationInfo', type: 'integration' }) + .catch(() => undefined) + + if (!stateStripeIntegrationInfo) return + + const { stripeWebhookId } = stateStripeIntegrationInfo.state.payload if (stripeWebhookId) { - const response = await StripeClient.deleteWebhook(stripeWebhookId) + const response = await stripeClient.deleteWebhook(stripeWebhookId) if (response.deleted) { logger.forBot().info(`Webhook successfully deleted - ${response.id}`) } diff --git a/integrations/stripe/src/client.ts b/integrations/stripe/src/stripe-api/stripe-client.ts similarity index 57% rename from integrations/stripe/src/client.ts rename to integrations/stripe/src/stripe-api/stripe-client.ts index d065f64aeba..6603feda1d2 100644 --- a/integrations/stripe/src/client.ts +++ b/integrations/stripe/src/stripe-api/stripe-client.ts @@ -1,36 +1,64 @@ import Stripe from 'stripe' -import { Configuration } from './misc/types' +import { StripeOAuthClient } from './stripe-oauth-client' +import * as bp from '.botpress' -export class StripeApi { - private _stripe: Stripe +type CreateProps = { + client: bp.Client + ctx: bp.Context + logger: bp.Logger +} + +type LegacyConfiguration = bp.Context['configuration'] & { + apiKey?: string +} + +export class StripeClient { + protected _stripe: Stripe public constructor(apiKey: string, apiVersion?: string) { // @ts-ignore this._stripe = new Stripe(apiKey, { apiVersion: apiVersion || '2023-08-16' }) } + public static async createFromStates({ client, ctx, logger }: CreateProps): Promise { + const oauth = new StripeOAuthClient({ client, ctx, logger }) + + // essential for old API key support + let accessToken: string + try { + accessToken = (await oauth.getAuthState()).accessToken + } catch (error) { + const legacyApiKey = (ctx.configuration as LegacyConfiguration).apiKey + if (!legacyApiKey) { + throw error + } + + await oauth.saveManualApiKey(legacyApiKey) + accessToken = legacyApiKey + } + return new StripeClient(accessToken, ctx.configuration.apiVersion) + } + + public async retrieveAccount(): Promise { + return await this._stripe.accounts.retrieve() + } + public async createProduct(name: string) { - const product = await this._stripe.products.create({ - name, - }) + const product = await this._stripe.products.create({ name }) return product } public async listProducts(startingAfter?: string) { - const products = await this._stripe.products.list({ + return await this._stripe.products.list({ active: true, limit: 100, starting_after: startingAfter, }) - - return products } public async listAllProductsBasic(startingAfter?: string) { let products = await this.listProducts(startingAfter) - const productsBasic = products.data.map((product) => { - return { id: product.id, name: product.name } - }) + const productsBasic = products.data.map((product) => ({ id: product.id, name: product.name })) while (products.has_more) { products = await this.listProducts(productsBasic[productsBasic.length - 1]?.id) for (const product of products.data) { @@ -41,12 +69,7 @@ export class StripeApi { } public async createPrice(product: string, unit_amount: number, currency: string) { - const price = await this._stripe.prices.create({ - product, - unit_amount, - currency, - }) - return price + return await this._stripe.prices.create({ product, unit_amount, currency }) } public async createSubsPrice( @@ -55,40 +78,31 @@ export class StripeApi { currency: string, recurring: Stripe.PriceCreateParams.Recurring ) { - const price = await this._stripe.prices.create({ - product, - unit_amount, - currency, - recurring, - }) - return price + return await this._stripe.prices.create({ product, unit_amount, currency, recurring }) } public async listPrices(productId?: string, isExpand: boolean = false, startingAfter?: string) { - const prices = await this._stripe.prices.list({ + return await this._stripe.prices.list({ active: true, limit: 100, product: productId, expand: isExpand ? ['data.product'] : undefined, starting_after: startingAfter, }) - return prices } public async listAllPricesBasic(productId?: string, isExpand: boolean = false, startingAfter?: string) { let prices = await this.listPrices(productId, isExpand, startingAfter) - const pricesBasic = prices.data.map((price) => { - return { - id: price.id, - unit_amount: price.unit_amount, - currency: price.currency, - recurring: price.recurring || undefined, - product: { - id: (price.product as Stripe.Product).id, - name: (price.product as Stripe.Product).name, - }, - } - }) + const pricesBasic = prices.data.map((price) => ({ + id: price.id, + unit_amount: price.unit_amount, + currency: price.currency, + recurring: price.recurring || undefined, + product: { + id: (price.product as Stripe.Product).id, + name: (price.product as Stripe.Product).name, + }, + })) while (prices.has_more) { prices = await this.listPrices(productId, isExpand, pricesBasic[pricesBasic.length - 1]?.id) for (const price of prices.data) { @@ -108,26 +122,23 @@ export class StripeApi { } public async createPaymentLink(lineItem: Stripe.PaymentLinkCreateParams.LineItem) { - const paymentLink = await this._stripe.paymentLinks.create({ - line_items: [lineItem], - }) - return paymentLink + return await this._stripe.paymentLinks.create({ line_items: [lineItem] }) } public async listPaymentLink(startingAfter?: string) { - const paymentLink = await this._stripe.paymentLinks.list({ + return await this._stripe.paymentLinks.list({ active: true, limit: 100, starting_after: startingAfter, }) - return paymentLink } public async listAllPaymentLinksBasic(startingAfter?: string) { let paymentLinks = await this.listPaymentLink(startingAfter) - const paymentLinksBasic = paymentLinks.data.map((paymentLink) => { - return { id: paymentLink.id, url: paymentLink.url } - }) + const paymentLinksBasic = paymentLinks.data.map((paymentLink) => ({ + id: paymentLink.id, + url: paymentLink.url, + })) while (paymentLinks.has_more) { paymentLinks = await this.listPaymentLink(paymentLinksBasic[paymentLinksBasic.length - 1]?.id) for (const paymentLink of paymentLinks.data) { @@ -141,43 +152,32 @@ export class StripeApi { lineItem: Stripe.PaymentLinkCreateParams.LineItem, subscriptionData: Stripe.PaymentLinkCreateParams.SubscriptionData | undefined ) { - const paymentLink = await this._stripe.paymentLinks.create({ + return await this._stripe.paymentLinks.create({ line_items: [lineItem], subscription_data: subscriptionData, }) - return paymentLink } public async deactivatePaymentLink(paymentLinkId: string) { - const paymentLink = this._stripe.paymentLinks.update(paymentLinkId, { - active: false, - }) - return paymentLink + return this._stripe.paymentLinks.update(paymentLinkId, { active: false }) } public async listCustomer(email?: string, startingAfter?: string) { - const customers = await this._stripe.customers.list({ - email, - limit: 100, - starting_after: startingAfter, - }) - return customers + return await this._stripe.customers.list({ email, limit: 100, starting_after: startingAfter }) } public async listAllCustomerBasic(email?: string, startingAfter?: string) { let customers = await this.listCustomer(email, startingAfter) - const customersBasic = customers.data.map((customer) => { - return { - id: customer.id, - email: customer.email, - name: customer.name, - description: customer.description, - phone: customer.phone, - address: customer.address, - created: customer.created, - delinquent: customer.delinquent, - } - }) + const customersBasic = customers.data.map((customer) => ({ + id: customer.id, + email: customer.email, + name: customer.name, + description: customer.description, + phone: customer.phone, + address: customer.address, + created: customer.created, + delinquent: customer.delinquent, + })) while (customers.has_more) { customers = await this.listCustomer(email, customersBasic[customersBasic.length - 1]?.id) for (const customer of customers.data) { @@ -198,99 +198,61 @@ export class StripeApi { public async searchCustomers(email?: string, name?: string, phone?: string) { const queryParts: string[] = [] - - if (email) { - queryParts.push(`email~'${email}'`) - } - - if (name) { - queryParts.push(`name~'${name}'`) - } - - if (phone) { - queryParts.push(`phone~'${phone}'`) - } - + if (email) queryParts.push(`email~'${email}'`) + if (name) queryParts.push(`name~'${name}'`) + if (phone) queryParts.push(`phone~'${phone}'`) const query = queryParts.join(' AND ') const limit = 100 - let response = await this._stripe.customers.search({ - query, - limit, - }) - + let response = await this._stripe.customers.search({ query, limit }) const customers = response.data while (response.has_more) { const page = response.next_page || undefined - response = await this._stripe.customers.search({ - query, - limit, - page, - }) + response = await this._stripe.customers.search({ query, limit, page }) customers.push(...response.data) } return customers } public async createCustomer(params: Stripe.CustomerCreateParams) { - const customer = await this._stripe.customers.create(params) - return customer + return await this._stripe.customers.create(params) } public async createWebhook(webhookData: Stripe.WebhookEndpointCreateParams) { - const webhook = await this._stripe.webhookEndpoints.create(webhookData) - return webhook + return await this._stripe.webhookEndpoints.create(webhookData) } public async listWebhooks(startingAfter?: string) { - const webhooks = await this._stripe.webhookEndpoints.list({ - limit: 100, - starting_after: startingAfter, - }) - - return webhooks + return await this._stripe.webhookEndpoints.list({ limit: 100, starting_after: startingAfter }) } public async listAllWebhooksBasic(startingAfter?: string) { let webhooks = await this.listWebhooks(startingAfter) - const webhooksBasic = webhooks.data.map((webhook) => { - return { - id: webhook.id, - url: webhook.url, - } - }) + const webhooksBasic = webhooks.data.map((webhook) => ({ id: webhook.id, url: webhook.url })) while (webhooks.has_more) { webhooks = await this.listWebhooks(webhooksBasic[webhooksBasic.length - 1]?.id) for (const webhook of webhooks.data) { - webhooksBasic.push({ - id: webhook.id, - url: webhook.url, - }) + webhooksBasic.push({ id: webhook.id, url: webhook.url }) } } return webhooksBasic } - public async createOrRetrieveWebhookId(webhookData: Stripe.WebhookEndpointCreateParams) { - const webhooks = await this.listAllWebhooksBasic() - let webhook = webhooks.find((w) => w.url === webhookData.url) - if (!webhook) { - webhook = await this.createWebhook(webhookData) + public async createWebhookEndpointWithSecret( + webhookData: Stripe.WebhookEndpointCreateParams + ): Promise<{ id: string; secret: string }> { + const webhook = await this.createWebhook(webhookData) + if (!webhook.secret) { + throw new Error('Stripe did not return a webhook signing secret on creation') } - return webhook.id + return { id: webhook.id, secret: webhook.secret } } public async deleteWebhook(webhookId: string) { - const response = await this._stripe.webhookEndpoints.del(webhookId) - return response + return await this._stripe.webhookEndpoints.del(webhookId) } public async retrieveCustomer(id: string) { - const customer = await this._stripe.customers.retrieve(id) - return customer + return await this._stripe.customers.retrieve(id) } } - -export function getClient(config: Configuration) { - return new StripeApi(config.apiKey, config.apiVersion) -} diff --git a/integrations/stripe/src/stripe-api/stripe-oauth-client.ts b/integrations/stripe/src/stripe-api/stripe-oauth-client.ts new file mode 100644 index 00000000000..5c0d9467cd4 --- /dev/null +++ b/integrations/stripe/src/stripe-api/stripe-oauth-client.ts @@ -0,0 +1,258 @@ +import * as sdk from '@botpress/sdk' +import * as bp from '.botpress' + +type StripeTokenResponse = { + access_token: string + refresh_token: string + token_type: string + stripe_user_id: string + stripe_publishable_key?: string + livemode: boolean + scope?: string + expires_in?: number + refresh_expires_in?: number +} + +type PrivateAuthState = { + readonly accessToken: { + readonly expiresAt: Date + readonly token: string + } + readonly refreshToken: { + readonly expiresAt: Date + readonly token: string + } + readonly scopes: string[] + readonly stripeUserId: string + readonly livemode: boolean +} + +type PublicAuthState = { + readonly accessToken: string + readonly scopes: string[] + readonly stripeUserId?: string + readonly livemode?: boolean +} + +const STRIPE_TOKEN_URL = 'https://api.stripe.com/v1/oauth/token' +const MINIMUM_TOKEN_VALIDITY_SECONDS = 3_600 +const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 3_600 +const DEFAULT_REFRESH_TOKEN_TTL_SECONDS = 365 * 24 * 60 * 60 + +export class StripeOAuthClient { + private readonly _client: bp.Client + private readonly _ctx: bp.Context + private readonly _logger: bp.Logger + private readonly _clientSecret: string + private _currentAuthState: PrivateAuthState | undefined = undefined + + public constructor({ + ctx, + client, + logger, + clientSecretOverride, + }: { + client: bp.Client + ctx: bp.Context + logger: bp.Logger + clientSecretOverride?: string + }) { + this._clientSecret = clientSecretOverride ?? bp.secrets.CLIENT_SECRET + this._client = client + this._ctx = ctx + this._logger = logger + } + + public async getAuthState(): Promise { + const manual = await this._getManualCredentialsState() + if (manual && manual.apiKey !== '') { + return { accessToken: manual.apiKey, scopes: [] } + } + + await this._refreshAuthStateIfNeeded() + + if (!this._currentAuthState) { + throw new sdk.RuntimeError('No Stripe credentials found. Re-run the integration setup wizard.') + } + + return { + accessToken: this._currentAuthState.accessToken.token, + scopes: this._currentAuthState.scopes, + stripeUserId: this._currentAuthState.stripeUserId, + livemode: this._currentAuthState.livemode, + } + } + + public readonly requestShortLivedCredentials = { + fromAuthorizationCode: async (authorizationCode: string) => { + this._logger.forBot().debug('Exchanging Stripe authorization code for credentials...') + const response = await this._postToken({ grant_type: 'authorization_code', code: authorizationCode }) + this._currentAuthState = this._parseStripeTokenResponse(response) + await this._saveOAuthCredentials() + await this._clearManualCredentials() + this._logger.forBot().debug('Successfully exchanged Stripe authorization code') + }, + + fromRefreshToken: async (refreshToken: string) => { + this._logger.forBot().debug('Refreshing Stripe access token...') + const response = await this._postToken({ grant_type: 'refresh_token', refresh_token: refreshToken }) + this._currentAuthState = this._parseStripeTokenResponse(response) + await this._saveOAuthCredentials() + this._logger.forBot().debug('Successfully refreshed Stripe access token') + }, + } + + public async saveManualApiKey(apiKey: string): Promise { + await this._client.setState({ + type: 'integration', + name: 'manualCredentials', + id: this._ctx.integrationId, + payload: { apiKey }, + }) + await this._clearOAuthCredentials() + } + + private async _postToken(body: Record): Promise { + const basicAuth = Buffer.from(`${this._clientSecret}:`).toString('base64') + const response = await fetch(STRIPE_TOKEN_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${basicAuth}`, + }, + body: new URLSearchParams(body).toString(), + }) + + if (!response.ok) { + const errorBody = await response.text() + this._logger.forBot().error(`Stripe token endpoint returned ${response.status}: ${errorBody}`) + throw new sdk.RuntimeError(`Stripe token request failed (${response.status})`) + } + + return (await response.json()) as StripeTokenResponse + } + + private async _refreshAuthStateIfNeeded(): Promise { + if (this._isTokenStillValid()) { + return + } + + const credentials = await this._getOAuthCredentialsState() + if (!credentials) { + throw new sdk.RuntimeError('No Stripe credentials found. Re-run the integration setup wizard.') + } + + await this._refreshAuth(credentials) + } + + private _isTokenStillValid() { + return this._currentAuthState && this._currentAuthState.accessToken.expiresAt > this._getMinExpiryDate() + } + + private _getMinExpiryDate() { + return new Date(Date.now() + MINIMUM_TOKEN_VALIDITY_SECONDS * 1000) + } + + private async _getManualCredentialsState() { + return this._client + .getState({ type: 'integration', id: this._ctx.integrationId, name: 'manualCredentials' }) + .then(({ state }) => state.payload) + .catch(() => undefined) + } + + private async _getOAuthCredentialsState() { + return this._client + .getState({ type: 'integration', id: this._ctx.integrationId, name: 'oAuthCredentials' }) + .then(({ state }) => state.payload) + .catch(() => undefined) + } + + private async _refreshAuth(credentials: bp.states.oAuthCredentials.OAuthCredentials['payload']) { + const accessTokenExpiresAt = new Date(credentials.expiresAt) + const refreshTokenExpiresAt = new Date(credentials.refreshExpiresAt) + + if (refreshTokenExpiresAt <= new Date()) { + throw new sdk.RuntimeError('Stripe refresh token has expired. Please re-run the integration setup wizard.') + } + + if (accessTokenExpiresAt > new Date()) { + this._currentAuthState = { + accessToken: { expiresAt: accessTokenExpiresAt, token: credentials.accessToken }, + refreshToken: { expiresAt: refreshTokenExpiresAt, token: credentials.refreshToken }, + scopes: credentials.scopes, + stripeUserId: credentials.stripeUserId, + livemode: credentials.livemode, + } + return + } + + await this.requestShortLivedCredentials.fromRefreshToken(credentials.refreshToken) + } + + private _parseStripeTokenResponse(response: StripeTokenResponse): PrivateAuthState { + if (!response.access_token || !response.refresh_token || !response.stripe_user_id) { + this._logger.forBot().error('Stripe OAuth response is missing required fields') + throw new sdk.RuntimeError('Stripe OAuth response is missing required fields') + } + + const now = Date.now() + const accessTtl = response.expires_in ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS + const refreshTtl = response.refresh_expires_in ?? DEFAULT_REFRESH_TOKEN_TTL_SECONDS + + return { + accessToken: { expiresAt: new Date(now + accessTtl * 1000), token: response.access_token }, + refreshToken: { expiresAt: new Date(now + refreshTtl * 1000), token: response.refresh_token }, + scopes: response.scope ? response.scope.split(' ') : [], + stripeUserId: response.stripe_user_id, + livemode: response.livemode ?? false, + } + } + + private async _clearManualCredentials() { + await this._client.setState({ + type: 'integration', + name: 'manualCredentials', + id: this._ctx.integrationId, + payload: { apiKey: '' }, + }) + } + + private async _clearOAuthCredentials() { + const epoch = new Date(0).toISOString() + await this._client.setState({ + type: 'integration', + name: 'oAuthCredentials', + id: this._ctx.integrationId, + payload: { + accessToken: '', + refreshToken: '', + expiresAt: epoch, + refreshExpiresAt: epoch, + scopes: [], + stripeUserId: '', + livemode: false, + }, + }) + } + + private async _saveOAuthCredentials() { + if (!this._currentAuthState) { + throw new sdk.RuntimeError('No credentials to save') + } + + await this._client.setState({ + type: 'integration', + name: 'oAuthCredentials', + id: this._ctx.integrationId, + payload: { + accessToken: this._currentAuthState.accessToken.token, + refreshToken: this._currentAuthState.refreshToken.token, + expiresAt: this._currentAuthState.accessToken.expiresAt.toISOString(), + refreshExpiresAt: this._currentAuthState.refreshToken.expiresAt.toISOString(), + scopes: this._currentAuthState.scopes, + stripeUserId: this._currentAuthState.stripeUserId, + livemode: this._currentAuthState.livemode, + }, + }) + } +} diff --git a/integrations/stripe/tsconfig.json b/integrations/stripe/tsconfig.json index d46abc5b88f..4d1aa2db641 100644 --- a/integrations/stripe/tsconfig.json +++ b/integrations/stripe/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../../tsconfig.json", "compilerOptions": { "paths": { "*": ["./*"] }, - "outDir": "dist" + "outDir": "dist", + "jsx": "react-jsx", + "jsxImportSource": "preact" }, "include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"] } diff --git a/packages/cli/package.json b/packages/cli/package.json index 488fd208f32..8be24db08f3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/cli", - "version": "6.8.2", + "version": "6.8.3", "description": "Botpress CLI", "scripts": { "build": "pnpm run build:types && pnpm run bundle && pnpm run template:gen", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 99c1a6d092e..cc500ba0f38 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -18,9 +18,11 @@ const onError = (thrown: unknown) => { process.exit(1) } -const yargsFail = (msg: string) => { +const yargsFail = (msg?: string) => { // usage errors are bad input, not crashes; show the clean message and help, never a stack. - new Logger().error(`${msg}\n`) + if (msg !== undefined) { + new Logger().error(`${msg}\n`) + } yargs.showHelp() process.exit(1) } diff --git a/packages/cli/src/register-yargs.ts b/packages/cli/src/register-yargs.ts index 195ba5b01e2..e4f3c01e617 100644 --- a/packages/cli/src/register-yargs.ts +++ b/packages/cli/src/register-yargs.ts @@ -17,7 +17,7 @@ export const registerYargs = (yargz: YargsInstance, commands: tree.CommandTree) if (tree.guards.command.isSubTree(command)) { yargz.command(cmdName, command.description ?? cmdName, (y) => { registerYargs(y, command.subcommands) - return y + return y.demandCommand(1) }) continue } diff --git a/packages/cognitive/package.json b/packages/cognitive/package.json index d90befc53df..b87d895a8f2 100644 --- a/packages/cognitive/package.json +++ b/packages/cognitive/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/cognitive", - "version": "0.5.5", + "version": "0.6.0", "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 c4fc32ef104..c115f4099de 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.79", + "version": "0.0.80", "types": "./dist/index.d.ts", "main": "./dist/index.cjs", "module": "./dist/index.js", @@ -72,9 +72,9 @@ }, "peerDependencies": { "@botpress/client": "1.46.0", - "@botpress/cognitive": "0.5.5", + "@botpress/cognitive": "0.6.0", "@bpinternal/thicktoken": "^2.0.0", - "@bpinternal/zui": "^2.2.1" + "@bpinternal/zui": "^2.3.0" }, "dependenciesMeta": { "@bpinternal/zui": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index f937b32c8cd..fa89d0de8f3 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -31,7 +31,7 @@ "tsup": "^8.0.2" }, "peerDependencies": { - "@bpinternal/zui": "^2.2.1", + "@bpinternal/zui": "^2.3.0", "esbuild": "^0.16.12" }, "peerDependenciesMeta": { diff --git a/packages/vai/package.json b/packages/vai/package.json index c4166d0829f..bc238e5191c 100644 --- a/packages/vai/package.json +++ b/packages/vai/package.json @@ -42,7 +42,7 @@ "peerDependencies": { "@botpress/client": "1.46.0", "@bpinternal/thicktoken": "^1.0.1", - "@bpinternal/zui": "^2.2.1", + "@bpinternal/zui": "^2.3.0", "lodash": "^4.17.21", "vitest": "^2 || ^3 || ^4 || ^5" }, diff --git a/packages/zai/package.json b/packages/zai/package.json index 160fc166187..a50ecbb3581 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.6.22", + "version": "2.6.23", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { @@ -16,8 +16,8 @@ "check:type": "tsc --noEmit", "build": "bp add -y && pnpm run build:types && pnpm run build:neutral && size-limit", "build:neutral": "ts-node -T ./build.ts", - "build:types": "tsup", - "watch": "tsup --watch", + "build:types": "tsdown", + "watch": "tsdown --watch", "test:e2e": "vitest run --config vitest.config.ts", "test:e2e:update": "vitest -u run --config vitest.config.ts", "test:e2e:watch": "vitest --config vitest.config.ts" @@ -32,7 +32,7 @@ "author": "", "license": "ISC", "dependencies": { - "@botpress/cognitive": "0.5.5", + "@botpress/cognitive": "0.6.0", "json5": "^2.2.3", "jsonrepair": "^3.10.0", "lodash-es": "^4.17.21", @@ -52,11 +52,12 @@ "lodash": "^4.17.21", "msw": "^2.12.0", "size-limit": "^11.1.6", - "tsup": "^8.0.2" + "tsdown": "^0.22.1", + "unrun": "^0.3.0" }, "peerDependencies": { "@bpinternal/thicktoken": "^1.0.0", - "@bpinternal/zui": "^2.2.1" + "@bpinternal/zui": "^2.3.0" }, "engines": { "node": ">=18.0.0" diff --git a/packages/zai/tsup.config.ts b/packages/zai/tsdown.config.ts similarity index 58% rename from packages/zai/tsup.config.ts rename to packages/zai/tsdown.config.ts index 8a478b23f1a..95bcfb0a5c1 100644 --- a/packages/zai/tsup.config.ts +++ b/packages/zai/tsdown.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'tsup' +import { defineConfig } from 'tsdown' export default defineConfig({ entry: ['src/index.ts'], @@ -6,5 +6,7 @@ export default defineConfig({ outDir: 'dist', platform: 'neutral', clean: true, - bundle: false, + unbundle: true, + format: 'cjs', + target: undefined, }) diff --git a/plugins/conversation-insights/package.json b/plugins/conversation-insights/package.json index 9edecc834bd..8c419cd41b4 100644 --- a/plugins/conversation-insights/package.json +++ b/plugins/conversation-insights/package.json @@ -7,7 +7,7 @@ }, "private": true, "dependencies": { - "@botpress/cognitive": "0.5.5", + "@botpress/cognitive": "0.6.0", "@botpress/sdk": "workspace:*", "browser-or-node": "^2.1.1", "jsonrepair": "^3.10.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c6964bf8458..0e936a7d590 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3136,13 +3136,13 @@ importers: specifier: 1.46.0 version: link:../client '@botpress/cognitive': - specifier: 0.5.5 + specifier: 0.6.0 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^2.0.0 version: 2.0.0 '@bpinternal/zui': - specifier: ^2.2.1 + specifier: ^2.3.0 version: link:../zui '@jitl/quickjs-singlefile-browser-release-sync': specifier: ^0.31.0 @@ -3242,7 +3242,7 @@ importers: specifier: 1.46.0 version: link:../client '@bpinternal/zui': - specifier: ^2.2.1 + specifier: ^2.3.0 version: link:../zui browser-or-node: specifier: ^2.1.1 @@ -3282,7 +3282,7 @@ importers: specifier: ^1.0.1 version: 1.0.2 '@bpinternal/zui': - specifier: ^2.2.1 + specifier: ^2.3.0 version: link:../zui json5: specifier: ^2.2.3 @@ -3322,13 +3322,13 @@ importers: packages/zai: dependencies: '@botpress/cognitive': - specifier: 0.5.5 + specifier: 0.6.0 version: link:../cognitive '@bpinternal/thicktoken': specifier: ^1.0.0 version: 1.0.2 '@bpinternal/zui': - specifier: ^2.2.1 + specifier: ^2.3.0 version: link:../zui json5: specifier: ^2.2.3 @@ -3382,9 +3382,12 @@ importers: size-limit: specifier: ^11.1.6 version: 11.1.6 - tsup: - specifier: ^8.0.2 - version: 8.0.2(@microsoft/api-extractor@7.49.0(@types/node@22.16.4))(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.3))(typescript@5.9.3) + tsdown: + specifier: ^0.22.1 + version: 0.22.1(tsx@4.19.2)(typescript@5.9.3)(unrun@0.3.0(synckit@0.11.11)) + unrun: + specifier: ^0.3.0 + version: 0.3.0(synckit@0.11.11) packages/zui: devDependencies: @@ -3435,7 +3438,7 @@ importers: plugins/conversation-insights: dependencies: '@botpress/cognitive': - specifier: 0.5.5 + specifier: 0.6.0 version: link:../../packages/cognitive '@botpress/sdk': specifier: workspace:* @@ -4029,6 +4032,10 @@ packages: resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0-rc.6': + resolution: {integrity: sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-annotate-as-pure@7.27.1': resolution: {integrity: sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==} engines: {node: '>=6.9.0'} @@ -4111,6 +4118,10 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@8.0.0-rc.6': + resolution: {integrity: sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-identifier@7.25.9': resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} @@ -4123,6 +4134,10 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@8.0.0-rc.6': + resolution: {integrity: sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==} + engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-option@7.25.9': resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} @@ -4144,16 +4159,16 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.27.2': - resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@8.0.0-rc.6': + resolution: {integrity: sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -4295,6 +4310,10 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@8.0.0-rc.6': + resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==} + engines: {node: ^22.18.0 || >=24.11.0} + '@bcherny/json-schema-ref-parser@10.0.5-fork': resolution: {integrity: sha512-E/jKbPoca1tfUPj3iSbitDZTGnq6FUFjkH6L8U2oDwSuwK1WhnnVtCG7oFOTg/DDnyoXbQYUiUiGOibHqaGVnw==} engines: {node: '>= 16'} @@ -4419,6 +4438,15 @@ packages: peerDependencies: type-fest: ^4.12.0 + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@es-joy/jsdoccomment@0.53.0': resolution: {integrity: sha512-Wyed8Wfn3vMNVwrZrgLMxmqwmlcCE1/RfUAOHFzMJb3QLH03mi9Yv1iOCZjif0yx5EZUeJ+17VD1MHPka9IQjQ==} engines: {node: '>=20.11.0'} @@ -5386,6 +5414,12 @@ packages: resolution: {integrity: sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==} engines: {node: '>=18'} + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} @@ -5634,6 +5668,9 @@ packages: resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} engines: {node: '>=14'} + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxlint/binding-android-arm-eabi@1.58.0': resolution: {integrity: sha512-1T7UN3SsWWxpWyWGn1cT3ASNJOo+pI3eUkmEl7HgtowapcV8kslYpFQcYn431VuxghXakPNlbjRwhqmR37PFOg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5793,6 +5830,9 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@react-email/body@0.0.10': resolution: {integrity: sha512-dMJyL9aU25ieatdPtVjCyQ/WHZYHwNc+Hy/XpF8Cc18gu21cUynVEeYQzFSeigDRMeBQ3PGAyjVDPIob7YlGwA==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. @@ -5994,6 +6034,98 @@ packages: peerDependencies: '@redis/client': ^1.0.0 + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/rollup-android-arm-eabi@4.24.2': resolution: {integrity: sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==} cpu: [arm] @@ -6764,6 +6896,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/append-query@2.0.3': resolution: {integrity: sha512-nHEzqYqoLiSyq3EJFpyqnXY83LOTx0yOQ5TS3yo++oNw42EE4iBeSjTOjFEPtSU+Zrssjajgs+6XPG91uMIh9w==} @@ -6870,6 +7005,9 @@ packages: '@types/jest@29.5.2': resolution: {integrity: sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==} + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/jsforce@1.11.6': resolution: {integrity: sha512-AVCSPccfb/L1uuzSneNx3f/W7i9vI3fyzjLZPhZ2sF+0HevpM9d8a74n915ONK02C20hOncKbUQYI4Wz9NUelA==} @@ -7317,6 +7455,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -7392,6 +7534,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-kit@3.0.0-beta.1: + resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} + engines: {node: '>=20.19.0'} + astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -7577,6 +7723,9 @@ packages: bintrees@1.0.2: resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==} + birpc@4.0.0: + resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + bl@1.2.3: resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} @@ -7716,6 +7865,10 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + cacheable-request@6.1.0: resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} engines: {node: '>=8'} @@ -8323,6 +8476,15 @@ packages: peerDependencies: '@types/node-fetch': ^2.5.7 + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -8366,6 +8528,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} @@ -8786,6 +8952,15 @@ packages: picomatch: optional: true + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -9089,6 +9264,10 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} + getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} @@ -9302,6 +9481,9 @@ packages: resolution: {integrity: sha512-r4xbIa3mGGGoH9nN4A14DOg2wx7y2oQyJEb5O57C/xzETG/qx4c7CVDQ5WMeKHZ7ORk2W0hZ/sQKXTav3cmYBA==} engines: {node: '>=16.9.0'} + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -9420,6 +9602,10 @@ packages: import-meta-resolve@3.1.1: resolution: {integrity: sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==} + import-without-cache@0.4.0: + resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} + engines: {node: ^22.18.0 || >=24.0.0} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -10829,6 +11015,9 @@ packages: obliterator@1.6.1: resolution: {integrity: sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + octokit@2.0.19: resolution: {integrity: sha512-hSloK4MK78QGbAuBrtIir0bsxMoRVZE5CkwKSbSRH9lqv2hx9EwhCxtPqEF+BtHqLXkXdfUaGkJMyMBotYno+A==} engines: {node: '>= 14'} @@ -11137,6 +11326,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -11345,6 +11538,9 @@ packages: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} engines: {node: '>=0.6'} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + query-string@6.14.1: resolution: {integrity: sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==} engines: {node: '>=6'} @@ -11565,6 +11761,30 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rolldown-plugin-dts@0.25.2: + resolution: {integrity: sha512-nMhN/R+vmR8GM45ZW1FWMSjRTSDDn/6w4GTf8RNrEFCBdl8B1kySWrU1ixPtbwzXoRlcO+R/S88VgXuJQwfdDg==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0 + typescript: ^5.0.0 || ^6.0.0 + vue-tsc: ~3.2.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-dts@6.4.1: resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==} engines: {node: '>=20'} @@ -11698,6 +11918,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} @@ -12112,10 +12337,18 @@ packages: tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.10: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tinypool@1.0.1: resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -12267,6 +12500,40 @@ packages: resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} engines: {node: '>=6'} + tsdown@0.22.1: + resolution: {integrity: sha512-Ldx1jLyDFEzsN/fMBi2TBVaZe4fuEJhIiHjQhX0pV7oa5uYz5Imdivs5mNzEXOrMEtFRR6C9BQ2YqLoroffB+Q==} + engines: {node: ^22.18.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.22.1 + '@tsdown/exe': 0.22.1 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -12446,6 +12713,9 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -12512,6 +12782,16 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unrun@0.3.0: + resolution: {integrity: sha512-5xw2AIVS2WR9Lqhz76qDIQLxipKRidf7Nq+Iz5SZ8shk1OmRlxnc6FyI/1Q2m99WLj6cbqaKFdESfWE99KPzlA==} + engines: {node: ^22.13.0 || >=24.0.0} + hasBin: true + peerDependencies: + synckit: ^0.11.11 + peerDependenciesMeta: + synckit: + optional: true + until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} @@ -14016,7 +14296,7 @@ snapshots: '@babel/generator@7.27.1': dependencies: - '@babel/parser': 7.27.2 + '@babel/parser': 7.29.2 '@babel/types': 7.27.1 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -14030,6 +14310,15 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@8.0.0-rc.6': + dependencies: + '@babel/parser': 8.0.0-rc.6 + '@babel/types': 8.0.0-rc.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.1': dependencies: '@babel/types': 7.27.1 @@ -14146,12 +14435,16 @@ snapshots: '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@8.0.0-rc.6': {} + '@babel/helper-validator-identifier@7.25.9': {} '@babel/helper-validator-identifier@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@8.0.0-rc.6': {} + '@babel/helper-validator-option@7.25.9': {} '@babel/helper-validator-option@7.27.1': {} @@ -14170,14 +14463,14 @@ snapshots: dependencies: '@babel/types': 7.26.9 - '@babel/parser@7.27.2': - dependencies: - '@babel/types': 7.27.1 - '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 + '@babel/parser@8.0.0-rc.6': + dependencies: + '@babel/types': 8.0.0-rc.6 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.9)': dependencies: '@babel/core': 7.26.9 @@ -14304,8 +14597,8 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.27.2 - '@babel/types': 7.27.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 '@babel/template@7.28.6': dependencies: @@ -14328,8 +14621,8 @@ snapshots: '@babel/traverse@7.27.1': dependencies: '@babel/code-frame': 7.29.0 - '@babel/generator': 7.27.1 - '@babel/parser': 7.27.2 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 '@babel/template': 7.27.2 '@babel/types': 7.27.1 debug: 4.4.3 @@ -14364,6 +14657,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@8.0.0-rc.6': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.6 + '@babel/helper-validator-identifier': 8.0.0-rc.6 + '@bcherny/json-schema-ref-parser@10.0.5-fork': dependencies: '@jsdevtools/ono': 7.1.3 @@ -14565,6 +14863,22 @@ snapshots: transitivePeerDependencies: - debug + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.6.2 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.6.2 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.6.2 + optional: true + '@es-joy/jsdoccomment@0.53.0': dependencies: '@types/estree': 1.0.8 @@ -15417,6 +15731,13 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': optional: true @@ -15749,6 +16070,8 @@ snapshots: '@opentelemetry/semantic-conventions@1.28.0': {} + '@oxc-project/types@0.133.0': {} + '@oxlint/binding-android-arm-eabi@1.58.0': optional: true @@ -15845,6 +16168,10 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + '@react-email/body@0.0.10(react@18.3.1)': dependencies: react: 18.3.1 @@ -16023,6 +16350,57 @@ snapshots: dependencies: '@redis/client': 1.6.0 + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + '@rollup/rollup-android-arm-eabi@4.24.2': optional: true @@ -17021,6 +17399,11 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.6.2 + optional: true + '@types/append-query@2.0.3': {} '@types/argparse@1.0.38': {} @@ -17159,6 +17542,8 @@ snapshots: expect: 29.5.0 pretty-format: 29.5.0 + '@types/jsesc@2.5.1': {} + '@types/jsforce@1.11.6': dependencies: '@types/node': 22.16.4 @@ -17669,6 +18054,8 @@ snapshots: ansi-styles@6.2.1: {} + ansis@4.3.1: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -17764,6 +18151,12 @@ snapshots: assertion-error@2.0.1: {} + ast-kit@3.0.0-beta.1: + dependencies: + '@babel/parser': 8.0.0-rc.6 + estree-walker: 3.0.3 + pathe: 2.0.3 + astring@1.9.0: {} async@3.2.4: {} @@ -18068,6 +18461,8 @@ snapshots: bintrees@1.0.2: {} + birpc@4.0.0: {} + bl@1.2.3: dependencies: readable-stream: 2.3.8 @@ -18310,6 +18705,8 @@ snapshots: cac@6.7.14: {} + cac@7.0.0: {} + cacheable-request@6.1.0: dependencies: clone-response: 1.0.3 @@ -18884,6 +19281,8 @@ snapshots: transitivePeerDependencies: - encoding + dts-resolver@3.0.0: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -18908,7 +19307,7 @@ snapshots: '@one-ini/wasm': 0.1.1 commander: 10.0.1 minimatch: 9.0.1 - semver: 7.7.2 + semver: 7.7.4 ee-first@1.1.1: {} @@ -18924,6 +19323,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.1: {} + enabled@2.0.0: {} encodeurl@1.0.2: {} @@ -19644,6 +20045,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + fecha@4.2.3: {} fetch-blob@3.2.0: @@ -19981,6 +20386,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + get-tsconfig@5.0.0-beta.5: + dependencies: + resolve-pkg-maps: 1.0.0 + getpass@0.1.7: dependencies: assert-plus: 1.0.0 @@ -20283,6 +20692,8 @@ snapshots: hono@4.12.11: optional: true + hookable@6.1.1: {} + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 @@ -20424,6 +20835,8 @@ snapshots: import-meta-resolve@3.1.1: {} + import-without-cache@0.4.0: {} + imurmurhash@0.1.4: {} indent-string@4.0.0: {} @@ -20693,7 +21106,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.26.9 - '@babel/parser': 7.27.2 + '@babel/parser': 7.29.2 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.1 @@ -20982,7 +21395,7 @@ snapshots: jest-util: 29.5.0 natural-compare: 1.4.0 pretty-format: 29.5.0 - semver: 7.7.2 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -22313,6 +22726,8 @@ snapshots: obliterator@1.6.1: {} + obug@2.1.1: {} + octokit@2.0.19: dependencies: '@octokit/app': 13.1.5 @@ -22638,6 +23053,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.4: {} + pidtree@0.6.0: {} pify@2.3.0: {} @@ -22813,6 +23230,8 @@ snapshots: qs@6.5.3: {} + quansync@1.0.0: {} + query-string@6.14.1: dependencies: decode-uri-component: 0.2.2 @@ -23119,6 +23538,43 @@ snapshots: dependencies: glob: 7.2.3 + rolldown-plugin-dts@0.25.2(rolldown@1.0.3)(typescript@5.9.3): + dependencies: + '@babel/generator': 8.0.0-rc.6 + '@babel/helper-validator-identifier': 8.0.0-rc.6 + '@babel/parser': 8.0.0-rc.6 + ast-kit: 3.0.0-beta.1 + birpc: 4.0.0 + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.1 + rolldown: 1.0.3 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + rollup-plugin-dts@6.4.1(rollup@4.60.4)(typescript@5.9.3): dependencies: '@jridgewell/remapping': 2.3.5 @@ -23295,6 +23751,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.1: {} + send@0.19.0: dependencies: debug: 2.6.9 @@ -23720,7 +24178,7 @@ snapshots: mime: 2.6.0 qs: 6.15.0 readable-stream: 3.6.2 - semver: 7.7.2 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -23821,11 +24279,18 @@ snapshots: tinyexec@0.3.1: {} + tinyexec@1.2.4: {} + tinyglobby@0.2.10: dependencies: fdir: 6.4.3(picomatch@4.0.3) picomatch: 4.0.3 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@1.0.1: {} tinyrainbow@1.2.0: {} @@ -23985,6 +24450,33 @@ snapshots: minimist: 1.2.8 strip-bom: 3.0.0 + tsdown@0.22.1(tsx@4.19.2)(typescript@5.9.3)(unrun@0.3.0(synckit@0.11.11)): + dependencies: + ansis: 4.3.1 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.1 + hookable: 6.1.1 + import-without-cache: 0.4.0 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.3 + rolldown-plugin-dts: 0.25.2(rolldown@1.0.3)(typescript@5.9.3) + semver: 7.8.1 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + optionalDependencies: + tsx: 4.19.2 + typescript: 5.9.3 + unrun: 0.3.0(synckit@0.11.11) + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - vue-tsc + tslib@1.14.1: {} tslib@2.6.2: {} @@ -24175,6 +24667,11 @@ snapshots: buffer: 5.7.1 through: 2.3.8 + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + undici-types@6.21.0: {} undici@5.28.4: @@ -24255,6 +24752,12 @@ snapshots: unpipe@1.0.0: {} + unrun@0.3.0(synckit@0.11.11): + dependencies: + rolldown: 1.0.3 + optionalDependencies: + synckit: 0.11.11 + until-async@3.0.2: {} update-browserslist-db@1.1.2(browserslist@4.24.4):