From 6f49481095277e2484ecf39098b139bbe50e2301 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:51:30 +0530 Subject: [PATCH] Imap perf improvement (#14629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed buffer to string conversion on message and we instead pass the direct Buffer - Removed redundant flattening of results - Cached `JSDOM` and `DOMPurify` Instance - MailParser added `skipTextToHtml` along with some other options, which reduced parsing time from 50ms to 35ms --------- Co-authored-by: Félix Malfait Co-authored-by: Félix Malfait --- .../imap/messaging-imap-driver.module.ts | 2 + .../services/imap-fetch-by-batch.service.ts | 17 +- .../services/imap-get-messages.service.ts | 32 +- .../imap-message-processor.service.ts | 39 +-- .../imap-message-text-extractor.service.ts | 50 ++++ .../extract-message-text.util.spec.ts | 22 +- .../imap/utils/extract-message-text.util.ts | 34 --- .../utils/safe-decode-uri-component.util.ts | 7 + yarn.lock | 277 +++++------------- 9 files changed, 187 insertions(+), 293 deletions(-) create mode 100644 packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts delete mode 100644 packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/extract-message-text.util.ts create mode 100644 packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util.ts diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts index f6974af96a..bbbb120204 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module.ts @@ -18,6 +18,7 @@ import { ImapHandleErrorService } from 'src/modules/messaging/message-import-man import { ImapIncrementalSyncService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-incremental-sync.service'; import { ImapMessageFetcherService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-fetcher.service'; import { ImapMessageProcessorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-processor.service'; +import { ImapMessageTextExtractorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service'; import { MessageParticipantManagerModule } from 'src/modules/messaging/message-participant-manager/message-participant-manager.module'; @Module({ @@ -41,6 +42,7 @@ import { MessageParticipantManagerModule } from 'src/modules/messaging/message-p ImapMessageFetcherService, ImapMessageProcessorService, ImapFindSentFolderService, + ImapMessageTextExtractorService, ], exports: [ ImapGetMessagesService, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-fetch-by-batch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-fetch-by-batch.service.ts index 1ca9008411..0ee20fc080 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-fetch-by-batch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-fetch-by-batch.service.ts @@ -13,8 +13,7 @@ type ConnectedAccount = Pick< >; type FetchAllResult = { - uidsByBatch: number[][]; - batchResults: MessageFetchResult[][]; + results: MessageFetchResult[]; }; @Injectable() @@ -32,8 +31,7 @@ export class ImapFetchByBatchService { folder: string, ): Promise { const batchLimit = 20; - const batchResults: MessageFetchResult[][] = []; - const uidsByBatch: number[][] = []; + const results: MessageFetchResult[] = []; this.logger.log( `Starting optimized batch fetch for ${uids.length} messages from folder ${folder}`, @@ -45,8 +43,6 @@ export class ImapFetchByBatchService { for (let i = 0; i < uids.length; i += batchLimit) { const batchUids = uids.slice(i, i + batchLimit); - uidsByBatch.push(batchUids); - try { const batchResult = await this.imapMessageProcessorService.processMessagesByUidsInFolder( @@ -55,7 +51,7 @@ export class ImapFetchByBatchService { client, ); - batchResults.push(batchResult); + results.push(...batchResult); this.logger.log( `Fetched batch ${Math.floor(i / batchLimit) + 1}/${Math.ceil(uids.length / batchLimit)} (${batchUids.length} messages)`, @@ -72,14 +68,11 @@ export class ImapFetchByBatchService { error as Error, ); - batchResults.push(errorResults); + results.push(...errorResults); } } - return { - uidsByBatch, - batchResults, - }; + return { results }; } finally { if (client) { await this.imapClientProvider.closeClient(client); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts index c613ba6875..e85d36ef28 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts @@ -7,7 +7,7 @@ import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-acco import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util'; import { ImapFetchByBatchService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-fetch-by-batch.service'; import { type MessageFetchResult } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-processor.service'; -import { extractTextWithoutReplyQuotations } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/extract-message-text.util'; +import { ImapMessageTextExtractorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service'; import { parseMessageId } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-message-id.util'; import { type EmailAddress } from 'src/modules/messaging/message-import-manager/types/email-address'; import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; @@ -25,7 +25,10 @@ type ConnectedAccountType = Pick< export class ImapGetMessagesService { private readonly logger = new Logger(ImapGetMessagesService.name); - constructor(private readonly fetchByBatchService: ImapFetchByBatchService) {} + constructor( + private readonly fetchByBatchService: ImapFetchByBatchService, + private readonly messageTextExtractor: ImapMessageTextExtractorService, + ) {} async getMessages( messageIds: string[], @@ -44,7 +47,7 @@ export class ImapGetMessagesService { continue; } - const { batchResults } = await this.fetchByBatchService.fetchAllByBatches( + const { results } = await this.fetchByBatchService.fetchAllByBatches( uids, connectedAccount, folder, @@ -52,8 +55,8 @@ export class ImapGetMessagesService { this.logger.log(`IMAP fetch completed for folder: ${folder}`); - const messages = this.formatBatchResponsesAsMessages( - batchResults, + const messages = this.formatBatchResponseAsMessages( + results, connectedAccount, folder, ); @@ -86,23 +89,6 @@ export class ImapGetMessagesService { return folderToUidsMap; } - public formatBatchResponsesAsMessages( - batchResults: MessageFetchResult[][], - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - 'handle' | 'handleAliases' - >, - folder: string, - ): MessageWithParticipants[] { - return batchResults.flatMap((batchResult) => { - return this.formatBatchResponseAsMessages( - batchResult, - connectedAccount, - folder, - ); - }); - } - private formatBatchResponseAsMessages( batchResults: MessageFetchResult[], connectedAccount: Pick< @@ -158,7 +144,7 @@ export class ImapGetMessagesService { const fromHandle = fromAddresses.length > 0 ? fromAddresses[0].address : ''; const textWithoutReplyQuotations = - extractTextWithoutReplyQuotations(parsed); + this.messageTextExtractor.extractTextWithoutReplyQuotations(parsed); const direction = computeMessageDirection(fromHandle, connectedAccount); const text = sanitizeString(textWithoutReplyQuotations); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-processor.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-processor.service.ts index aedfed9c2e..62164fd8d9 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-processor.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-processor.service.ts @@ -66,23 +66,23 @@ export class ImapMessageProcessorService { { uid: true }, ); - const messagesData = new Map(); + const processedMessages = new Map(); for await (const message of fetchResults) { - messagesData.set(message.uid, message); + const result = await this.processMessageData( + message.uid, + message, + startTime, + ); + + processedMessages.set(message.uid, result); } for (const uid of uids) { - const messageData = messagesData.get(uid); + const processedMessage = processedMessages.get(uid); - if (messageData) { - const result = await this.processMessageData( - uid, - messageData, - startTime, - ); - - results.push(result); + if (processedMessage) { + results.push(processedMessage); } else { results.push({ uid, @@ -108,9 +108,7 @@ export class ImapMessageProcessorService { startTime: number, ): Promise { try { - const rawContent = messageData.source?.toString() || ''; - - if (!rawContent) { + if (!messageData.source) { this.logger.debug(`No source content for message UID ${uid}`); return { @@ -120,7 +118,7 @@ export class ImapMessageProcessorService { }; } - const parsed = await this.parseMessage(rawContent, uid); + const parsed = await this.parseMessageFromBuffer(messageData.source, uid); const processingTime = Date.now() - startTime; this.logger.debug(`Processed message UID ${uid} in ${processingTime}ms`); @@ -135,12 +133,17 @@ export class ImapMessageProcessorService { } } - private async parseMessage( - rawContent: string, + private async parseMessageFromBuffer( + source: Buffer, uid: number, ): Promise { try { - return await simpleParser(rawContent); + return await simpleParser(source, { + skipTextToHtml: true, + skipImageLinks: true, + skipTextLinks: true, + keepCidLinks: false, + }); } catch (error) { this.logger.error(`Failed to parse message UID ${uid}: ${error.message}`); throw error; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts new file mode 100644 index 0000000000..f81f151a0a --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; + +import DOMPurify from 'dompurify'; +import { convert } from 'html-to-text'; +import { JSDOM } from 'jsdom'; +import { type ParsedMail } from 'mailparser'; +import * as planer from 'planer'; + +import { safeDecodeURIComponent } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util'; + +@Injectable() +export class ImapMessageTextExtractorService { + private readonly jsdomInstance: JSDOM; + private readonly purify: DOMPurify.DOMPurify; + + constructor() { + this.jsdomInstance = new JSDOM(''); + this.purify = DOMPurify(this.jsdomInstance.window); + } + + extractTextWithoutReplyQuotations(parsed: ParsedMail): string { + if (parsed.text) { + const extractedText = planer.extractFrom(parsed.text, 'text/plain'); + + return safeDecodeURIComponent(extractedText); + } + + if (parsed.html) { + const sanitizedHtml = this.purify.sanitize(parsed.html); + + this.jsdomInstance.window.document.documentElement.innerHTML = `${sanitizedHtml}`; + + const cleanedHtml = planer.extractFromHtml( + sanitizedHtml, + this.jsdomInstance.window.document, + ); + + const text = convert(cleanedHtml, { + wordwrap: false, + preserveNewlines: true, + }).trim(); + + const processedText = text.replace(/\u00A0/g, ' '); + + return safeDecodeURIComponent(processedText); + } + + return ''; + } +} diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/extract-message-text.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/extract-message-text.util.spec.ts index 02c064d87d..9f42113fb6 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/extract-message-text.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/extract-message-text.util.spec.ts @@ -1,8 +1,14 @@ import { type ParsedMail } from 'mailparser'; -import { extractTextWithoutReplyQuotations } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/extract-message-text.util'; +import { ImapMessageTextExtractorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-text-extractor.service'; + +describe('ImapMessageTextExtractorService', () => { + let service: ImapMessageTextExtractorService; + + beforeEach(() => { + service = new ImapMessageTextExtractorService(); + }); -describe('extractTextWithoutReplyQuotations', () => { it('should extract text from plain text emails with lot of reply quotations', () => { const parsed: ParsedMail = { text: `Hi John, @@ -96,7 +102,7 @@ Developer Support html: false, }; - const result = extractTextWithoutReplyQuotations(parsed); + const result = service.extractTextWithoutReplyQuotations(parsed); expect(result).toBe(`Hi John, @@ -132,7 +138,7 @@ Developer Support`); html: false, }; - const result = extractTextWithoutReplyQuotations(parsed); + const result = service.extractTextWithoutReplyQuotations(parsed); expect(result).toBe('just a follow up'); }); @@ -154,7 +160,7 @@ Developer Support`); headerLines: [], }; - const result = extractTextWithoutReplyQuotations(parsed); + const result = service.extractTextWithoutReplyQuotations(parsed); expect(result).toBe('just a follow up'); }); @@ -167,7 +173,7 @@ Developer Support`); html: false, }; - const result = extractTextWithoutReplyQuotations(parsed); + const result = service.extractTextWithoutReplyQuotations(parsed); expect(result).toBe(''); }); @@ -295,7 +301,7 @@ Developer Support`);
Hi Sarah,

I wanted to quickly follow up regarding the Q3 marketing campaign results.  
We’ve seen a 14% increase in engagement compared to last quarter, but conversions are still slightly below target.  

Let’s schedule a short call early next week to discuss adjustments before the Q4 push.  
Would Monday 10 AM work for you?

Best regards,  
John

`, }; - const result = extractTextWithoutReplyQuotations(parsed); + const result = service.extractTextWithoutReplyQuotations(parsed); expect(result).toEqual(`Hi Sarah, @@ -321,7 +327,7 @@ John`); headerLines: [], }; - const result = extractTextWithoutReplyQuotations(parsed); + const result = service.extractTextWithoutReplyQuotations(parsed); expect(result).toBe('Plain text content'); }); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/extract-message-text.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/extract-message-text.util.ts deleted file mode 100644 index 9c3450985d..0000000000 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/extract-message-text.util.ts +++ /dev/null @@ -1,34 +0,0 @@ -import DOMPurify from 'dompurify'; -import { convert } from 'html-to-text'; -import { JSDOM } from 'jsdom'; -import { type ParsedMail } from 'mailparser'; -import * as planer from 'planer'; - -export const extractTextWithoutReplyQuotations = ( - parsed: ParsedMail, -): string => { - if (parsed.text) { - return planer.extractFrom(parsed.text, 'text/plain'); - } - - if (parsed.html) { - const window = new JSDOM('').window; - const purify = DOMPurify(window); - const sanitizedHtml = purify.sanitize(parsed.html); - const dom = new JSDOM(sanitizedHtml, { runScripts: 'outside-only' }); - - const cleanedHtml = planer.extractFromHtml( - sanitizedHtml, - dom.window.document, - ); - - const text = convert(cleanedHtml, { - wordwrap: false, - preserveNewlines: true, - }).trim(); - - return text.replace(/\u00A0/g, ' '); - } - - return ''; -}; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util.ts new file mode 100644 index 0000000000..518f7bdc99 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/safe-decode-uri-component.util.ts @@ -0,0 +1,7 @@ +export const safeDecodeURIComponent = (text: string): string => { + try { + return decodeURIComponent(text); + } catch { + return text; + } +}; diff --git a/yarn.lock b/yarn.lock index 2a1b76af98..5749bd03a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7133,47 +7133,6 @@ __metadata: languageName: node linkType: hard -"@jest/core@npm:30.1.3": - version: 30.1.3 - resolution: "@jest/core@npm:30.1.3" - dependencies: - "@jest/console": "npm:30.1.2" - "@jest/pattern": "npm:30.0.1" - "@jest/reporters": "npm:30.1.3" - "@jest/test-result": "npm:30.1.3" - "@jest/transform": "npm:30.1.2" - "@jest/types": "npm:30.0.5" - "@types/node": "npm:*" - ansi-escapes: "npm:^4.3.2" - chalk: "npm:^4.1.2" - ci-info: "npm:^4.2.0" - exit-x: "npm:^0.2.2" - graceful-fs: "npm:^4.2.11" - jest-changed-files: "npm:30.0.5" - jest-config: "npm:30.1.3" - jest-haste-map: "npm:30.1.0" - jest-message-util: "npm:30.1.0" - jest-regex-util: "npm:30.0.1" - jest-resolve: "npm:30.1.3" - jest-resolve-dependencies: "npm:30.1.3" - jest-runner: "npm:30.1.3" - jest-runtime: "npm:30.1.3" - jest-snapshot: "npm:30.1.2" - jest-util: "npm:30.0.5" - jest-validate: "npm:30.1.0" - jest-watcher: "npm:30.1.3" - micromatch: "npm:^4.0.8" - pretty-format: "npm:30.0.5" - slash: "npm:^3.0.0" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/0f934a027b969daebe8e44ba3127a80c2ac1a65becc16b1f9d5a072718d950ce3c1910ce0675a98d5ef1777014e842b5bf6dd736fb9c6442a22ebfd326ae50c5 - languageName: node - linkType: hard - "@jest/core@npm:^29.7.0": version: 29.7.0 resolution: "@jest/core@npm:29.7.0" @@ -7419,42 +7378,6 @@ __metadata: languageName: node linkType: hard -"@jest/reporters@npm:30.1.3, @jest/reporters@npm:^30.0.2": - version: 30.1.3 - resolution: "@jest/reporters@npm:30.1.3" - dependencies: - "@bcoe/v8-coverage": "npm:^0.2.3" - "@jest/console": "npm:30.1.2" - "@jest/test-result": "npm:30.1.3" - "@jest/transform": "npm:30.1.2" - "@jest/types": "npm:30.0.5" - "@jridgewell/trace-mapping": "npm:^0.3.25" - "@types/node": "npm:*" - chalk: "npm:^4.1.2" - collect-v8-coverage: "npm:^1.0.2" - exit-x: "npm:^0.2.2" - glob: "npm:^10.3.10" - graceful-fs: "npm:^4.2.11" - istanbul-lib-coverage: "npm:^3.0.0" - istanbul-lib-instrument: "npm:^6.0.0" - istanbul-lib-report: "npm:^3.0.0" - istanbul-lib-source-maps: "npm:^5.0.0" - istanbul-reports: "npm:^3.1.3" - jest-message-util: "npm:30.1.0" - jest-util: "npm:30.0.5" - jest-worker: "npm:30.1.0" - slash: "npm:^3.0.0" - string-length: "npm:^4.0.2" - v8-to-istanbul: "npm:^9.0.1" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - checksum: 10c0/2b027e775216804b4f3766105bd4eb3e8e31480f78cc9a409ae7d335cc21883f5bd063c66215c0bc18cbcdda98824d1b02e74593f6464caf8920f0804ce706bb - languageName: node - linkType: hard - "@jest/reporters@npm:^29.7.0": version: 29.7.0 resolution: "@jest/reporters@npm:29.7.0" @@ -7492,6 +7415,42 @@ __metadata: languageName: node linkType: hard +"@jest/reporters@npm:^30.0.2": + version: 30.1.3 + resolution: "@jest/reporters@npm:30.1.3" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:30.1.2" + "@jest/test-result": "npm:30.1.3" + "@jest/transform": "npm:30.1.2" + "@jest/types": "npm:30.0.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + collect-v8-coverage: "npm:^1.0.2" + exit-x: "npm:^0.2.2" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^5.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:30.1.0" + jest-util: "npm:30.0.5" + jest-worker: "npm:30.1.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.2" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/2b027e775216804b4f3766105bd4eb3e8e31480f78cc9a409ae7d335cc21883f5bd063c66215c0bc18cbcdda98824d1b02e74593f6464caf8920f0804ce706bb + languageName: node + linkType: hard + "@jest/schemas@npm:30.0.0-beta.3": version: 30.0.0-beta.3 resolution: "@jest/schemas@npm:30.0.0-beta.3" @@ -18873,7 +18832,7 @@ __metadata: languageName: node linkType: hard -"@swc/core@npm:^1.12.11, @swc/core@npm:^1.5.22, @swc/core@npm:^1.9.3": +"@swc/core@npm:^1.12.11, @swc/core@npm:^1.5.22": version: 1.13.5 resolution: "@swc/core@npm:1.13.5" dependencies: @@ -18954,7 +18913,7 @@ __metadata: languageName: node linkType: hard -"@swc/jest@npm:^0.2.23, @swc/jest@npm:^0.2.36, @swc/jest@npm:^0.2.39": +"@swc/jest@npm:^0.2.23, @swc/jest@npm:^0.2.39": version: 0.2.39 resolution: "@swc/jest@npm:0.2.39" dependencies: @@ -20815,7 +20774,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=8.1.0, @types/node@npm:^24.0.0, @types/node@npm:^24.3.1": +"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=8.1.0, @types/node@npm:^24.0.0": version: 24.3.1 resolution: "@types/node@npm:24.3.1" dependencies: @@ -35354,7 +35313,7 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.0.2, import-local@npm:^3.2.0": +"import-local@npm:^3.0.2": version: 3.2.0 resolution: "import-local@npm:3.2.0" dependencies: @@ -36823,17 +36782,6 @@ __metadata: languageName: node linkType: hard -"jest-changed-files@npm:30.0.5": - version: 30.0.5 - resolution: "jest-changed-files@npm:30.0.5" - dependencies: - execa: "npm:^5.1.1" - jest-util: "npm:30.0.5" - p-limit: "npm:^3.1.0" - checksum: 10c0/41ce090f324e8450443327f19f772a9c3f225b4b1374ba9704358f0c8b8cd91fd134fa41df7db4d278428ab974c432abc3eca9484e67c8f18528974378fddef6 - languageName: node - linkType: hard - "jest-changed-files@npm:^29.7.0": version: 29.7.0 resolution: "jest-changed-files@npm:29.7.0" @@ -36901,31 +36849,6 @@ __metadata: languageName: node linkType: hard -"jest-cli@npm:30.1.3": - version: 30.1.3 - resolution: "jest-cli@npm:30.1.3" - dependencies: - "@jest/core": "npm:30.1.3" - "@jest/test-result": "npm:30.1.3" - "@jest/types": "npm:30.0.5" - chalk: "npm:^4.1.2" - exit-x: "npm:^0.2.2" - import-local: "npm:^3.2.0" - jest-config: "npm:30.1.3" - jest-util: "npm:30.0.5" - jest-validate: "npm:30.1.0" - yargs: "npm:^17.7.2" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/e15e811b0a14625ce70b1b3948955e9a9c7a523b2c7970effa8f924006dc4d1839b5636fa5c7c5c772d7959ed331a45b0e9d85d3b71f9a065aa6440ae3c1aa64 - languageName: node - linkType: hard - "jest-cli@npm:^29.7.0": version: 29.7.0 resolution: "jest-cli@npm:29.7.0" @@ -36952,7 +36875,45 @@ __metadata: languageName: node linkType: hard -"jest-config@npm:30.1.3, jest-config@npm:^30.0.2": +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + languageName: node + linkType: hard + +"jest-config@npm:^30.0.2": version: 30.1.3 resolution: "jest-config@npm:30.1.3" dependencies: @@ -36995,44 +36956,6 @@ __metadata: languageName: node linkType: hard -"jest-config@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-config@npm:29.7.0" - dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/test-sequencer": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-jest: "npm:^29.7.0" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - deepmerge: "npm:^4.2.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-circus: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - micromatch: "npm:^4.0.4" - parse-json: "npm:^5.2.0" - pretty-format: "npm:^29.7.0" - slash: "npm:^3.0.0" - strip-json-comments: "npm:^3.1.1" - peerDependencies: - "@types/node": "*" - ts-node: ">=9.0.0" - peerDependenciesMeta: - "@types/node": - optional: true - ts-node: - optional: true - checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 - languageName: node - linkType: hard - "jest-diff@npm:30.1.2, jest-diff@npm:^30.0.2": version: 30.1.2 resolution: "jest-diff@npm:30.1.2" @@ -37420,16 +37343,6 @@ __metadata: languageName: node linkType: hard -"jest-resolve-dependencies@npm:30.1.3": - version: 30.1.3 - resolution: "jest-resolve-dependencies@npm:30.1.3" - dependencies: - jest-regex-util: "npm:30.0.1" - jest-snapshot: "npm:30.1.2" - checksum: 10c0/1fc144c3107ad7faae455ac13b32de0cabb8e2718a55f431246a222da86c6da539f80230814b1cbcaf22c06df704c3c00ab761d065b9a9241659757e3fd28f66 - languageName: node - linkType: hard - "jest-resolve-dependencies@npm:^29.7.0": version: 29.7.0 resolution: "jest-resolve-dependencies@npm:29.7.0" @@ -37832,25 +37745,6 @@ __metadata: languageName: node linkType: hard -"jest@npm:^30.1.3": - version: 30.1.3 - resolution: "jest@npm:30.1.3" - dependencies: - "@jest/core": "npm:30.1.3" - "@jest/types": "npm:30.0.5" - import-local: "npm:^3.2.0" - jest-cli: "npm:30.1.3" - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - bin: - jest: ./bin/jest.js - checksum: 10c0/6f0c18d8c94cfb3158cae90d048f38985b8aab9634f2fd8481f09ac0839697bf4a14fc3ed46d9d16e5bf653cad3af12547af62fbc46f64b3f06b32f8f8ee7d55 - languageName: node - linkType: hard - "jiti@npm:1.17.1": version: 1.17.1 resolution: "jiti@npm:1.17.1" @@ -51500,19 +51394,6 @@ __metadata: languageName: node linkType: hard -"twenty-apps@workspace:packages/twenty-apps": - version: 0.0.0-use.local - resolution: "twenty-apps@workspace:packages/twenty-apps" - dependencies: - "@swc/core": "npm:^1.9.3" - "@swc/jest": "npm:^0.2.36" - "@types/jest": "npm:^30.0.0" - "@types/node": "npm:^24.3.1" - jest: "npm:^30.1.3" - typescript: "npm:^5.9.2" - languageName: unknown - linkType: soft - "twenty-cli@workspace:packages/twenty-cli": version: 0.0.0-use.local resolution: "twenty-cli@workspace:packages/twenty-cli"