From 3c5595e4ff48c49b257031c985bf6b0c40b09ca1 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:31:27 +0200 Subject: [PATCH] Nitpick: psl types (#12925) Close https://github.com/twentyhq/twenty/issues/12917 --- packages/twenty-server/package.json | 1 + .../types/is-psl-parsed-domain.type.ts | 8 ++ ...company-name-from-domain-name.util.spec.ts | 60 +++++++++++++++ .../get-domain-name-from-handle.util.spec.ts | 73 +++++++++++++++++++ .../get-company-name-from-domain-name.util.ts | 13 +++- .../utils/get-domain-name-from-handle.util.ts | 11 ++- yarn.lock | 8 ++ 7 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 packages/twenty-server/src/modules/contact-creation-manager/types/is-psl-parsed-domain.type.ts create mode 100644 packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-company-name-from-domain-name.util.spec.ts create mode 100644 packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-domain-name-from-handle.util.spec.ts diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index 8ec5eb187..0c764b3bb 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -83,6 +83,7 @@ "@types/lodash.uniqby": "^4.7.9", "@types/lodash.upperfirst": "^4.3.7", "@types/openid-client": "^3.7.0", + "@types/psl": "^1.1.3", "@types/react": "^18.2.39", "@types/unzipper": "^0", "rimraf": "^5.0.5", diff --git a/packages/twenty-server/src/modules/contact-creation-manager/types/is-psl-parsed-domain.type.ts b/packages/twenty-server/src/modules/contact-creation-manager/types/is-psl-parsed-domain.type.ts new file mode 100644 index 000000000..3c57ff749 --- /dev/null +++ b/packages/twenty-server/src/modules/contact-creation-manager/types/is-psl-parsed-domain.type.ts @@ -0,0 +1,8 @@ +import psl, { ParsedDomain } from 'psl'; +import { isDefined } from 'twenty-shared/utils'; + +export const isParsedDomain = ( + result: ReturnType, +): result is ParsedDomain => + !isDefined(result.error) && + Object.prototype.hasOwnProperty.call(result, 'sld'); diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-company-name-from-domain-name.util.spec.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-company-name-from-domain-name.util.spec.ts new file mode 100644 index 000000000..4e16b3838 --- /dev/null +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-company-name-from-domain-name.util.spec.ts @@ -0,0 +1,60 @@ +import { EachTestingContext } from 'twenty-shared/testing'; + +import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util'; + +type GetCompanyNameFromDomainNameTestCase = EachTestingContext<{ + input: string; + expected: string; +}>; + +describe('getCompanyNameFromDomainName', () => { + const testCases: GetCompanyNameFromDomainNameTestCase[] = [ + { + title: 'should extract and capitalize company name from simple domain', + context: { + input: 'twenty.dev', + expected: 'Twenty', + }, + }, + { + title: 'should extract and capitalize company name from subdomain', + context: { + input: 'app.twenty.dev', + expected: 'Twenty', + }, + }, + { + title: + 'should extract and capitalize company name from multiple subdomains', + context: { + input: 'test.app.twenty.dev', + expected: 'Twenty', + }, + }, + { + title: 'should handle domain with multiple parts', + context: { + input: 'twenty.co.uk', + expected: 'Twenty', + }, + }, + { + title: 'should handle empty string', + context: { + input: '', + expected: '', + }, + }, + { + title: 'should handle invalid domain', + context: { + input: 'not-a-valid-domain', + expected: '', + }, + }, + ]; + + test.each(testCases)('$title', ({ context: { input, expected } }) => { + expect(getCompanyNameFromDomainName(input)).toBe(expected); + }); +}); diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-domain-name-from-handle.util.spec.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-domain-name-from-handle.util.spec.ts new file mode 100644 index 000000000..53eefabab --- /dev/null +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/__tests__/get-domain-name-from-handle.util.spec.ts @@ -0,0 +1,73 @@ +import { EachTestingContext } from 'twenty-shared/testing'; + +import { getDomainNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util'; + +type GetDomainNameFromHandleTestCase = EachTestingContext<{ + input: string; + expected: string; +}>; + +describe('getDomainNameFromHandle', () => { + const testCases: GetDomainNameFromHandleTestCase[] = [ + { + title: 'should extract domain from email handle', + context: { + input: 'user@twenty.dev', + expected: 'twenty.dev', + }, + }, + { + title: 'should extract domain from email handle with subdomain', + context: { + input: 'user@app.twenty.dev', + expected: 'twenty.dev', + }, + }, + { + title: 'should extract domain from email handle with multiple subdomains', + context: { + input: 'user@test.app.twenty.dev', + expected: 'twenty.dev', + }, + }, + { + title: 'should handle domain with multiple parts', + context: { + input: 'user@twenty.co.uk', + expected: 'twenty.co.uk', + }, + }, + { + title: 'should handle empty string', + context: { + input: '', + expected: '', + }, + }, + { + title: 'should handle string without @ symbol', + context: { + input: 'not-an-email', + expected: '', + }, + }, + { + title: 'should handle undefined handle part after @', + context: { + input: 'user@', + expected: '', + }, + }, + { + title: 'should handle invalid domain', + context: { + input: 'user@not-a-valid-domain', + expected: '', + }, + }, + ]; + + test.each(testCases)('$title', ({ context: { input, expected } }) => { + expect(getDomainNameFromHandle(input)).toBe(expected); + }); +}); diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util.ts index f7be1b469..1c469f05e 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util.ts @@ -1,9 +1,14 @@ -// @ts-expect-error legacy noImplicitAny import psl from 'psl'; import { capitalize } from 'twenty-shared/utils'; -export const getCompanyNameFromDomainName = (domainName: string) => { - const { sld } = psl.parse(domainName); +import { isParsedDomain } from 'src/modules/contact-creation-manager/types/is-psl-parsed-domain.type'; - return sld ? capitalize(sld) : ''; +export const getCompanyNameFromDomainName = (domainName: string) => { + const result = psl.parse(domainName); + + if (!isParsedDomain(result)) { + return ''; + } + + return result.sld ? capitalize(result.sld) : ''; }; diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util.ts index 4004ec329..22db0ad27 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util.ts @@ -1,10 +1,15 @@ -// @ts-expect-error legacy noImplicitAny import psl from 'psl'; +import { isParsedDomain } from 'src/modules/contact-creation-manager/types/is-psl-parsed-domain.type'; + export const getDomainNameFromHandle = (handle: string): string => { const wholeDomain = handle?.split('@')?.[1] || ''; - const { domain } = psl.parse(wholeDomain); + const result = psl.parse(wholeDomain); - return domain || ''; + if (!isParsedDomain(result)) { + return ''; + } + + return result.domain ?? ''; }; diff --git a/yarn.lock b/yarn.lock index 1e94dc983..874b91c88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24413,6 +24413,13 @@ __metadata: languageName: node linkType: hard +"@types/psl@npm:^1.1.3": + version: 1.1.3 + resolution: "@types/psl@npm:1.1.3" + checksum: 10c0/8719bac618613a2f47d4d2088742c2c01c00fd8d6fe92a3ed474aad83f7199ff53c813d8eee678768d816b02fc94c6e1f0cb4bce2e512efd0bcb3af2524ae5a6 + languageName: node + linkType: hard + "@types/qs@npm:*, @types/qs@npm:^6.9.5": version: 6.9.15 resolution: "@types/qs@npm:6.9.15" @@ -56556,6 +56563,7 @@ __metadata: "@types/lodash.uniqby": "npm:^4.7.9" "@types/lodash.upperfirst": "npm:^4.3.7" "@types/openid-client": "npm:^3.7.0" + "@types/psl": "npm:^1.1.3" "@types/react": "npm:^18.2.39" "@types/unzipper": "npm:^0" ai: "npm:^4.3.16"