diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index 7cedf04348bf..c5778aa6493d 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -44,6 +44,7 @@ "monaco-editor-auto-typings": "^0.4.5", "passport": "^0.7.0", "psl": "^1.9.0", + "ts-morph": "^24.0.0", "tsconfig-paths": "^4.2.0", "typeorm": "patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch", "unzipper": "^0.12.3", diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.exception.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.exception.ts new file mode 100644 index 000000000000..22ebbd7bf300 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.exception.ts @@ -0,0 +1,12 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class CodeIntrospectionException extends CustomException { + code: CodeIntrospectionExceptionCode; + constructor(message: string, code: CodeIntrospectionExceptionCode) { + super(message, code); + } +} + +export enum CodeIntrospectionExceptionCode { + ONLY_ONE_FUNCTION_ALLOWED = 'ONLY_ONE_FUNCTION_ALLOWED', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.module.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.module.ts new file mode 100644 index 000000000000..9d2acb250403 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { CodeIntrospectionService } from 'src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.service'; + +@Module({ + providers: [CodeIntrospectionService], + exports: [CodeIntrospectionService], +}) +export class CodeIntrospectionModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.service.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.service.ts new file mode 100644 index 000000000000..77ed32b63be2 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@nestjs/common'; + +import { + ArrowFunction, + FunctionDeclaration, + Project, + SyntaxKind, +} from 'ts-morph'; + +import { + CodeIntrospectionException, + CodeIntrospectionExceptionCode, +} from 'src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.exception'; + +type FunctionParameter = { + name: string; + type: string; +}; + +@Injectable() +export class CodeIntrospectionService { + private project: Project; + + constructor() { + this.project = new Project(); + } + + public analyze( + fileContent: string, + fileName = 'temp.ts', + ): FunctionParameter[] { + const sourceFile = this.project.createSourceFile(fileName, fileContent, { + overwrite: true, + }); + + const functionDeclarations = sourceFile.getFunctions(); + + if (functionDeclarations.length > 0) { + return this.analyzeFunctions(functionDeclarations); + } + + const arrowFunctions = sourceFile.getDescendantsOfKind( + SyntaxKind.ArrowFunction, + ); + + if (arrowFunctions.length > 0) { + return this.analyzeArrowFunctions(arrowFunctions); + } + + return []; + } + + private analyzeFunctions( + functionDeclarations: FunctionDeclaration[], + ): FunctionParameter[] { + if (functionDeclarations.length > 1) { + throw new CodeIntrospectionException( + 'Only one function is allowed', + CodeIntrospectionExceptionCode.ONLY_ONE_FUNCTION_ALLOWED, + ); + } + + const functionDeclaration = functionDeclarations[0]; + + return functionDeclaration.getParameters().map((parameter) => { + return { + name: parameter.getName(), + type: parameter.getType().getText(), + }; + }); + } + + private analyzeArrowFunctions(arrowFunctions: ArrowFunction[]) { + if (arrowFunctions.length > 1) { + throw new CodeIntrospectionException( + 'Only one arrow function is allowed', + CodeIntrospectionExceptionCode.ONLY_ONE_FUNCTION_ALLOWED, + ); + } + + const arrowFunction = arrowFunctions[0]; + + return arrowFunction.getParameters().map((parameter) => { + return { + name: parameter.getName(), + type: parameter.getType().getText(), + }; + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts index 076343f4d206..95cadf27c682 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.module.ts @@ -14,6 +14,7 @@ import { FileModule } from 'src/engine/core-modules/file/file.module'; import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module'; import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { CodeIntrospectionModule } from 'src/engine/metadata-modules/serverless-function/code-introspection/code-introspection.module'; import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto'; import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { ServerlessFunctionResolver } from 'src/engine/metadata-modules/serverless-function/serverless-function.resolver'; @@ -31,6 +32,7 @@ import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverles TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), FileModule, ThrottlerModule, + CodeIntrospectionModule, ], services: [ServerlessFunctionService], resolvers: [ diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts index 191dc9edf414..db162ac12af3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.service.ts @@ -4,19 +4,24 @@ import { InjectRepository } from '@nestjs/typeorm'; import { basename, dirname, join } from 'path'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; -import { Repository } from 'typeorm'; import deepEqual from 'deep-equal'; +import { Repository } from 'typeorm'; import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; import { ServerlessExecuteResult } from 'src/engine/core-modules/serverless/drivers/interfaces/serverless-driver.interface'; -import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; import { readFileContent } from 'src/engine/core-modules/file-storage/utils/read-file-content'; +import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name'; import { INDEX_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/index-file-name'; +import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version'; +import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files'; +import { getLastLayerDependencies } from 'src/engine/core-modules/serverless/drivers/utils/get-last-layer-dependencies'; import { ServerlessService } from 'src/engine/core-modules/serverless/serverless.service'; import { getServerlessFolder } from 'src/engine/core-modules/serverless/utils/serverless-get-folder.utils'; +import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service'; +import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input'; import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input'; import { ServerlessFunctionEntity, @@ -27,11 +32,6 @@ import { ServerlessFunctionExceptionCode, } from 'src/engine/metadata-modules/serverless-function/serverless-function.exception'; import { isDefined } from 'src/utils/is-defined'; -import { getLastLayerDependencies } from 'src/engine/core-modules/serverless/drivers/utils/get-last-layer-dependencies'; -import { LAST_LAYER_VERSION } from 'src/engine/core-modules/serverless/drivers/layers/last-layer-version'; -import { CreateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/create-serverless-function.input'; -import { getBaseTypescriptProjectFiles } from 'src/engine/core-modules/serverless/drivers/utils/get-base-typescript-project-files'; -import { ENV_FILE_NAME } from 'src/engine/core-modules/serverless/drivers/constants/env-file-name'; @Injectable() export class ServerlessFunctionService extends TypeOrmQueryService { @@ -81,9 +81,11 @@ export class ServerlessFunctionService extends TypeOrmQueryService