fix: create company workflow (use psl for domain suffix) (#19289)

Fixes edge case where suffix like `.com.cy` which is TLD for Cyprus
would attach person to wrong company

<img width="715" height="115" alt="image"
src="https://github.com/user-attachments/assets/bbaf7c2c-8365-4655-afda-508fbc04de2a"
/>
This commit is contained in:
neo773
2026-04-03 13:44:11 +05:30
committed by GitHub
parent bb3d556799
commit baa4fda3d2
4 changed files with 30 additions and 122 deletions
@@ -38,6 +38,7 @@
"lodash.snakecase": "^4.1.1",
"lodash.upperfirst": "^4.3.1",
"nodemailer": "^8.0.4",
"psl": "^1.15.0",
"sharp": "^0.34.5",
"uuid": "^10.0.0",
"winston": "^3.14.2"
@@ -2374,6 +2374,22 @@ __metadata:
languageName: node
linkType: hard
"psl@npm:^1.15.0":
version: 1.15.0
resolution: "psl@npm:1.15.0"
dependencies:
punycode: "npm:^2.3.1"
checksum: 10c0/d8d45a99e4ca62ca12ac3c373e63d80d2368d38892daa40cfddaa1eb908be98cd549ac059783ef3a56cfd96d57ae8e2fd9ae53d1378d90d42bc661ff924e102a
languageName: node
linkType: hard
"punycode@npm:^2.3.1":
version: 2.3.1
resolution: "punycode@npm:2.3.1"
checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9
languageName: node
linkType: hard
"qs@npm:~6.14.0":
version: 6.14.2
resolution: "qs@npm:6.14.2"
@@ -2511,6 +2527,7 @@ __metadata:
lodash.snakecase: "npm:^4.1.1"
lodash.upperfirst: "npm:^4.3.1"
nodemailer: "npm:^8.0.4"
psl: "npm:^1.15.0"
sharp: "npm:^0.34.5"
uuid: "npm:^10.0.0"
winston: "npm:^3.14.2"
@@ -7,8 +7,8 @@ import { SEED_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/application/a
// To regenerate: use logicFunctionCreateHash from logic-function-create-hash.utils.
// package.json: hash(JSON.stringify(JSON.parse(content))). yarn.lock: hash(content).
// Both use first 32 chars of SHA512 hex digest.
const DEFAULT_PACKAGE_JSON_CHECKSUM = '4cf57bd317cfe8e49c47b0aa76aabb39';
const DEFAULT_YARN_LOCK_CHECKSUM = '415a52f896221c813b6bcafad8d93b19';
const DEFAULT_PACKAGE_JSON_CHECKSUM = 'cce6edc8bb5046d992b51a3260b19bfe';
const DEFAULT_YARN_LOCK_CHECKSUM = 'e290256e22000e0f4b46001b91999d16';
export type DefaultApplicationPackageFields = {
packageJsonChecksum: string;
@@ -56,53 +56,13 @@ const FIND_MATCHING_COMPANY_BY_DOMAIN_TOOL_INPUT_SCHEMA = {
required: ['companies', 'domain'],
};
const EXTRACT_DOMAIN_LOGIC_FUNCTION_SOURCE = `const MULTI_PART_SUFFIXES = new Set([
'ac.uk',
'co.in',
'co.jp',
'co.kr',
'co.nz',
'co.uk',
'co.za',
'com.ar',
'com.au',
'com.br',
'com.cn',
'com.hk',
'com.mx',
'com.sg',
'com.tr',
'com.tw',
'com.ua',
'gov.uk',
'ne.jp',
'net.au',
'org.au',
'org.uk',
]);
const getRegistrableDomain = (host) => {
const normalizedHost = host.toLowerCase().replace(/^www\\./, '');
const parts = normalizedHost.split('.').filter(Boolean);
if (parts.length <= 2) {
return normalizedHost;
}
const lastTwo = parts.slice(-2).join('.');
if (MULTI_PART_SUFFIXES.has(lastTwo) && parts.length >= 3) {
return parts.slice(-3).join('.');
}
return lastTwo;
};
const EXTRACT_DOMAIN_LOGIC_FUNCTION_SOURCE = `const psl = require('psl');
export const main = async (params) => {
const email =
typeof params?.email === 'string' ? params.email.trim().toLowerCase() : '';
const domainFromEmail = email.split('@')[1] ?? '';
const domain = domainFromEmail ? getRegistrableDomain(domainFromEmail) : '';
const host = email.split('@')[1] ?? '';
const domain = host ? (psl.get(host) ?? host) : '';
return {
url: domain ? \`https://\${domain}\` : '',
@@ -110,30 +70,7 @@ export const main = async (params) => {
};
};`;
const IS_PERSONAL_EMAIL_LOGIC_FUNCTION_SOURCE = `const MULTI_PART_SUFFIXES = new Set([
'ac.uk',
'co.in',
'co.jp',
'co.kr',
'co.nz',
'co.uk',
'co.za',
'com.ar',
'com.au',
'com.br',
'com.cn',
'com.hk',
'com.mx',
'com.sg',
'com.tr',
'com.tw',
'com.ua',
'gov.uk',
'ne.jp',
'net.au',
'org.au',
'org.uk',
]);
const IS_PERSONAL_EMAIL_LOGIC_FUNCTION_SOURCE = `const psl = require('psl');
const PERSONAL_EMAIL_DOMAINS = new Set([
'aol.com',
@@ -172,23 +109,6 @@ const PERSONAL_EMAIL_DOMAINS = new Set([
'gmail.com',
]);
const getRegistrableDomain = (host) => {
const normalizedHost = host.toLowerCase().replace(/^www\\./, '');
const parts = normalizedHost.split('.').filter(Boolean);
if (parts.length <= 2) {
return normalizedHost;
}
const lastTwo = parts.slice(-2).join('.');
if (MULTI_PART_SUFFIXES.has(lastTwo) && parts.length >= 3) {
return parts.slice(-3).join('.');
}
return lastTwo;
};
export const main = async (params) => {
const email =
typeof params?.primaryEmail === 'string'
@@ -200,7 +120,7 @@ export const main = async (params) => {
}
const host = email.split('@')[1] ?? '';
const registrableDomain = host ? getRegistrableDomain(host) : '';
const registrableDomain = host ? (psl.get(host) ?? '') : '';
return {
isPersonal:
@@ -210,30 +130,7 @@ export const main = async (params) => {
};
};`;
const FIND_MATCHING_COMPANY_BY_DOMAIN_LOGIC_FUNCTION_SOURCE = `const MULTI_PART_SUFFIXES = new Set([
'ac.uk',
'co.in',
'co.jp',
'co.kr',
'co.nz',
'co.uk',
'co.za',
'com.ar',
'com.au',
'com.br',
'com.cn',
'com.hk',
'com.mx',
'com.sg',
'com.tr',
'com.tw',
'com.ua',
'gov.uk',
'ne.jp',
'net.au',
'org.au',
'org.uk',
]);
const FIND_MATCHING_COMPANY_BY_DOMAIN_LOGIC_FUNCTION_SOURCE = `const psl = require('psl');
const normalizeHost = (value) => {
if (typeof value !== 'string') {
@@ -252,20 +149,13 @@ const normalizeHost = (value) => {
};
const getRegistrableDomain = (value) => {
const normalizedHost = normalizeHost(value);
const parts = normalizedHost.split('.').filter(Boolean);
const host = normalizeHost(value);
if (parts.length <= 2) {
return normalizedHost;
if (!host) {
return '';
}
const lastTwo = parts.slice(-2).join('.');
if (MULTI_PART_SUFFIXES.has(lastTwo) && parts.length >= 3) {
return parts.slice(-3).join('.');
}
return lastTwo;
return psl.get(host) ?? host;
};
export const main = async (params) => {