Logic function refactorization (#17861)

As title
This commit is contained in:
martmull
2026-02-12 11:40:49 +01:00
committed by GitHub
parent b456f79167
commit a4ed043d43
122 changed files with 1441 additions and 1897 deletions
@@ -0,0 +1,11 @@
import path from 'path';
import { ASSET_PATH } from 'src/constants/assets-path';
export const SEED_DEPENDENCIES_DIRNAME = path.resolve(
__dirname,
path.join(
ASSET_PATH,
'engine/core-modules/application/constants/seed-dependencies',
),
);
@@ -0,0 +1,45 @@
{
"dependencies": {
"@types/bcrypt": "^6.0.0",
"@types/deep-equal": "^1.0.4",
"@types/lodash.camelcase": "^4.3.9",
"@types/lodash.compact": "^3.0.9",
"@types/lodash.groupby": "^4.6.9",
"@types/lodash.identity": "^3.0.9",
"@types/lodash.isempty": "^4.4.9",
"@types/lodash.isequal": "^4.5.8",
"@types/lodash.isobject": "^3.0.9",
"@types/lodash.kebabcase": "^4.1.9",
"@types/lodash.mapvalues": "^4.6.9",
"@types/lodash.omit": "^4.5.9",
"@types/lodash.pickby": "^4.6.9",
"@types/lodash.snakecase": "^4.1.9",
"@types/lodash.upperfirst": "^4.3.9",
"@types/uuid": "^10.0.0",
"archiver": "^7.0.1",
"axios": "^1.13.5",
"bcrypt": "^6.0.0",
"body-parser": "^1.20.4",
"deep-equal": "^2.2.3",
"jsonwebtoken": "^9.0.2",
"lodash.camelcase": "^4.3.0",
"lodash.chunk": "^4.2.0",
"lodash.compact": "^3.0.1",
"lodash.groupby": "^4.6.0",
"lodash.identity": "^3.0.0",
"lodash.isempty": "^4.4.0",
"lodash.isequal": "^4.5.0",
"lodash.isobject": "^3.0.2",
"lodash.kebabcase": "^4.1.1",
"lodash.mapvalues": "^4.6.0",
"lodash.merge": "^4.6.2",
"lodash.omit": "^4.5.0",
"lodash.pickby": "^4.6.0",
"lodash.snakecase": "^4.1.1",
"lodash.upperfirst": "^4.3.1",
"nodemailer": "^7.0.11",
"sharp": "^0.34.5",
"uuid": "^10.0.0",
"winston": "^3.14.2"
}
}
@@ -0,0 +1,11 @@
import path from 'path';
import { ASSET_PATH } from 'src/constants/assets-path';
export const YARN_ENGINE_DIRNAME = path.resolve(
__dirname,
path.join(
ASSET_PATH,
'engine/core-modules/application/constants/yarn-engine',
),
);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,5 @@
enableInlineHunks: true
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.9.2.cjs
@@ -4,7 +4,6 @@ import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { PackageJson } from 'type-fest';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import {
ApplicationException,
@@ -15,6 +14,7 @@ import { ApplicationInput } from 'src/engine/core-modules/application/dtos/appli
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/services/application-manifest-migration.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
import { getEmptyApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-empty-application-manifest-all-universal-flat-entity-maps.util';
import { getSubApplicationFromToAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-sub-application-from-to-all-flat-entity-maps.util';
import { ApplicationVariableEntityService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
@@ -10,8 +10,8 @@ import {
ApplicationException,
ApplicationExceptionCode,
} from 'src/engine/core-modules/application/application.exception';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant';
@@ -0,0 +1,52 @@
import { execFile } from 'child_process';
import { promises as fs, statSync } from 'fs';
import { join } from 'path';
import { promisify } from 'util';
import { YARN_ENGINE_DIRNAME } from 'src/engine/core-modules/application/constants/yarn-engine-dirname';
const execFilePromise = promisify(execFile);
export const copyYarnEngineAndBuildDependencies = async (
buildDirectory: string,
) => {
await fs.mkdir(buildDirectory, {
recursive: true,
});
await fs.cp(YARN_ENGINE_DIRNAME, buildDirectory, {
recursive: true,
});
const localYarnPath = join(buildDirectory, '.yarn/releases/yarn-4.9.2.cjs');
// Strip NODE_OPTIONS to prevent tsx loader from interfering with yarn
const { NODE_OPTIONS: _nodeOptions, ...cleanEnv } = process.env;
try {
await execFilePromise(process.execPath, [localYarnPath], {
cwd: buildDirectory,
env: cleanEnv,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
const errorMessage =
[error?.stdout, error?.stderr].filter(Boolean).join('\n') ||
'Failed to install logic function executor dependencies';
throw new Error(errorMessage);
}
const objects = await fs.readdir(buildDirectory);
await Promise.all(
objects
.filter((object) => object !== 'node_modules')
.map((object) => {
const fullPath = join(buildDirectory, object);
return statSync(fullPath).isDirectory()
? fs.rm(fullPath, { recursive: true, force: true })
: fs.rm(fullPath);
}),
);
};
@@ -35,6 +35,7 @@ export const fromLogicFunctionManifestToUniversalFlatLogicFunction = ({
logicFunctionManifest.databaseEventTriggerSettings ?? null,
httpRouteTriggerSettings:
logicFunctionManifest.httpRouteTriggerSettings ?? null,
isBuildUpToDate: true,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -0,0 +1,40 @@
import { readFile } from 'fs/promises';
import path from 'path';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { SEED_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/application/constants/seed-dependencies-dirname';
// 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 = 'c160582cf017853b6340d3defec4e6ec';
export type DefaultApplicationPackageFields = {
packageJsonChecksum: string;
yarnLockChecksum: string;
availablePackages: Record<string, string>;
packageJsonContent: string;
yarnLockContent: string;
};
export const getDefaultApplicationPackageFields =
async (): Promise<DefaultApplicationPackageFields> => {
const [packageJsonContent, yarnLockContent] = await Promise.all([
readFile(path.join(SEED_DEPENDENCIES_DIRNAME, 'package.json'), 'utf8'),
readFile(path.join(SEED_DEPENDENCIES_DIRNAME, 'yarn.lock'), 'utf8'),
]);
const availablePackages = parseAvailablePackagesFromPackageJsonAndYarnLock(
packageJsonContent,
yarnLockContent,
);
return {
packageJsonChecksum: DEFAULT_PACKAGE_JSON_CHECKSUM,
yarnLockChecksum: DEFAULT_YARN_LOCK_CHECKSUM,
availablePackages,
packageJsonContent,
yarnLockContent,
};
};
@@ -0,0 +1,31 @@
import { type PackageJson } from 'type-fest';
const PACKAGE_VERSION_REGEX =
/^"(@?[^@]+(?:\/[^@]+)?)@.*?":\n\s+version:\s*(.+)$/gm;
const MAX_PACKAGE_VERSION_MATCHES = 1_000;
export const parseAvailablePackagesFromPackageJsonAndYarnLock = (
packageJsonContent: string,
yarnLockContent: string,
): Record<string, string> => {
const packageJson = JSON.parse(packageJsonContent) as PackageJson;
const versions: Record<string, string> = {};
let match: RegExpExecArray | null;
let matchCount = 0;
while (
matchCount < MAX_PACKAGE_VERSION_MATCHES &&
(match = PACKAGE_VERSION_REGEX.exec(yarnLockContent)) !== null
) {
matchCount += 1;
const packageName = match[1];
const version = match[2];
if (packageJson.dependencies?.[packageName]) {
versions[packageName] = version;
}
}
return versions;
};