import { InjectRepository } from '@nestjs/typeorm'; import chalk from 'chalk'; import { SemVer } from 'semver'; import { isDefined } from 'twenty-shared'; import { Repository } from 'typeorm'; import { ActiveOrSuspendedWorkspacesMigrationCommandRunner, RunOnWorkspaceArgs, } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command'; import { CompareVersionMajorAndMinorReturnType, compareVersionMajorAndMinor, } from 'src/utils/version/compare-version-minor-and-major'; type ValidateWorkspaceVersionEqualsWorkspaceFromVersionOrThrowArgs = { workspaceId: string; appVersion: string | undefined; }; export abstract class UpgradeCommandRunner extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { abstract readonly fromWorkspaceVersion: SemVer; public readonly VALIDATE_WORKSPACE_VERSION_FEATURE_FLAG?: true; constructor( @InjectRepository(Workspace, 'core') protected readonly workspaceRepository: Repository, protected readonly environmentService: EnvironmentService, protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, protected readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand, ) { super(workspaceRepository, twentyORMGlobalManager); } override async runOnWorkspace(args: RunOnWorkspaceArgs): Promise { const { workspaceId, index, total, options } = args; const appVersion = this.environmentService.get('APP_VERSION'); this.logger.log( chalk.blue( `${options.dryRun ? '(dry run)' : ''} Upgrading workspace ${workspaceId} ${index + 1}/${total}`, ), ); const workspaceVersionCompareResult = await this.retrieveWorkspaceVersionAndCompareToWorkspaceFromVersion({ appVersion, workspaceId, }); switch (workspaceVersionCompareResult) { case 'lower': { throw new Error( `WORKSPACE_VERSION_MISSMATCH Upgrade for workspace ${workspaceId} failed as its version is beneath fromWorkspaceVersion=${this.fromWorkspaceVersion.version}`, ); } case 'equal': { await this.runBeforeSyncMetadata(args); await this.syncWorkspaceMetadataCommand.runOnWorkspace(args); await this.runAfterSyncMetadata(args); await this.workspaceRepository.update( { id: workspaceId }, { version: appVersion }, ); this.logger.log( chalk.blue(`Upgrade for workspace ${workspaceId} completed.`), ); return; } case 'higher': { this.logger.log( chalk.blue( `Upgrade for workspace ${workspaceId} ignored as is already at a higher version.`, ), ); return; } default: { throw new Error( `Should never occur, encountered unexpected value from retrieveWorkspaceVersionAndCompareToWorkspaceFromVersion ${workspaceVersionCompareResult}`, ); } } } private async retrieveWorkspaceVersionAndCompareToWorkspaceFromVersion({ appVersion, workspaceId, }: ValidateWorkspaceVersionEqualsWorkspaceFromVersionOrThrowArgs): Promise { if (!isDefined(appVersion)) { throw new Error( 'Cannot run upgrade command when APP_VERSION is not defined', ); } // TODO remove after first release has been done using workspace_version if (!isDefined(this.VALIDATE_WORKSPACE_VERSION_FEATURE_FLAG)) { this.logger.warn( 'VALIDATE_WORKSPACE_VERSION_FEATURE_FLAG set to true ignoring workspace versions validation step', ); return 'equal'; } const workspace = await this.workspaceRepository.findOneByOrFail({ id: workspaceId, }); const currentWorkspaceVersion = workspace.version; if (!isDefined(currentWorkspaceVersion)) { throw new Error(`WORKSPACE_VERSION_NOT_DEFINED to=${appVersion}`); } return compareVersionMajorAndMinor( currentWorkspaceVersion, this.fromWorkspaceVersion.version, ); } protected abstract runBeforeSyncMetadata( args: RunOnWorkspaceArgs, ): Promise; protected abstract runAfterSyncMetadata( args: RunOnWorkspaceArgs, ): Promise; }