From c5325b21997471d60dadea2c2be55b568b64ed13 Mon Sep 17 00:00:00 2001 From: Xavier Hamel <76625630+xavierhamel@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:16:08 -0400 Subject: [PATCH 1/2] feat(cli): `bp dev` uses incremental build (#14277) Co-authored-by: Xavier Hamel --- packages/chat-api/package.json | 2 +- packages/chat-client/package.json | 2 +- packages/cli/package.json | 4 +- .../command-implementations/build-command.ts | 9 +- .../command-implementations/bundle-command.ts | 19 +- .../command-implementations/dev-command.ts | 8 +- packages/cli/src/utils/esbuild-utils.ts | 31 ++ packages/client/package.json | 2 +- packages/cognitive/package.json | 2 +- packages/llmz/package.json | 2 +- packages/sdk/package.json | 2 +- packages/vai/package.json | 2 +- packages/zai/package.json | 2 +- pnpm-lock.yaml | 372 ++++++++++-------- 14 files changed, 271 insertions(+), 188 deletions(-) diff --git a/packages/chat-api/package.json b/packages/chat-api/package.json index 6af26c976ff..55596685a67 100644 --- a/packages/chat-api/package.json +++ b/packages/chat-api/package.json @@ -10,7 +10,7 @@ "@types/json-schema": "^7.0.12", "@types/lodash": "^4.14.191", "@types/tmp": "^0.2.3", - "esbuild": "^0.16.12" + "esbuild": "^0.25.10" }, "dependencies": { "@bpinternal/opapi": "0.12.1", diff --git a/packages/chat-client/package.json b/packages/chat-client/package.json index 5d13ddf3712..c7a05f6ff3c 100644 --- a/packages/chat-client/package.json +++ b/packages/chat-client/package.json @@ -37,7 +37,7 @@ "@types/verror": "^1.10.6", "@types/web": "^0.0.115", "dotenv": "^16.4.4", - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "esbuild-plugin-polyfill-node": "^0.3.0", "lodash": "^4.17.21", "uuid": "^9.0.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index 04ee9ce882b..b6638a70bb2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@botpress/cli", - "version": "4.17.13", + "version": "4.17.14", "description": "Botpress CLI", "scripts": { "build": "pnpm run bundle && pnpm run template:gen", @@ -37,7 +37,7 @@ "boxen": "5.1.2", "chalk": "^4.1.2", "dotenv": "^16.4.4", - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "handlebars": "^4.7.8", "latest-version": "5.1.0", "lodash": "^4.17.21", diff --git a/packages/cli/src/command-implementations/build-command.ts b/packages/cli/src/command-implementations/build-command.ts index a3383f8d94e..456a97098be 100644 --- a/packages/cli/src/command-implementations/build-command.ts +++ b/packages/cli/src/command-implementations/build-command.ts @@ -1,11 +1,12 @@ import type commandDefinitions from '../command-definitions' +import * as utils from '../utils' import { BundleCommand } from './bundle-command' import { GenerateCommand } from './gen-command' import { ProjectCommand } from './project-command' export type BuildCommandDefinition = typeof commandDefinitions.build export class BuildCommand extends ProjectCommand { - public async run(): Promise { + public async run(buildContext?: utils.esbuild.IncrementalBuildContext): Promise { const t0 = Date.now() const { type: projectType, definition: integrationDef } = await this.readProjectDefinitionFromFS() @@ -18,7 +19,7 @@ export class BuildCommand extends ProjectCommand { await this._runGenerate() } - await this._runBundle() + await this._runBundle(buildContext) const dt = Date.now() - t0 this.logger.log(`Build completed in ${dt}ms`) } @@ -27,7 +28,7 @@ export class BuildCommand extends ProjectCommand { return new GenerateCommand(this.api, this.prompt, this.logger, this.argv).run() } - private _runBundle() { - return new BundleCommand(this.api, this.prompt, this.logger, this.argv).run() + private _runBundle(buildContext?: utils.esbuild.IncrementalBuildContext) { + return new BundleCommand(this.api, this.prompt, this.logger, this.argv).run(buildContext) } } diff --git a/packages/cli/src/command-implementations/bundle-command.ts b/packages/cli/src/command-implementations/bundle-command.ts index e8f72a838c2..3a9ceecd4dc 100644 --- a/packages/cli/src/command-implementations/bundle-command.ts +++ b/packages/cli/src/command-implementations/bundle-command.ts @@ -6,7 +6,7 @@ import { ProjectCommand } from './project-command' export type BundleCommandDefinition = typeof commandDefinitions.bundle export class BundleCommand extends ProjectCommand { - public async run(): Promise { + public async run(buildContext?: utils.esbuild.IncrementalBuildContext): Promise { const projectDef = await this.readProjectDefinitionFromFS() const abs = this.projectPaths.abs @@ -18,16 +18,16 @@ export class BundleCommand extends ProjectCommand { } else if (projectDef.type === 'integration') { const { name, __advanced } = projectDef.definition line.started(`Bundling integration ${chalk.bold(name)}...`) - await this._bundle(abs.outFileCJS, __advanced?.esbuild ?? {}) + await this._bundle(abs.outFileCJS, buildContext, __advanced?.esbuild ?? {}) } else if (projectDef.type === 'bot') { line.started('Bundling bot...') - await this._bundle(abs.outFileCJS) + await this._bundle(abs.outFileCJS, buildContext) } else if (projectDef.type === 'plugin') { line.started('Bundling plugin with platform node...') - await this._bundle(abs.outFileCJS) + await this._bundle(abs.outFileCJS, buildContext) line.started('Bundling plugin with platform browser...') - await this._bundle(abs.outFileESM, { platform: 'browser', format: 'esm' }) + await this._bundle(abs.outFileESM, buildContext, { platform: 'browser', format: 'esm' }) } else { type _assertion = utils.types.AssertNever throw new errors.UnsupportedProjectType() @@ -36,9 +36,14 @@ export class BundleCommand extends ProjectCommand { line.success(`Bundle available at ${chalk.grey(rel.outDir)}`) } - private async _bundle(outfile: string, props: Partial = {}) { + private async _bundle( + outfile: string, + buildContext?: utils.esbuild.IncrementalBuildContext, + props: Partial = {} + ) { const abs = this.projectPaths.abs - await utils.esbuild.buildCode( + const context = buildContext ?? new utils.esbuild.IncrementalBuildContext() + await context.rebuild( { outfile, absWorkingDir: abs.workDir, diff --git a/packages/cli/src/command-implementations/dev-command.ts b/packages/cli/src/command-implementations/dev-command.ts index 09e4b4fd39d..94b2f6c3dc6 100644 --- a/packages/cli/src/command-implementations/dev-command.ts +++ b/packages/cli/src/command-implementations/dev-command.ts @@ -24,6 +24,12 @@ export type DevCommandDefinition = typeof commandDefinitions.dev export class DevCommand extends ProjectCommand { private _initialDef: ProjectDefinition | undefined = undefined private _cacheDevRequestBody: apiUtils.UpdateBotRequestBody | apiUtils.UpdateIntegrationRequestBody | undefined + private _buildContext: utils.esbuild.IncrementalBuildContext + + public constructor(...args: ConstructorParameters>) { + super(...args) + this._buildContext = new utils.esbuild.IncrementalBuildContext() + } public async run(): Promise { this.logger.warn('This command is experimental and subject to breaking changes without notice.') @@ -229,7 +235,7 @@ export class DevCommand extends ProjectCommand { } private _runBuild() { - return new BuildCommand(this.api, this.prompt, this.logger, this.argv).run() + return new BuildCommand(this.api, this.prompt, this.logger, this.argv).run(this._buildContext) } private async _deployDevIntegration( diff --git a/packages/cli/src/utils/esbuild-utils.ts b/packages/cli/src/utils/esbuild-utils.ts index 691b39a435a..87d8993b035 100644 --- a/packages/cli/src/utils/esbuild-utils.ts +++ b/packages/cli/src/utils/esbuild-utils.ts @@ -1,4 +1,5 @@ import * as esb from 'esbuild' +import _ from 'lodash' export * from 'esbuild' @@ -27,6 +28,36 @@ const DEFAULT_OPTIONS: esb.BuildOptions = { minify: false, } +export class IncrementalBuildContext { + private _context: esb.BuildContext | undefined + private _previousProps: BuildCodeProps | undefined + private _previousOpts: esb.BuildOptions = {} + + private _createContext(props: BuildCodeProps, opts: esb.BuildOptions = {}): Promise { + const { absWorkingDir, code, outfile } = props + return esb.context({ + ...DEFAULT_OPTIONS, + ...opts, + absWorkingDir, + outfile, + stdin: { contents: code, resolveDir: absWorkingDir, loader: 'ts' }, + write: true, + }) + } + + public async rebuild(props: BuildCodeProps, opts: esb.BuildOptions = {}) { + if (!this._context || !_.isEqual(props, this._previousProps) || !_.isEqual(opts, this._previousOpts)) { + if (this._context) { + await this._context.dispose() + } + this._context = await this._createContext(props, opts) + this._previousOpts = opts + this._previousProps = props + } + await this._context?.rebuild() + } +} + /** * Bundles a string of typescript code and writes the output to a file */ diff --git a/packages/client/package.json b/packages/client/package.json index 9a540f92457..e5de4e46747 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -29,7 +29,7 @@ }, "devDependencies": { "@types/qs": "^6.9.7", - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "lodash": "^4.17.21", "tsup": "^8.0.2" }, diff --git a/packages/cognitive/package.json b/packages/cognitive/package.json index 99e738e1ace..a0f7357fe78 100644 --- a/packages/cognitive/package.json +++ b/packages/cognitive/package.json @@ -36,7 +36,7 @@ "@types/debug": "^4.1.12", "axios": "^1.7.9", "dotenv": "^16.4.4", - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "size-limit": "^11.1.6", "tsup": "^8.0.2" }, diff --git a/packages/llmz/package.json b/packages/llmz/package.json index 3b30ea35126..c799e6ff7f5 100644 --- a/packages/llmz/package.json +++ b/packages/llmz/package.json @@ -59,7 +59,7 @@ "chalk": "^4.1.2", "diff": "^8.0.1", "dotenv": "^16.4.4", - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "glob": "^9.3.4", "source-map-js": "1.2.1", "ts-node": "^10.9.2", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 6492a23fb15..ade83f0aa9e 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -20,7 +20,7 @@ "browser-or-node": "^2.1.1" }, "devDependencies": { - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "esbuild-plugin-polyfill-node": "^0.3.0", "tsup": "^8.0.2" }, diff --git a/packages/vai/package.json b/packages/vai/package.json index 0676f207e70..77371f9d9b0 100644 --- a/packages/vai/package.json +++ b/packages/vai/package.json @@ -31,7 +31,7 @@ "@botpress/common": "workspace:*", "@types/lodash": "^4.14.191", "dotenv": "^16.4.4", - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "glob": "^9.3.4", "tsup": "^8.0.2" }, diff --git a/packages/zai/package.json b/packages/zai/package.json index abec17ee6c2..2be1e6a1384 100644 --- a/packages/zai/package.json +++ b/packages/zai/package.json @@ -42,7 +42,7 @@ "@types/lodash-es": "^4.17.12", "diff": "^8.0.1", "dotenv": "^16.4.4", - "esbuild": "^0.16.12", + "esbuild": "^0.25.10", "glob": "^9.3.4", "lodash": "^4.17.21", "size-limit": "^11.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b78f2c19f6f..68daaf120ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2072,8 +2072,8 @@ importers: specifier: ^0.2.3 version: 0.2.3 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 packages/chat-client: dependencies: @@ -2136,11 +2136,11 @@ importers: specifier: ^16.4.4 version: 16.4.4 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 esbuild-plugin-polyfill-node: specifier: ^0.3.0 - version: 0.3.0(esbuild@0.16.17) + version: 0.3.0(esbuild@0.25.10) lodash: specifier: ^4.17.21 version: 4.17.21 @@ -2205,8 +2205,8 @@ importers: specifier: ^16.4.4 version: 16.4.4 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 handlebars: specifier: ^4.7.8 version: 4.7.8 @@ -2368,8 +2368,8 @@ importers: specifier: ^6.9.7 version: 6.9.7 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -2411,8 +2411,8 @@ importers: specifier: ^16.4.4 version: 16.4.4 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 size-limit: specifier: ^11.1.6 version: 11.1.6 @@ -2553,8 +2553,8 @@ importers: specifier: ^16.4.4 version: 16.4.4 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 glob: specifier: ^9.3.4 version: 9.3.5 @@ -2584,11 +2584,11 @@ importers: version: 2.1.1 devDependencies: esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 esbuild-plugin-polyfill-node: specifier: ^0.3.0 - version: 0.3.0(esbuild@0.16.17) + version: 0.3.0(esbuild@0.25.10) 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.8.3))(typescript@5.8.3) @@ -2636,8 +2636,8 @@ importers: specifier: ^16.4.4 version: 16.4.4 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 glob: specifier: ^9.3.4 version: 9.3.5 @@ -2688,8 +2688,8 @@ importers: specifier: ^16.4.4 version: 16.4.4 esbuild: - specifier: ^0.16.12 - version: 0.16.17 + specifier: ^0.25.10 + version: 0.25.10 glob: specifier: ^9.3.4 version: 9.3.5 @@ -3688,11 +3688,11 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.16.17': - resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] + '@esbuild/aix-ppc64@0.25.10': + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.19.12': resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} @@ -3712,10 +3712,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.16.17': - resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/android-arm64@0.25.10': + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + engines: {node: '>=18'} + cpu: [arm64] os: [android] '@esbuild/android-arm@0.19.12': @@ -3736,10 +3736,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.16.17': - resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/android-arm@0.25.10': + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + engines: {node: '>=18'} + cpu: [arm] os: [android] '@esbuild/android-x64@0.19.12': @@ -3760,11 +3760,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.16.17': - resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] + '@esbuild/android-x64@0.25.10': + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.19.12': resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} @@ -3784,10 +3784,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.16.17': - resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/darwin-arm64@0.25.10': + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + engines: {node: '>=18'} + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.19.12': @@ -3808,11 +3808,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.16.17': - resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] + '@esbuild/darwin-x64@0.25.10': + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.19.12': resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} @@ -3832,10 +3832,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.16.17': - resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/freebsd-arm64@0.25.10': + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + engines: {node: '>=18'} + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.19.12': @@ -3856,11 +3856,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.16.17': - resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] + '@esbuild/freebsd-x64@0.25.10': + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.19.12': resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} @@ -3880,10 +3880,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.16.17': - resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/linux-arm64@0.25.10': + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + engines: {node: '>=18'} + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.19.12': @@ -3904,10 +3904,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.16.17': - resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/linux-arm@0.25.10': + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + engines: {node: '>=18'} + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.19.12': @@ -3928,10 +3928,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.16.17': - resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} - engines: {node: '>=12'} - cpu: [loong64] + '@esbuild/linux-ia32@0.25.10': + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + engines: {node: '>=18'} + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.19.12': @@ -3952,10 +3952,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.16.17': - resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} - engines: {node: '>=12'} - cpu: [mips64el] + '@esbuild/linux-loong64@0.25.10': + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + engines: {node: '>=18'} + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.19.12': @@ -3976,10 +3976,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.16.17': - resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} - engines: {node: '>=12'} - cpu: [ppc64] + '@esbuild/linux-mips64el@0.25.10': + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + engines: {node: '>=18'} + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.19.12': @@ -4000,10 +4000,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.16.17': - resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} - engines: {node: '>=12'} - cpu: [riscv64] + '@esbuild/linux-ppc64@0.25.10': + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + engines: {node: '>=18'} + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.19.12': @@ -4024,10 +4024,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.16.17': - resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} - engines: {node: '>=12'} - cpu: [s390x] + '@esbuild/linux-riscv64@0.25.10': + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + engines: {node: '>=18'} + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.19.12': @@ -4048,10 +4048,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.16.17': - resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/linux-s390x@0.25.10': + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + engines: {node: '>=18'} + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.19.12': @@ -4072,10 +4072,16 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.16.17': - resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.10': + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + engines: {node: '>=18'} cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + engines: {node: '>=18'} + cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.19.12': @@ -4096,16 +4102,22 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.10': + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.23.1': resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.16.17': - resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/openbsd-arm64@0.25.10': + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + engines: {node: '>=18'} + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.19.12': @@ -4126,11 +4138,17 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.16.17': - resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} - engines: {node: '>=12'} + '@esbuild/openbsd-x64@0.25.10': + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + engines: {node: '>=18'} cpu: [x64] - os: [sunos] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] '@esbuild/sunos-x64@0.19.12': resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} @@ -4150,11 +4168,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.16.17': - resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] + '@esbuild/sunos-x64@0.25.10': + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.19.12': resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} @@ -4174,10 +4192,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.16.17': - resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/win32-arm64@0.25.10': + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + engines: {node: '>=18'} + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.19.12': @@ -4198,10 +4216,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.16.17': - resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/win32-ia32@0.25.10': + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + engines: {node: '>=18'} + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.19.12': @@ -4222,6 +4240,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.10': + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -7095,11 +7119,6 @@ packages: peerDependencies: esbuild: '*' - esbuild@0.16.17: - resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} @@ -7115,6 +7134,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -12643,7 +12667,7 @@ snapshots: '@esbuild/aix-ppc64@0.23.1': optional: true - '@esbuild/android-arm64@0.16.17': + '@esbuild/aix-ppc64@0.25.10': optional: true '@esbuild/android-arm64@0.19.12': @@ -12655,7 +12679,7 @@ snapshots: '@esbuild/android-arm64@0.23.1': optional: true - '@esbuild/android-arm@0.16.17': + '@esbuild/android-arm64@0.25.10': optional: true '@esbuild/android-arm@0.19.12': @@ -12667,7 +12691,7 @@ snapshots: '@esbuild/android-arm@0.23.1': optional: true - '@esbuild/android-x64@0.16.17': + '@esbuild/android-arm@0.25.10': optional: true '@esbuild/android-x64@0.19.12': @@ -12679,7 +12703,7 @@ snapshots: '@esbuild/android-x64@0.23.1': optional: true - '@esbuild/darwin-arm64@0.16.17': + '@esbuild/android-x64@0.25.10': optional: true '@esbuild/darwin-arm64@0.19.12': @@ -12691,7 +12715,7 @@ snapshots: '@esbuild/darwin-arm64@0.23.1': optional: true - '@esbuild/darwin-x64@0.16.17': + '@esbuild/darwin-arm64@0.25.10': optional: true '@esbuild/darwin-x64@0.19.12': @@ -12703,7 +12727,7 @@ snapshots: '@esbuild/darwin-x64@0.23.1': optional: true - '@esbuild/freebsd-arm64@0.16.17': + '@esbuild/darwin-x64@0.25.10': optional: true '@esbuild/freebsd-arm64@0.19.12': @@ -12715,7 +12739,7 @@ snapshots: '@esbuild/freebsd-arm64@0.23.1': optional: true - '@esbuild/freebsd-x64@0.16.17': + '@esbuild/freebsd-arm64@0.25.10': optional: true '@esbuild/freebsd-x64@0.19.12': @@ -12727,7 +12751,7 @@ snapshots: '@esbuild/freebsd-x64@0.23.1': optional: true - '@esbuild/linux-arm64@0.16.17': + '@esbuild/freebsd-x64@0.25.10': optional: true '@esbuild/linux-arm64@0.19.12': @@ -12739,7 +12763,7 @@ snapshots: '@esbuild/linux-arm64@0.23.1': optional: true - '@esbuild/linux-arm@0.16.17': + '@esbuild/linux-arm64@0.25.10': optional: true '@esbuild/linux-arm@0.19.12': @@ -12751,7 +12775,7 @@ snapshots: '@esbuild/linux-arm@0.23.1': optional: true - '@esbuild/linux-ia32@0.16.17': + '@esbuild/linux-arm@0.25.10': optional: true '@esbuild/linux-ia32@0.19.12': @@ -12763,7 +12787,7 @@ snapshots: '@esbuild/linux-ia32@0.23.1': optional: true - '@esbuild/linux-loong64@0.16.17': + '@esbuild/linux-ia32@0.25.10': optional: true '@esbuild/linux-loong64@0.19.12': @@ -12775,7 +12799,7 @@ snapshots: '@esbuild/linux-loong64@0.23.1': optional: true - '@esbuild/linux-mips64el@0.16.17': + '@esbuild/linux-loong64@0.25.10': optional: true '@esbuild/linux-mips64el@0.19.12': @@ -12787,7 +12811,7 @@ snapshots: '@esbuild/linux-mips64el@0.23.1': optional: true - '@esbuild/linux-ppc64@0.16.17': + '@esbuild/linux-mips64el@0.25.10': optional: true '@esbuild/linux-ppc64@0.19.12': @@ -12799,7 +12823,7 @@ snapshots: '@esbuild/linux-ppc64@0.23.1': optional: true - '@esbuild/linux-riscv64@0.16.17': + '@esbuild/linux-ppc64@0.25.10': optional: true '@esbuild/linux-riscv64@0.19.12': @@ -12811,7 +12835,7 @@ snapshots: '@esbuild/linux-riscv64@0.23.1': optional: true - '@esbuild/linux-s390x@0.16.17': + '@esbuild/linux-riscv64@0.25.10': optional: true '@esbuild/linux-s390x@0.19.12': @@ -12823,7 +12847,7 @@ snapshots: '@esbuild/linux-s390x@0.23.1': optional: true - '@esbuild/linux-x64@0.16.17': + '@esbuild/linux-s390x@0.25.10': optional: true '@esbuild/linux-x64@0.19.12': @@ -12835,7 +12859,10 @@ snapshots: '@esbuild/linux-x64@0.23.1': optional: true - '@esbuild/netbsd-x64@0.16.17': + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': optional: true '@esbuild/netbsd-x64@0.19.12': @@ -12847,10 +12874,13 @@ snapshots: '@esbuild/netbsd-x64@0.23.1': optional: true + '@esbuild/netbsd-x64@0.25.10': + optional: true + '@esbuild/openbsd-arm64@0.23.1': optional: true - '@esbuild/openbsd-x64@0.16.17': + '@esbuild/openbsd-arm64@0.25.10': optional: true '@esbuild/openbsd-x64@0.19.12': @@ -12862,7 +12892,10 @@ snapshots: '@esbuild/openbsd-x64@0.23.1': optional: true - '@esbuild/sunos-x64@0.16.17': + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': optional: true '@esbuild/sunos-x64@0.19.12': @@ -12874,7 +12907,7 @@ snapshots: '@esbuild/sunos-x64@0.23.1': optional: true - '@esbuild/win32-arm64@0.16.17': + '@esbuild/sunos-x64@0.25.10': optional: true '@esbuild/win32-arm64@0.19.12': @@ -12886,7 +12919,7 @@ snapshots: '@esbuild/win32-arm64@0.23.1': optional: true - '@esbuild/win32-ia32@0.16.17': + '@esbuild/win32-arm64@0.25.10': optional: true '@esbuild/win32-ia32@0.19.12': @@ -12898,7 +12931,7 @@ snapshots: '@esbuild/win32-ia32@0.23.1': optional: true - '@esbuild/win32-x64@0.16.17': + '@esbuild/win32-ia32@0.25.10': optional: true '@esbuild/win32-x64@0.19.12': @@ -12910,6 +12943,9 @@ snapshots: '@esbuild/win32-x64@0.23.1': optional: true + '@esbuild/win32-x64@0.25.10': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@9.34.0(jiti@2.4.2))': dependencies: eslint: 9.34.0(jiti@2.4.2) @@ -16638,37 +16674,12 @@ snapshots: es6-iterator: 2.0.3 es6-symbol: 3.1.3 - esbuild-plugin-polyfill-node@0.3.0(esbuild@0.16.17): + esbuild-plugin-polyfill-node@0.3.0(esbuild@0.25.10): dependencies: '@jspm/core': 2.1.0 - esbuild: 0.16.17 + esbuild: 0.25.10 import-meta-resolve: 3.1.1 - esbuild@0.16.17: - optionalDependencies: - '@esbuild/android-arm': 0.16.17 - '@esbuild/android-arm64': 0.16.17 - '@esbuild/android-x64': 0.16.17 - '@esbuild/darwin-arm64': 0.16.17 - '@esbuild/darwin-x64': 0.16.17 - '@esbuild/freebsd-arm64': 0.16.17 - '@esbuild/freebsd-x64': 0.16.17 - '@esbuild/linux-arm': 0.16.17 - '@esbuild/linux-arm64': 0.16.17 - '@esbuild/linux-ia32': 0.16.17 - '@esbuild/linux-loong64': 0.16.17 - '@esbuild/linux-mips64el': 0.16.17 - '@esbuild/linux-ppc64': 0.16.17 - '@esbuild/linux-riscv64': 0.16.17 - '@esbuild/linux-s390x': 0.16.17 - '@esbuild/linux-x64': 0.16.17 - '@esbuild/netbsd-x64': 0.16.17 - '@esbuild/openbsd-x64': 0.16.17 - '@esbuild/sunos-x64': 0.16.17 - '@esbuild/win32-arm64': 0.16.17 - '@esbuild/win32-ia32': 0.16.17 - '@esbuild/win32-x64': 0.16.17 - esbuild@0.19.12: optionalDependencies: '@esbuild/aix-ppc64': 0.19.12 @@ -16748,6 +16759,35 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + escalade@3.2.0: {} escape-html@1.0.3: {} From 97882f245e1e545287d4c9753714619c5cfdfe79 Mon Sep 17 00:00:00 2001 From: Mathieu Faucher <99497774+Math-Fauch@users.noreply.github.com> Date: Fri, 26 Sep 2025 11:42:01 -0400 Subject: [PATCH 2/2] feat(integrations/whatsapp): add template events (#14276) Co-authored-by: Mathieu Faucher Co-authored-by: Mathieu Faucher --- integrations/whatsapp/definitions/events.ts | 141 ++++++++++++++++++ .../whatsapp/integration.definition.ts | 30 +++- integrations/whatsapp/src/misc/types.ts | 121 ++++++++++++++- integrations/whatsapp/src/webhook/handler.ts | 83 +++++++++-- .../whatsapp/src/webhook/handlers/messages.ts | 8 +- .../whatsapp/src/webhook/handlers/sandbox.ts | 6 +- 6 files changed, 364 insertions(+), 25 deletions(-) create mode 100644 integrations/whatsapp/definitions/events.ts diff --git a/integrations/whatsapp/definitions/events.ts b/integrations/whatsapp/definitions/events.ts new file mode 100644 index 00000000000..cdc3bff13b8 --- /dev/null +++ b/integrations/whatsapp/definitions/events.ts @@ -0,0 +1,141 @@ +import { z } from '@botpress/sdk' + +export const qualityScoreSchema = z.enum(['GREEN', 'RED', 'YELLOW', 'UNKNOWN']) + +export const WhatsAppMessageTemplateComponentsUpdateValueSchema = z + .object({ + id: z.number().describe('Template ID.').title('Template Id'), + name: z.string().describe('Template name.').title('Template Name'), + language: z.string().describe('Template language and locale code.').title('Template Language'), + element: z.string().describe('Template body text.').title('Template Body Text'), + title: z.string().describe('Template Header Text.').title('Template Header Text').optional(), + footer: z.string().describe('Template Footer Text.').title('Template Footer Text').optional(), + buttons: z + .array( + z.object({ + button_type: z + .enum([ + 'CATALOG', + 'COPY_CODE', + 'EXTENSION', + 'FLOW', + 'MPM', + 'ORDER_DETAILS', + 'OTP', + 'PHONE_NUMBER', + 'POSTBACK', + 'REMINDER', + 'SEND_LOCATION', + 'SPM', + 'QUICK_REPLY', + 'URL', + 'VOICE_CALL', + ]) + .describe('Button type.') + .title('Button Type'), + button_text: z.string().describe('Button label text.').title('Button Label Text'), + button_url: z.string().describe('Button URL.').title('Button URL').optional(), + button_phone_number: z.string().describe('Button phone number.').title('Button Phone Number').optional(), + }) + ) + .describe('Array of button objects, if present.') + .title('Buttons') + .optional(), + }) + .describe("The message_template_components_update webhook notifies you of changes to a template's components.") + .title('Message Template Components Update') + .describe("The message_template_components_update webhook notifies you of changes to a template's components.") + +export const WhatsAppMessageTemplateQualityUpdateValueSchema = z + .object({ + previous_quality_score: qualityScoreSchema + .describe('Previous template quality score.') + .title('Previous Quality Score'), + new_quality_score: qualityScoreSchema.describe('New template quality score.').title('New Quality Score'), + id: z.number().describe('Template ID.').title('Template Id'), + name: z.string().describe('Template name.').title('Template Name'), + language: z.string().describe('Template language and locale code.').title('Template Language'), + }) + .describe("The message_template_quality_update webhook notifies you of changes to a template's quality score.") + .title('Message Template Quality Update') + +export const WhatsAppMessageTemplateStatusUpdateValueSchema = z + .object({ + event: z + .enum([ + 'APPROVED', + 'ARCHIVED', + 'DELETED', + 'DISABLED', + 'FLAGGED', + 'IN_APPEAL', + 'LIMIT_EXCEEDED', + 'LOCKED', + 'PAUSED', + 'PENDING', + 'REINSTATED', + 'PENDING_DELETION', + 'REJECTED', + ]) + .describe('Template status event.') + .title('Template Status Event'), + id: z.number().describe('Template ID.').title('Template Id'), + name: z.string().describe('Template name.').title('Template Name'), + language: z.string().describe('Language and locale code.').title('Template Language'), + reason: z + .enum([ + 'ABUSIVE_CONTENT', + 'CATEGORY_NOT_AVAILABLE', + 'INCORRECT_CATEGORY', + 'INVALID_FORMAT', + 'NONE', + 'PROMOTIONAL', + 'SCAM', + 'TAG_CONTENT_MISMATCH', + ]) + .describe('Template rejection reason, if rejected.') + .title('Rejection Reason') + .nullable(), + disable_info: z + .object({ + disable_date: z + .number() + .describe('Unix timestamp indicating when the template was disabled.') + .title('Disable Timestamp'), + }) + .describe('only included if template disabled') + .title('Disable Info') + .optional(), + other_info: z + .object({ + title: z + .enum(['FIRST_PAUSE', 'SECOND_PAUSE', 'RATE_LIMITING_PAUSE', 'UNPAUSE', 'DISABLED']) + .describe('Title of template pause or unpause event.') + .title('Title'), + description: z + .string() + .describe('String describing why the template was locked or unlocked.') + .title('Description'), + }) + .describe('only included if template locked or unlocked') + .title('Other Info') + .optional(), + }) + .describe('The message_template_status_update webhook notifies you of changes to the status of an existing template.') + .title('Message Template Status Update') + +export const WhatsAppTemplateCategoryUpdateValueSchema = z + .object({ + id: z.number().describe('Template ID.').title('Template Id'), + name: z.string().describe('Template name.').title('Template Name'), + language: z.string().describe('Template language and locale code.').title('Template Language'), + correct_category: z + .string() + .describe('The category that the template will be recategorized as in 24 hours.') + .title('Correct Category') + .optional(), + previous_category: z.string().describe("The template's previous category.").title('Previous Category').optional(), + new_category: z.string().describe("The template's new category.").title('New Category').optional(), + }) + .describe("The template_category_update webhook notifies you of changes to template's category.") + .title('Template Category Update') diff --git a/integrations/whatsapp/integration.definition.ts b/integrations/whatsapp/integration.definition.ts index 5b856b74fd5..7fbc1b6549c 100644 --- a/integrations/whatsapp/integration.definition.ts +++ b/integrations/whatsapp/integration.definition.ts @@ -2,6 +2,12 @@ import { z, IntegrationDefinition, messages } from '@botpress/sdk' import { sentry as sentryHelpers } from '@botpress/sdk-addons' import proactiveConversation from 'bp_modules/proactive-conversation' import typingIndicator from 'bp_modules/typing-indicator' +import { + WhatsAppMessageTemplateComponentsUpdateValueSchema, + WhatsAppMessageTemplateQualityUpdateValueSchema, + WhatsAppMessageTemplateStatusUpdateValueSchema, + WhatsAppTemplateCategoryUpdateValueSchema, +} from 'definitions/events' export const INTEGRATION_NAME = 'whatsapp' @@ -75,6 +81,7 @@ const startConversationProps = { .title('Bot Phone Number ID') .describe('Phone number ID to use as sender (uses the default phone number ID if not provided)'), }) + .title('Conversation Details') .describe('Details of the conversation'), }), }, @@ -87,7 +94,7 @@ const defaultBotPhoneNumberId = { export default new IntegrationDefinition({ name: INTEGRATION_NAME, - version: '4.3.0', + version: '4.4.0', title: 'WhatsApp', description: 'Send and receive messages through WhatsApp.', icon: 'icon.svg', @@ -260,6 +267,27 @@ export default new IntegrationDefinition({ conversationId: z.string().optional().title('Conversation ID').describe('ID of the conversation'), }), }, + messageTemplateComponentsUpdate: { + title: 'Message Template Components Update', + description: 'Triggered when a template is edited', + schema: WhatsAppMessageTemplateComponentsUpdateValueSchema, + }, + messageTemplateQualityUpdate: { + title: 'Message Template Quality Update', + description: "Triggered when a template's quality score changes", + schema: WhatsAppMessageTemplateQualityUpdateValueSchema, + }, + messageTemplateStatusUpdate: { + title: 'Message Template Status Update', + description: 'Triggered when a template is approved, rejected or disabled', + schema: WhatsAppMessageTemplateStatusUpdateValueSchema, + }, + templateCategoryUpdate: { + title: 'Template Category Update', + description: + 'Triggered when the category of a WhatsApp template is changed — whether manually or by an automated process, or when such a change is about to occur.', + schema: WhatsAppTemplateCategoryUpdateValueSchema, + }, }, states: { credentials: { diff --git a/integrations/whatsapp/src/misc/types.ts b/integrations/whatsapp/src/misc/types.ts index 394120a53fa..78f777b7960 100644 --- a/integrations/whatsapp/src/misc/types.ts +++ b/integrations/whatsapp/src/misc/types.ts @@ -1,4 +1,5 @@ import { z } from '@botpress/sdk' +import { qualityScoreSchema } from 'definitions/events' const WhatsAppContactSchema = z.object({ wa_id: z.string(), @@ -151,7 +152,7 @@ export type WhatsAppReactionMessage = WhatsAppMessage & { type: 'reaction' } -const WhatsAppValueSchema = z.object({ +const WhatsAppMessageValueSchema = z.object({ messaging_product: z.literal('whatsapp'), metadata: z.object({ display_phone_number: z.string(), @@ -160,13 +161,123 @@ const WhatsAppValueSchema = z.object({ contacts: z.array(WhatsAppContactSchema).optional(), messages: z.array(WhatsAppMessageSchema).optional(), }) -export type WhatsAppValue = z.infer +export type WhatsAppMessageValue = z.infer -const WhatsAppChangesSchema = z.object({ - value: WhatsAppValueSchema, - field: z.literal('messages'), +export const WhatsAppMessageTemplateComponentsUpdateValueSchema = z.object({ + message_template_id: z.number(), + message_template_name: z.string(), + message_template_language: z.string(), + message_template_element: z.string(), + message_template_title: z.string().optional(), + message_template_footer: z.string().optional(), + message_template_buttons: z + .array( + z.object({ + message_template_button_type: z.enum([ + 'CATALOG', + 'COPY_CODE', + 'EXTENSION', + 'FLOW', + 'MPM', + 'ORDER_DETAILS', + 'OTP', + 'PHONE_NUMBER', + 'POSTBACK', + 'REMINDER', + 'SEND_LOCATION', + 'SPM', + 'QUICK_REPLY', + 'URL', + 'VOICE_CALL', + ]), + message_template_button_text: z.string(), + message_template_button_url: z.string().optional(), + message_template_button_phone_number: z.string().optional(), + }) + ) + .optional(), +}) + +export const WhatsAppMessageTemplateQualityUpdateValueSchema = z.object({ + previous_quality_score: qualityScoreSchema, + new_quality_score: qualityScoreSchema, + message_template_id: z.number(), + message_template_name: z.string(), + message_template_language: z.string(), }) +export const WhatsAppMessageTemplateStatusUpdateValueSchema = z.object({ + event: z.enum([ + 'APPROVED', + 'ARCHIVED', + 'DELETED', + 'DISABLED', + 'FLAGGED', + 'IN_APPEAL', + 'LIMIT_EXCEEDED', + 'LOCKED', + 'PAUSED', + 'PENDING', + 'REINSTATED', + 'PENDING_DELETION', + 'REJECTED', + ]), + message_template_id: z.number(), + message_template_name: z.string(), + message_template_language: z.string(), + reason: z + .enum([ + 'ABUSIVE_CONTENT', + 'CATEGORY_NOT_AVAILABLE', + 'INCORRECT_CATEGORY', + 'INVALID_FORMAT', + 'NONE', + 'PROMOTIONAL', + 'SCAM', + 'TAG_CONTENT_MISMATCH', + ]) + .nullable(), + disable_info: z.object({ disable_date: z.number() }).optional(), + other_info: z + .object({ + title: z.enum(['FIRST_PAUSE', 'SECOND_PAUSE', 'RATE_LIMITING_PAUSE', 'UNPAUSE', 'DISABLED']), + description: z.string(), + }) + .optional(), +}) + +export const WhatsAppTemplateCategoryUpdateValueSchema = z.object({ + message_template_id: z.number(), + message_template_name: z.string(), + message_template_language: z.string(), + correct_category: z.string().optional(), + previous_category: z.string().optional(), + new_category: z.string().optional(), +}) + +const WhatsAppChangesSchema = z.discriminatedUnion('field', [ + z.object({ + field: z.literal('messages'), + value: WhatsAppMessageValueSchema, + }), + z.object({ + field: z.literal('message_template_components_update'), + value: WhatsAppMessageTemplateComponentsUpdateValueSchema, + }), + z.object({ + field: z.literal('message_template_quality_update'), + value: WhatsAppMessageTemplateQualityUpdateValueSchema, + }), + z.object({ + field: z.literal('message_template_status_update'), + value: WhatsAppMessageTemplateStatusUpdateValueSchema, + }), + z.object({ + field: z.literal('template_category_update'), + value: WhatsAppTemplateCategoryUpdateValueSchema, + }), +]) + const WhatsAppEntrySchema = z.object({ id: z.string(), changes: z.array(WhatsAppChangesSchema), diff --git a/integrations/whatsapp/src/webhook/handler.ts b/integrations/whatsapp/src/webhook/handler.ts index 1e883c85066..71b352a14c3 100644 --- a/integrations/whatsapp/src/webhook/handler.ts +++ b/integrations/whatsapp/src/webhook/handler.ts @@ -10,7 +10,7 @@ import { subscribeHandler } from './handlers/subscribe' import * as bp from '.botpress' const _handler: bp.IntegrationProps['handler'] = async (props: bp.HandlerProps) => { - const { req, logger } = props + const { req, logger, client } = props if (req.path.startsWith('/oauth')) { return await oauthCallbackHandler(props) } @@ -19,7 +19,7 @@ const _handler: bp.IntegrationProps['handler'] = async (props: bp.HandlerProps) return await sandboxHandler(props) } - props.logger.debug('Received request with body:', req.body ?? '[empty]') + logger.debug('Received request with body:', req.body ?? '[empty]') const queryParams = new URLSearchParams(req.query) if (queryParams.has('hub.mode')) { return await subscribeHandler(props) @@ -44,21 +44,80 @@ const _handler: bp.IntegrationProps['handler'] = async (props: bp.HandlerProps) return { status: 500, body: 'Error while handling request: ' + e.message } } - const value = payload.entry[0]?.changes[0]?.value - if (!value) { + const changes = payload.entry[0]?.changes[0] + if (!changes) { + logger.debug('No changes found in the payload, ignoring message') + return + } + if (!changes.value) { logger.debug('No value found in the payload, ignoring message') return } - const messages = value.messages - if (messages) { - for (const message of messages) { - if (message.type === 'reaction') { - await reactionHandler(message, props) - } else { - await messagesHandler(message, value, props) + switch (changes.field) { + case 'messages': + for (const message of changes.value.messages ?? []) { + if (message.type === 'reaction') { + await reactionHandler(message, props) + } else { + await messagesHandler(message, changes.value, props) + } } - } + break + case 'message_template_components_update': + await client.createEvent({ + type: 'messageTemplateComponentsUpdate', + payload: { + id: changes.value.message_template_id, + name: changes.value.message_template_name, + language: changes.value.message_template_language, + element: changes.value.message_template_element, + title: changes.value.message_template_title, + footer: changes.value.message_template_footer, + buttons: changes.value.message_template_buttons?.map((button) => ({ + button_type: button.message_template_button_type, + button_text: button.message_template_button_text, + button_url: button.message_template_button_url, + button_phone_number: button.message_template_button_phone_number, + })), + }, + }) + break + case 'message_template_quality_update': + await client.createEvent({ + type: 'messageTemplateQualityUpdate', + payload: { + ...changes.value, + id: changes.value.message_template_id, + name: changes.value.message_template_name, + language: changes.value.message_template_language, + }, + }) + break + case 'message_template_status_update': + await client.createEvent({ + type: 'messageTemplateStatusUpdate', + payload: { + ...changes.value, + id: changes.value.message_template_id, + name: changes.value.message_template_name, + language: changes.value.message_template_language, + }, + }) + break + case 'template_category_update': + await client.createEvent({ + type: 'templateCategoryUpdate', + payload: { + ...changes.value, + id: changes.value.message_template_id, + name: changes.value.message_template_name, + language: changes.value.message_template_language, + }, + }) + break + default: + logger.forBot().info('The event sent to the bot is not yet handled by botpress.') } } diff --git a/integrations/whatsapp/src/webhook/handlers/messages.ts b/integrations/whatsapp/src/webhook/handlers/messages.ts index e7f3e43e705..86f1b7492f4 100644 --- a/integrations/whatsapp/src/webhook/handlers/messages.ts +++ b/integrations/whatsapp/src/webhook/handlers/messages.ts @@ -3,7 +3,7 @@ import { ValueOf } from '@botpress/sdk/dist/utils/type-utils' import axios from 'axios' import { getAccessToken, getAuthenticatedWhatsappClient } from 'src/auth' import { getMessageFromWhatsappMessageId } from 'src/misc/util' -import { WhatsAppMessage, WhatsAppValue } from '../../misc/types' +import { WhatsAppMessage, WhatsAppMessageValue } from '../../misc/types' import { getMediaInfos } from '../../misc/whatsapp-utils' import * as bp from '.botpress' @@ -15,8 +15,8 @@ type IncomingMessages = { } export const messagesHandler = async ( - message: NonNullable[number], - value: WhatsAppValue, + message: NonNullable[number], + value: WhatsAppMessageValue, props: bp.HandlerProps ) => { const { ctx, client, logger } = props @@ -31,7 +31,7 @@ export const messagesHandler = async ( async function _handleIncomingMessage( message: WhatsAppMessage, - value: WhatsAppValue, + value: WhatsAppMessageValue, ctx: bp.Context, client: bp.Client, logger: bp.Logger diff --git a/integrations/whatsapp/src/webhook/handlers/sandbox.ts b/integrations/whatsapp/src/webhook/handlers/sandbox.ts index 3e11acaf38c..5c97efefdd3 100644 --- a/integrations/whatsapp/src/webhook/handlers/sandbox.ts +++ b/integrations/whatsapp/src/webhook/handlers/sandbox.ts @@ -1,6 +1,6 @@ import { z } from '@botpress/sdk' import { getAuthenticatedWhatsappClient } from 'src/auth' -import { WhatsAppPayloadSchema, WhatsAppValue } from 'src/misc/types' +import { WhatsAppPayloadSchema, WhatsAppMessageValue } from 'src/misc/types' import { Text } from 'whatsapp-api-js/messages' import * as bp from '.botpress' @@ -79,7 +79,7 @@ const _handleLeaveCommand = async (props: bp.HandlerProps) => { return } -const _extractValueFromRequest = (props: bp.HandlerProps): WhatsAppValue | undefined => { +const _extractValueFromRequest = (props: bp.HandlerProps): WhatsAppMessageValue | undefined => { const { req, logger } = props if (!req.body) { return undefined @@ -88,7 +88,7 @@ const _extractValueFromRequest = (props: bp.HandlerProps): WhatsAppValue | undef try { const data = JSON.parse(req.body) const payload = WhatsAppPayloadSchema.parse(data) - return payload.entry[0]?.changes[0]?.value + return payload.entry[0]?.changes[0]?.field === 'messages' ? payload.entry[0]?.changes[0]?.value : undefined } catch (e: any) { logger.error('Error while extracting message from request:', e?.message ?? '[unknown error]') return undefined