Imap perf improvement (#14629)
- 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 <felix.malfait@gmail.com> Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
+2
@@ -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,
|
||||
|
||||
+5
-12
@@ -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<FetchAllResult> {
|
||||
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);
|
||||
|
||||
+9
-23
@@ -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);
|
||||
|
||||
+21
-18
@@ -66,23 +66,23 @@ export class ImapMessageProcessorService {
|
||||
{ uid: true },
|
||||
);
|
||||
|
||||
const messagesData = new Map<number, FetchMessageObject>();
|
||||
const processedMessages = new Map<number, MessageFetchResult>();
|
||||
|
||||
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<MessageFetchResult> {
|
||||
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<ParsedMail> {
|
||||
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;
|
||||
|
||||
+50
@@ -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 = `<html><body>${sanitizedHtml}</body></html>`;
|
||||
|
||||
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 '';
|
||||
}
|
||||
}
|
||||
+14
-8
@@ -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`);
|
||||
</style></head><body><div id="inbox-html-wrapper"><div id="isPasted" fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;">Hi Sarah,</div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"><br fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"></div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;">I wanted to quickly follow up regarding the Q3 marketing campaign results. </div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;">We’ve seen a 14% increase in engagement compared to last quarter, but conversions are still slightly below target. </div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"><br fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"></div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;">Let’s schedule a short call early next week to discuss adjustments before the Q4 push. </div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;">Would Monday 10 AM work for you?</div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"><br fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"></div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;">Best regards, </div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;">John</div><div fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"><br fr-original-style="" style="user-select: inherit; scrollbar-color: var(--scrollbar-active-color) #0000; box-sizing: border-box;"></div><img class="flm-open" width="0" height="0" style="border: 0px; width: 0px; height: 0px; max-width: 100vw;" data-open-tracking-src="{{track-read-receipt}}"></div></body></html>`,
|
||||
};
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
-34
@@ -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 '';
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export const safeDecodeURIComponent = (text: string): string => {
|
||||
try {
|
||||
return decodeURIComponent(text);
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user