[Fix] Class validator native isSemver does not handle v-prefix (#10907)
# Introduction Under the hood class-validator isSemver uses https://github.com/validatorjs/validator.js/blob/master/src/lib/isSemVer.js which does not cover all semVer use cases ## Even tho Had a discussion with @charles was about to store in db ws version as `vx.y.z`. We felt like we wanted it to be stored as `x.y.z`, in my opinion `APP_VERSION` should reflect the tag used to be build the instance and not be updated But we could extract only `x.y.z` from it at runtime Also handling the `v` extraction in CD is IMO not the most reliable ## Env var logging refactor Now not stopping on first error log ```ts Successfully compiled: 2128 files with swc (185.34ms) Watching for file changes. [Nest] 52686 - 03/14/2025, 6:28:33 PM ERROR PG_DATABASE_URL should not be null or undefined PG_DATABASE_URL must be a URL address [Nest] 52686 - 03/14/2025, 6:28:33 PM ERROR APP_VERSION must be a valid semantic version (e.g., 1.0.0) /Users/paulrastoin/ws/twenty/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts:1019 throw new Error("Environment variables validation failed") ^ Error: Environment variables validation failed at Object.validate (/Users/paulrastoin/ws/twenty/packages/twenty-server/src/engine/core-modules/environment/environment-variables.ts:1019:11) at Function.forRoot (/Users/paulrastoin/ws/twenty/node_modules/@nestjs/config/dist/config.module.js:67:45) at Object.<anonymous> (/Users/paulrastoin/ws/twenty/packages/twenty-server/src/engine/core-modules/environment/environment.module.ts:11:18) at Module._compile (node:internal/modules/cjs/loader:1256:14) at Object.Module._extensions..js (node:internal/modules/cjs/loader:1310:10) at Module.load (node:internal/modules/cjs/loader:1119:32) at Function.Module._load (node:internal/modules/cjs/loader:960:12) at Module.require (node:internal/modules/cjs/loader:1143:19) at require (node:internal/modules/cjs/helpers:121:18) at Object.<anonymous> (/Users/paulrastoin/ws/twenty/packages/twenty-server/dist/src/database/typeorm/typeorm.module.js:14:28) ```
This commit is contained in:
@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -7,12 +7,13 @@ import {
|
|||||||
IsEnum,
|
IsEnum,
|
||||||
IsNumber,
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsSemVer,
|
|
||||||
IsString,
|
IsString,
|
||||||
IsUrl,
|
IsUrl,
|
||||||
ValidateIf,
|
ValidateIf,
|
||||||
|
ValidationError,
|
||||||
validateSync,
|
validateSync,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
import { EmailDriver } from 'src/engine/core-modules/email/interfaces/email.interface';
|
import { EmailDriver } from 'src/engine/core-modules/email/interfaces/email.interface';
|
||||||
import { AwsRegion } from 'src/engine/core-modules/environment/interfaces/aws-region.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 { 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 { 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 { 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 { EnvironmentVariablesGroup } from 'src/engine/core-modules/environment/enums/environment-variables-group.enum';
|
||||||
import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces';
|
import { ExceptionHandlerDriver } from 'src/engine/core-modules/exception-handler/interfaces';
|
||||||
import { StorageDriverType } from 'src/engine/core-modules/file-storage/interfaces';
|
import { StorageDriverType } from 'src/engine/core-modules/file-storage/interfaces';
|
||||||
import { LoggerDriverType } from 'src/engine/core-modules/logger/interfaces';
|
import { LoggerDriverType } from 'src/engine/core-modules/logger/interfaces';
|
||||||
import { ServerlessDriverType } from 'src/engine/core-modules/serverless/serverless.interface';
|
import { ServerlessDriverType } from 'src/engine/core-modules/serverless/serverless.interface';
|
||||||
import { assert } from 'src/utils/assert';
|
|
||||||
|
|
||||||
export class EnvironmentVariables {
|
export class EnvironmentVariables {
|
||||||
@EnvironmentVariablesMetadata({
|
@EnvironmentVariablesMetadata({
|
||||||
@ -986,7 +987,7 @@ export class EnvironmentVariables {
|
|||||||
description: 'Twenty server version',
|
description: 'Twenty server version',
|
||||||
})
|
})
|
||||||
@IsOptionalOrEmptyString()
|
@IsOptionalOrEmptyString()
|
||||||
@IsSemVer()
|
@IsTwentySemVer()
|
||||||
APP_VERSION?: string;
|
APP_VERSION?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -995,21 +996,32 @@ export const validate = (
|
|||||||
): EnvironmentVariables => {
|
): EnvironmentVariables => {
|
||||||
const validatedConfig = plainToClass(EnvironmentVariables, config);
|
const validatedConfig = plainToClass(EnvironmentVariables, config);
|
||||||
|
|
||||||
const errors = validateSync(validatedConfig, { strictGroups: true });
|
const validationErrors = validateSync(validatedConfig, {
|
||||||
|
strictGroups: true,
|
||||||
|
});
|
||||||
|
|
||||||
const warnings = validateSync(validatedConfig, { groups: ['warning'] });
|
const validationWarnings = validateSync(validatedConfig, {
|
||||||
|
groups: ['warning'],
|
||||||
if (warnings.length > 0) {
|
});
|
||||||
warnings.forEach((warning) => {
|
const logValidatonErrors = (
|
||||||
if (warning.constraints && warning.property) {
|
errorCollection: ValidationError[],
|
||||||
Object.values(warning.constraints).forEach((message) => {
|
type: 'error' | 'warn',
|
||||||
Logger.warn(message);
|
) =>
|
||||||
});
|
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;
|
return validatedConfig;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user