/** * Команды для системы миграции конфигураций */ import { MigrationCommand, MigrationStepResult, ConfigVersion, VersionDetector, ConfigValidator } from './types' // Команды для системы миграции конфигураций /** Команда детекции версии конфигурации */ export class DetectVersionCommand implements MigrationCommand { name = 'DetectVersion' description = 'Определяет версию конфигурации' execute(config: any): MigrationStepResult { try { const detector = new DefaultVersionDetector() const version = detector.detect(config) if (!version) { return { success: false, errors: ['Не удалось определить версию конфигурации'], warnings: [], modified: false } } return { success: true, data: { version, config }, errors: [], warnings: [], modified: false } } catch (error) { return { success: false, errors: [`Ошибка при детекции версии: ${error}`], warnings: [], modified: false } } } } /** Команда валидации конфигурации */ export class ValidateConfigCommand implements MigrationCommand { name = 'ValidateConfig' description = 'Валидирует конфигурацию для указанной версии' execute(config: any, options: { version: ConfigVersion }): MigrationStepResult { try { const validator = new DefaultConfigValidator() const result = validator.validate(config, options.version) return { success: result.isValid, data: { validationResult: result, config }, errors: result.errors, warnings: result.warnings, modified: false } } catch (error) { return { success: false, errors: [`Ошибка при валидации: ${error}`], warnings: [], modified: false } } } } /** Команда очистки конфигурации от неизвестных полей */ export class CleanConfigCommand implements MigrationCommand { name = 'CleanConfig' description = 'Очищает конфигурацию от неизвестных полей для указанной версии' execute(config: any, options: { version: ConfigVersion; preserveUnknown?: boolean }): MigrationStepResult { try { if (options.preserveUnknown) { return { success: true, data: config, errors: [], warnings: ['Очистка пропущена - preserveUnknown: true'], modified: false } } // Здесь можно реализовать логику очистки на основе схем const cleanedConfig = this.cleanConfigForVersion(config, options.version) return { success: true, data: cleanedConfig, errors: [], warnings: ['Конфигурация очищена от неизвестных полей'], modified: true } } catch (error) { return { success: false, errors: [`Ошибка при очистке конфигурации: ${error}`], warnings: [], modified: false } } } private cleanConfigForVersion(config: any, _version: ConfigVersion): any { // Базовая реализация - возвращаем как есть // В реальном проекте здесь была бы логика очистки на основе схем return { ...config } } } /** Команда создания резервной копии */ export class BackupConfigCommand implements MigrationCommand { name = 'BackupConfig' description = 'Создает резервную копию конфигурации' execute(config: any, options?: { timestamp?: boolean }): MigrationStepResult { try { const timestamp = options?.timestamp ? Date.now() : undefined const backup = { original: JSON.parse(JSON.stringify(config)), timestamp: timestamp || Date.now(), version: new DefaultVersionDetector().detect(config) } return { success: true, data: { backup, config }, errors: [], warnings: [], modified: false } } catch (error) { return { success: false, errors: [`Ошибка при создании резервной копии: ${error}`], warnings: [], modified: false } } } } /** Дефолтный детектор версий */ export class DefaultVersionDetector implements VersionDetector { detect(config: any): ConfigVersion | null { try { // Проверяем структуру для определения версии if (!config || !config.settings || !config.sections) { return null } // Explicit schema version hint (helps distinguish 3.1 configs). if ((config as any)?.schema?.version === '3.1') { return '3.1' } // V3 signals: presence of enhanced fields const hasV3Features = [ config.settings.theme !== undefined, config.settings.container !== undefined, config.sections.top.params?.border !== undefined, config.sections.top.params?.sideMargin !== undefined, config.sections.top.params?.borderRadius !== undefined, config.sections.inside.dataAction !== undefined, config.sections.bottom.btnVoice !== undefined, config.sections.inside.welcomeMessage?.borderColor !== undefined, ] // If there are at least 3 V3 signals, treat it as V3 family (3.0/3.1) const v3FeaturesCount = hasV3Features.filter(Boolean).length if (v3FeaturesCount >= 3) { // Default to 3.0 when schema version is not explicitly set to 3.1. return (config as any)?.schema?.version === '3.1' ? '3.1' : '3.0' } // V2 signals: presence of newer fields const hasV2Features = [ config.settings.loader !== undefined, config.settings.buttonStyle !== undefined, config.settings.buttonType !== undefined, config.sections.top.params?.chipStyle !== undefined, config.sections.inside.messageUser?.bgType !== undefined, config.sections.inside.aprooveButton !== undefined, config.sections.bottom.inputSend?.borderStyle !== undefined, config.sections.bottom.warningAlert !== undefined, config.sections.bottom.disclaimer !== undefined ] // If there are at least 3 V2 signals, treat it as V2 const v2FeaturesCount = hasV2Features.filter(Boolean).length if (v2FeaturesCount >= 3) { return '2.0' } // Базовая проверка на V1 const hasV1Structure = [ config.settings.widgetTitle !== undefined, config.settings.bgChat !== undefined, config.sections.top !== undefined, config.sections.inside !== undefined, config.sections.bottom !== undefined ].every(Boolean) if (hasV1Structure) { return '1.0' } return null } catch (error) { console.error('Ошибка при детекции версии:', error) return null } } } /** Дефолтный валидатор конфигураций */ export class DefaultConfigValidator implements ConfigValidator { validate(config: any, version: ConfigVersion): { isValid: boolean; errors: string[]; warnings: string[] } { const errors: string[] = [] const warnings: string[] = [] try { if (!config) { errors.push('Конфигурация не может быть пустой') return { isValid: false, errors, warnings } } // Базовая валидация структуры if (!config.settings) { errors.push('Отсутствует секция settings') } if (!config.sections) { errors.push('Отсутствует секция sections') } if (config.sections) { if (!config.sections.top) errors.push('Отсутствует секция sections.top') if (!config.sections.inside) errors.push('Отсутствует секция sections.inside') if (!config.sections.bottom) errors.push('Отсутствует секция sections.bottom') } // Версионная валидация switch (version) { case '1.0': this.validateV1(config, errors, warnings) break case '2.0': this.validateV2(config, errors, warnings) break case '3.0': case '3.1': this.validateV3(config, errors, warnings) break default: warnings.push(`Валидация для версии ${version} не реализована`) } return { isValid: errors.length === 0, errors, warnings } } catch (error) { errors.push(`Ошибка при валидации: ${error}`) return { isValid: false, errors, warnings } } } private validateV1(config: any, errors: string[], _warnings: string[]): void { // Проверяем обязательные поля V1 const requiredV1Fields = [ 'settings.widgetTitle', 'settings.welcomeMessage', 'settings.bgChat' ] for (const field of requiredV1Fields) { if (!this.getNestedValue(config, field)) { errors.push(`Отсутствует обязательное поле: ${field}`) } } } private validateV2(config: any, errors: string[], warnings: string[]): void { // Сначала валидируем как V1 this.validateV1(config, errors, warnings) // Дополнительные проверки для V2 const expectedV2Fields = [ 'settings.loader', 'settings.buttonStyle', 'sections.inside.aprooveButton', 'sections.bottom.warningAlert' ] for (const field of expectedV2Fields) { if (!this.getNestedValue(config, field)) { warnings.push(`Рекомендуется добавить поле V2: ${field}`) } } } private validateV3(config: any, errors: string[], warnings: string[]): void { // Сначала валидируем как V2 this.validateV2(config, errors, warnings) // Дополнительные проверки для V3 const expectedV3Fields = [ 'settings.theme', 'settings.container', 'sections.top.params.border', 'sections.top.params.sideMargin', 'sections.top.params.borderRadius', 'sections.inside.dataAction', 'sections.bottom.btnVoice' ] for (const field of expectedV3Fields) { if (!this.getNestedValue(config, field)) { warnings.push(`Рекомендуется добавить поле V3: ${field}`) } } // Проверяем структуру container если он есть if (config.settings?.container) { if (!config.settings.container.innerBorder) { warnings.push('settings.container.innerBorder рекомендуется добавить') } if (!config.settings.container.outerBorder) { warnings.push('settings.container.outerBorder рекомендуется добавить') } if (!config.settings.container.gradient) { warnings.push('settings.container.gradient is recommended to add') } } } private getNestedValue(obj: any, path: string): any { return path.split('.').reduce((current, key) => current?.[key], obj) } } /** Фабрика команд */ export class CommandFactory { private static commands = new Map MigrationCommand>([ ['DetectVersion', () => new DetectVersionCommand()], ['ValidateConfig', () => new ValidateConfigCommand()], ['CleanConfig', () => new CleanConfigCommand()], ['BackupConfig', () => new BackupConfigCommand()] ]) static createCommand(name: string): MigrationCommand | null { const factory = this.commands.get(name) return factory ? factory() : null } static getAllCommands(): string[] { return Array.from(this.commands.keys()) } static registerCommand(name: string, factory: () => MigrationCommand): void { this.commands.set(name, factory) } }