diff --git a/packages/twenty-server/src/engine/core-modules/environment/decorators/is-twenty-semver.decorator.ts b/packages/twenty-server/src/engine/core-modules/environment/decorators/is-twenty-semver.decorator.ts new file mode 100644 index 000000000..e40746675 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/environment/decorators/is-twenty-semver.decorator.ts @@ -0,0 +1,33 @@ +import { + registerDecorator, + ValidationArguments, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; +import semver from 'semver'; + +@ValidatorConstraint({ async: false }) +export class IsTwentySemVerValidator implements ValidatorConstraintInterface { + validate(version: string) { + const parsed = semver.parse(version); + + return parsed !== null; + } + + defaultMessage(args: ValidationArguments) { + return `${args.property} must be a valid semantic version (e.g., 1.0.0)`; + } +} + +export const IsTwentySemVer = + (validationOptions?: ValidationOptions) => + (object: object, propertyName: string) => { + registerDecorator({ + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [], + validator: IsTwentySemVerValidator, + }); + }; diff --git a/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts index d07bb35fd..2a814f30a 100644 --- a/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts @@ -7,12 +7,13 @@ import { IsEnum, IsNumber, IsOptional, - IsSemVer, IsString, IsUrl, ValidateIf, + ValidationError, validateSync, } from 'class-validator'; +import { isDefined } from 'twenty-shared'; import { EmailDriver } from 'src/engine/core-modules/email/interfaces/email.interface'; import { AwsRegion } from 'src/engine/core-modules/environment/interfaces/aws-region.interface'; @@ -30,12 +31,12 @@ import { IsAWSRegion } from 'src/engine/core-modules/environment/decorators/is-a import { IsDuration } from 'src/engine/core-modules/environment/decorators/is-duration.decorator'; import { IsOptionalOrEmptyString } from 'src/engine/core-modules/environment/decorators/is-optional-or-empty-string.decorator'; import { IsStrictlyLowerThan } from 'src/engine/core-modules/environment/decorators/is-strictly-lower-than.decorator'; +import { IsTwentySemVer } from 'src/engine/core-modules/environment/decorators/is-twenty-semver.decorator'; import { EnvironmentVariablesGroup } from 'src/engine/core-modules/environment/enums/environment-variables-group.enum'; import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces'; import { StorageDriverType } from 'src/engine/core-modules/file-storage/interfaces'; import { LoggerDriverType } from 'src/engine/core-modules/logger/interfaces'; import { ServerlessDriverType } from 'src/engine/core-modules/serverless/serverless.interface'; -import { assert } from 'src/utils/assert'; export class EnvironmentVariables { @EnvironmentVariablesMetadata({ @@ -986,7 +987,7 @@ export class EnvironmentVariables { description: 'Twenty server version', }) @IsOptionalOrEmptyString() - @IsSemVer() + @IsTwentySemVer() APP_VERSION?: string; } @@ -995,21 +996,32 @@ export const validate = ( ): EnvironmentVariables => { const validatedConfig = plainToClass(EnvironmentVariables, config); - const errors = validateSync(validatedConfig, { strictGroups: true }); + const validationErrors = validateSync(validatedConfig, { + strictGroups: true, + }); - const warnings = validateSync(validatedConfig, { groups: ['warning'] }); - - if (warnings.length > 0) { - warnings.forEach((warning) => { - if (warning.constraints && warning.property) { - Object.values(warning.constraints).forEach((message) => { - Logger.warn(message); - }); + const validationWarnings = validateSync(validatedConfig, { + groups: ['warning'], + }); + const logValidatonErrors = ( + errorCollection: ValidationError[], + type: 'error' | 'warn', + ) => + errorCollection.forEach((error) => { + if (!isDefined(error.constraints) || !isDefined(error.property)) { + return; } + Logger[type](Object.values(error.constraints).join('\n')); }); + + if (validationWarnings.length > 0) { + logValidatonErrors(validationWarnings, 'warn'); } - assert(!errors.length, errors.toString()); + if (validationErrors.length > 0) { + logValidatonErrors(validationErrors, 'error'); + throw new Error('Environment variables validation failed'); + } return validatedConfig; };