Validate range version app dev sync (#22625)

# Introduction
Also now validating the workspace version when running a sync manifest

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22625?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Paul Rastoin
2026-07-08 18:37:42 +02:00
committed by GitHub
parent b5a73ad86a
commit 163c96c2e5
16 changed files with 253 additions and 49 deletions
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
import { ApplicationManifestModule } from 'src/engine/core-modules/application/application-manifest/application-manifest.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module';
import { ApplicationDevelopmentResolver } from 'src/engine/core-modules/application/application-development/application-development.resolver';
import { CacheLockModule } from 'src/engine/core-modules/cache-lock/cache-lock.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
@@ -16,6 +17,7 @@ import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/wor
imports: [
ApplicationModule,
ApplicationManifestModule,
ApplicationPackageModule,
ApplicationRegistrationModule,
CacheLockModule,
FeatureFlagModule,
@@ -22,6 +22,8 @@ import { WorkspaceMigrationDTO } from 'src/engine/core-modules/application/appli
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
import { resolveManifestAssetUrls } from 'src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util';
import { ApplicationVersionValidationService } from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE } from 'src/engine/core-modules/application/application-package/constants/version-reason-to-exception-code.constant';
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
@@ -64,6 +66,7 @@ export class ApplicationDevelopmentResolver {
private readonly applicationSyncService: ApplicationSyncService,
private readonly applicationRegistrationService: ApplicationRegistrationService,
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
private readonly applicationVersionValidationService: ApplicationVersionValidationService,
private readonly fileStorageService: FileStorageService,
private readonly sdkClientGenerationService: SdkClientGenerationService,
private readonly twentyConfigService: TwentyConfigService,
@@ -118,6 +121,22 @@ export class ApplicationDevelopmentResolver {
workspaceId,
);
const versionValidation =
await this.applicationVersionValidationService.validateWorkspaceCompatibility(
{
requiredServerVersion:
manifest.application.requiredServerVersionRange ?? undefined,
workspaceId,
},
);
if (!versionValidation.compatible) {
throw new ApplicationException(
versionValidation.message,
VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE[versionValidation.reason],
);
}
if (dryRun === true) {
const { workspaceMigration } =
await this.applicationSyncService.synchronizeFromManifest({
@@ -21,10 +21,8 @@ import { ManifestAssetUrlResolverService } from 'src/engine/core-modules/applica
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationPackageFetcherService } from 'src/engine/core-modules/application/application-package/application-package-fetcher.service';
import {
ApplicationVersionValidationService,
type VersionValidationFailureReason,
} from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { ApplicationVersionValidationService } from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE } from 'src/engine/core-modules/application/application-package/constants/version-reason-to-exception-code.constant';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/services/file-storage.service';
@@ -43,20 +41,6 @@ import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-func
export class ApplicationInstallService {
private readonly logger = new Logger(ApplicationInstallService.name);
private static readonly VERSION_REASON_TO_EXCEPTION_CODE: Record<
VersionValidationFailureReason,
ApplicationExceptionCode
> = {
INVALID_REQUIRED_VERSION:
ApplicationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT,
INVALID_SERVER_VERSION: ApplicationExceptionCode.INVALID_SERVER_VERSION,
INVALID_WORKSPACE_VERSION:
ApplicationExceptionCode.INVALID_WORKSPACE_VERSION,
INSTANCE_INCOMPATIBLE: ApplicationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
WORKSPACE_INCOMPATIBLE:
ApplicationExceptionCode.WORKSPACE_VERSION_INCOMPATIBLE,
};
constructor(
@InjectRepository(ApplicationRegistrationEntity)
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
@@ -157,9 +141,7 @@ export class ApplicationInstallService {
throw new ApplicationException(
versionValidation.message,
ApplicationInstallService.VERSION_REASON_TO_EXCEPTION_CODE[
versionValidation.reason
],
VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE[versionValidation.reason],
);
}
@@ -0,0 +1,34 @@
import { type VersionValidationFailureReason } from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { ApplicationRegistrationExceptionCode } from 'src/engine/core-modules/application/application-registration/application-registration.exception';
import { ApplicationExceptionCode } from 'src/engine/core-modules/application/application.exception';
export const VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE: Record<
VersionValidationFailureReason,
ApplicationExceptionCode
> = {
INVALID_REQUIRED_VERSION:
ApplicationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT,
INVALID_SERVER_VERSION: ApplicationExceptionCode.INVALID_SERVER_VERSION,
INVALID_WORKSPACE_VERSION: ApplicationExceptionCode.INVALID_WORKSPACE_VERSION,
INSTANCE_INCOMPATIBLE: ApplicationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
WORKSPACE_INCOMPATIBLE:
ApplicationExceptionCode.WORKSPACE_VERSION_INCOMPATIBLE,
};
// The registration flow (tarball upload) has no per-workspace context, so
// workspace-scoped reasons collapse onto the server-scoped registration codes.
export const VERSION_REASON_TO_APPLICATION_REGISTRATION_EXCEPTION_CODE: Record<
VersionValidationFailureReason,
ApplicationRegistrationExceptionCode
> = {
INVALID_REQUIRED_VERSION:
ApplicationRegistrationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT,
INVALID_SERVER_VERSION:
ApplicationRegistrationExceptionCode.INVALID_SERVER_VERSION,
INVALID_WORKSPACE_VERSION:
ApplicationRegistrationExceptionCode.INVALID_SERVER_VERSION,
INSTANCE_INCOMPATIBLE:
ApplicationRegistrationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
WORKSPACE_INCOMPATIBLE:
ApplicationRegistrationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
};
@@ -12,10 +12,8 @@ import { Repository } from 'typeorm';
import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { v4 } from 'uuid';
import {
ApplicationVersionValidationService,
type VersionValidationFailureReason,
} from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { ApplicationVersionValidationService } from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { VERSION_REASON_TO_APPLICATION_REGISTRATION_EXCEPTION_CODE } from 'src/engine/core-modules/application/application-package/constants/version-reason-to-exception-code.constant';
import { extractTarballSecurely } from 'src/engine/core-modules/application/application-package/utils/extract-tarball-securely.util';
import { readJsonFile } from 'src/engine/core-modules/application/application-package/utils/read-json-file.util';
import { resolvePackageContentDir } from 'src/engine/core-modules/application/application-package/utils/tarball-utils';
@@ -35,22 +33,6 @@ import type { ApplicationManifest } from 'twenty-shared/application';
export class ApplicationTarballService {
private readonly logger = new Logger(ApplicationTarballService.name);
private static readonly VERSION_REASON_TO_EXCEPTION_CODE: Record<
VersionValidationFailureReason,
ApplicationRegistrationExceptionCode
> = {
INVALID_REQUIRED_VERSION:
ApplicationRegistrationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT,
INVALID_SERVER_VERSION:
ApplicationRegistrationExceptionCode.INVALID_SERVER_VERSION,
INVALID_WORKSPACE_VERSION:
ApplicationRegistrationExceptionCode.INVALID_SERVER_VERSION,
INSTANCE_INCOMPATIBLE:
ApplicationRegistrationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
WORKSPACE_INCOMPATIBLE:
ApplicationRegistrationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
};
constructor(
@InjectRepository(ApplicationRegistrationEntity)
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
@@ -107,7 +89,7 @@ export class ApplicationTarballService {
if (!versionValidation.compatible) {
throw new ApplicationRegistrationException(
versionValidation.message,
ApplicationTarballService.VERSION_REASON_TO_EXCEPTION_CODE[
VERSION_REASON_TO_APPLICATION_REGISTRATION_EXCEPTION_CODE[
versionValidation.reason
],
);