@yartsun/chat-widget-types 1.0.2 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -115
- package/dist/config.types.d.ts +111 -44
- package/dist/config.types.d.ts.map +1 -1
- package/dist/config.types.js +2 -67
- package/dist/config.types.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/migration/commands.d.ts +59 -0
- package/dist/migration/commands.d.ts.map +1 -0
- package/dist/migration/commands.js +286 -0
- package/dist/migration/commands.js.map +1 -0
- package/dist/migration/examples.d.ts +198 -0
- package/dist/migration/examples.d.ts.map +1 -0
- package/dist/migration/examples.js +439 -0
- package/dist/migration/examples.js.map +1 -0
- package/dist/migration/facade.d.ts +85 -0
- package/dist/migration/facade.d.ts.map +1 -0
- package/dist/migration/facade.js +168 -0
- package/dist/migration/facade.js.map +1 -0
- package/dist/migration/migrator.d.ts +49 -0
- package/dist/migration/migrator.d.ts.map +1 -0
- package/dist/migration/migrator.js +245 -0
- package/dist/migration/migrator.js.map +1 -0
- package/dist/migration/strategies.d.ts +85 -0
- package/dist/migration/strategies.d.ts.map +1 -0
- package/dist/migration/strategies.js +217 -0
- package/dist/migration/strategies.js.map +1 -0
- package/dist/migration/types.d.ts +196 -0
- package/dist/migration/types.d.ts.map +1 -0
- package/dist/migration/types.js +5 -0
- package/dist/migration/types.js.map +1 -0
- package/dist/utils.d.ts +1 -11
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -127
- package/dist/utils.js.map +1 -1
- package/package.json +13 -4
- package/src/config.types.ts +132 -118
- package/src/index.ts +26 -0
- package/src/migration/commands.ts +314 -0
- package/src/migration/examples.ts +471 -0
- package/src/migration/facade.ts +196 -0
- package/src/migration/migrator.ts +361 -0
- package/src/migration/strategies.ts +249 -0
- package/src/migration/types.ts +182 -0
- package/src/utils.ts +3 -143
package/src/config.types.ts
CHANGED
|
@@ -1,63 +1,165 @@
|
|
|
1
|
+
import config from '../examples/configV2.json'
|
|
1
2
|
/**
|
|
2
|
-
* Типы для конфигурации виджета чата
|
|
3
|
+
* Типы для конфигурации виджета чата (V2)
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
// Базовые типы
|
|
6
7
|
export type Size = 'sm' | 'md' | 'lg'
|
|
7
8
|
export type BorderRadius = '0' | 'sm' | 'md' | 'lg'
|
|
8
9
|
export type BtnType = 'icon' | 'text' | 'both'
|
|
9
|
-
export type LaunchView = 'closed' | 'compact'
|
|
10
|
+
export type LaunchView = 'closed' | 'compact'
|
|
11
|
+
export type ChipStyle = 'filled' | 'outlined' | 'invisible'
|
|
12
|
+
export type Loader = 'dots' | 'dots-pulse' | 'circle-pulse' | 'circle-pulse-1'
|
|
13
|
+
export type BgType = 'plain' | 'bubble'
|
|
14
|
+
export type InputStyle = 'inside' | 'stacked'
|
|
15
|
+
export type ButtonStyle = 'filled' | 'outlined' | 'invisible'
|
|
16
|
+
export type WarningStyle = 'gradient' | 'filled' | 'faded'
|
|
17
|
+
|
|
18
|
+
// Схема конфигурации
|
|
19
|
+
export interface ConfigSchema {
|
|
20
|
+
version: string
|
|
21
|
+
required: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Настройки виджета
|
|
25
|
+
export interface WidgetSettings {
|
|
26
|
+
bgChat: string
|
|
27
|
+
gapMessageLine: number
|
|
28
|
+
fontFamily: string
|
|
29
|
+
borderRadius: BorderRadius
|
|
30
|
+
launchView: LaunchView
|
|
31
|
+
letterSpacing: number
|
|
32
|
+
logo: string
|
|
33
|
+
fontWeight: number
|
|
34
|
+
loader: Loader
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Тексты виджета
|
|
38
|
+
export interface WidgetTexts {
|
|
39
|
+
welcomeMessage: string
|
|
40
|
+
widgetTitle: string
|
|
41
|
+
launchIssueTitle: string
|
|
42
|
+
launchIssueText: string
|
|
43
|
+
issueText: string
|
|
44
|
+
reconnectText: string
|
|
45
|
+
inputPlaceholder: string
|
|
46
|
+
disableInputText: string
|
|
47
|
+
disclaimer: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Базовые типы для элементов
|
|
51
|
+
export interface BorderStyle {
|
|
52
|
+
borderColor: string
|
|
53
|
+
borderWidth?: number
|
|
54
|
+
}
|
|
55
|
+
|
|
10
56
|
export interface ColorItems {
|
|
11
57
|
color?: string
|
|
12
58
|
bgColor?: string
|
|
13
59
|
}
|
|
14
|
-
|
|
60
|
+
|
|
15
61
|
export interface UIElement {
|
|
16
62
|
color: string
|
|
17
63
|
bgColor?: string
|
|
18
64
|
type?: BtnType
|
|
65
|
+
bgType?: BgType
|
|
19
66
|
}
|
|
20
67
|
|
|
21
|
-
export interface
|
|
22
|
-
|
|
68
|
+
export interface InputSendElement extends UIElement {
|
|
69
|
+
inputStyle: InputStyle
|
|
70
|
+
borderStyle: BorderStyle
|
|
23
71
|
}
|
|
24
72
|
|
|
25
|
-
// Типы для
|
|
26
|
-
export interface
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
73
|
+
// Типы для Active Snippet
|
|
74
|
+
export interface ActiveSnippetParams {
|
|
75
|
+
buttonType: BtnType
|
|
76
|
+
buttonStyle: ButtonStyle
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ActiveSnippetElement {
|
|
80
|
+
params: ActiveSnippetParams
|
|
81
|
+
color: string
|
|
82
|
+
bgColor: string
|
|
83
|
+
headerChip: ColorItems
|
|
84
|
+
propertyColor: string
|
|
85
|
+
valueColor: string
|
|
86
|
+
yesButton: ColorItems
|
|
87
|
+
noButton: ColorItems
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Типы для предупреждений
|
|
91
|
+
export interface WarningParams {
|
|
92
|
+
warningStyle: WarningStyle
|
|
93
|
+
showIcon: boolean
|
|
94
|
+
icon: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface WarningElement {
|
|
98
|
+
params: WarningParams
|
|
99
|
+
iconColor: string
|
|
100
|
+
bgColor: string
|
|
101
|
+
headlineColor?: string
|
|
102
|
+
color: string
|
|
103
|
+
resetButton?: {
|
|
104
|
+
color: string
|
|
105
|
+
bgColor: string
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface WarningsSection {
|
|
110
|
+
launchIssue: WarningElement
|
|
111
|
+
connectionIssue: WarningElement
|
|
112
|
+
reconnectIssue: WarningElement
|
|
113
|
+
disableInputIssue: {
|
|
114
|
+
iconColor: string
|
|
115
|
+
bgColor: string
|
|
116
|
+
color: string
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Типы для лоадера
|
|
121
|
+
export interface LoaderSection {
|
|
122
|
+
completeStep: string
|
|
123
|
+
activeStep: string
|
|
37
124
|
}
|
|
38
125
|
|
|
39
126
|
// Типы для секций
|
|
40
127
|
export interface TopSection {
|
|
41
|
-
params:
|
|
42
|
-
|
|
43
|
-
|
|
128
|
+
params: {
|
|
129
|
+
size: Size
|
|
130
|
+
chipStyle: ChipStyle
|
|
131
|
+
chipType: BtnType
|
|
132
|
+
buttonStyle: ButtonStyle
|
|
133
|
+
buttonType: BtnType
|
|
134
|
+
}
|
|
135
|
+
chipWidgetTitle: ColorItems
|
|
136
|
+
buttons: ColorItems
|
|
44
137
|
}
|
|
45
138
|
|
|
46
139
|
export interface InsideSection {
|
|
47
|
-
params:
|
|
140
|
+
params: {
|
|
141
|
+
size: Size
|
|
142
|
+
}
|
|
48
143
|
messageUser: UIElement
|
|
49
144
|
messageBot: UIElement
|
|
145
|
+
activeSnippet: ActiveSnippetElement
|
|
50
146
|
welcomeMessage: UIElement
|
|
51
147
|
}
|
|
52
148
|
|
|
53
149
|
export interface BottomSection {
|
|
54
|
-
params:
|
|
55
|
-
|
|
150
|
+
params: {
|
|
151
|
+
size: Size
|
|
152
|
+
disclaimerShow: boolean
|
|
153
|
+
}
|
|
154
|
+
inputSend: InputSendElement
|
|
155
|
+
warningAlert: UIElement
|
|
56
156
|
btnSend: UIElement
|
|
57
157
|
activeBtn: UIElement
|
|
58
158
|
}
|
|
59
159
|
|
|
60
160
|
export interface WidgetSections {
|
|
161
|
+
warnings: WarningsSection
|
|
162
|
+
loader: LoaderSection
|
|
61
163
|
top: TopSection
|
|
62
164
|
inside: InsideSection
|
|
63
165
|
bottom: BottomSection
|
|
@@ -65,18 +167,22 @@ export interface WidgetSections {
|
|
|
65
167
|
|
|
66
168
|
// Основной тип конфигурации
|
|
67
169
|
export interface WidgetConfig {
|
|
170
|
+
schema: ConfigSchema
|
|
68
171
|
settings: WidgetSettings
|
|
172
|
+
texts: WidgetTexts
|
|
69
173
|
sections: WidgetSections
|
|
70
174
|
}
|
|
71
175
|
|
|
72
176
|
// Типы для частичного обновления конфигурации
|
|
73
177
|
export type PartialWidgetConfig = Partial<WidgetConfig>
|
|
74
178
|
export type PartialWidgetSettings = Partial<WidgetSettings>
|
|
179
|
+
export type PartialWidgetTexts = Partial<WidgetTexts>
|
|
75
180
|
export type PartialWidgetSections = Partial<WidgetSections>
|
|
76
181
|
|
|
77
182
|
// Утилитарные типы
|
|
78
183
|
export type ConfigKey = keyof WidgetConfig
|
|
79
184
|
export type SettingsKey = keyof WidgetSettings
|
|
185
|
+
export type TextsKey = keyof WidgetTexts
|
|
80
186
|
export type SectionsKey = keyof WidgetSections
|
|
81
187
|
|
|
82
188
|
// Типы для валидации
|
|
@@ -85,101 +191,9 @@ export interface ConfigValidationResult {
|
|
|
85
191
|
errors: string[]
|
|
86
192
|
}
|
|
87
193
|
|
|
88
|
-
|
|
89
|
-
export type GeneralSettings = Pick<WidgetSettings, 'widgetTitle' | 'welcomeMessage' | 'launchView' | 'logo'>
|
|
90
|
-
|
|
91
|
-
export type ShapesSettings = Pick<
|
|
92
|
-
WidgetSettings,
|
|
93
|
-
'gapMessageLine' | 'paddingChat' | 'fontFamily' | 'borderRadius'
|
|
94
|
-
> & {
|
|
95
|
-
bottomSize: WidgetConfig['sections']['bottom']['params']['size']
|
|
96
|
-
sendButtonType: BtnType
|
|
97
|
-
headerSize: WidgetConfig['sections']['top']['params']['size']
|
|
98
|
-
messageSize: WidgetConfig['sections']['inside']['params']['size']
|
|
99
|
-
}
|
|
100
|
-
export type EmittedColorElement = {
|
|
101
|
-
bgChat: ColorItems
|
|
102
|
-
chipWidgetTitle: ColorItems
|
|
103
|
-
btnClose: ColorItems
|
|
104
|
-
messageUser: ColorItems
|
|
105
|
-
messageBot: ColorItems
|
|
106
|
-
inputSend: ColorItems
|
|
107
|
-
btnSend: ColorItems
|
|
108
|
-
activeBtn: ColorItems
|
|
109
|
-
welcomeMessage: ColorItems
|
|
110
|
-
}
|
|
111
|
-
export interface FontSettings {
|
|
112
|
-
fontFamily: string
|
|
113
|
-
letterSpacing: number
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Экспорт дефолтной конфигурации (для типизации)
|
|
117
|
-
export const DEFAULT_CONFIG: WidgetConfig = {
|
|
118
|
-
settings: {
|
|
119
|
-
widgetTitle: 'HyperShadow',
|
|
120
|
-
welcomeMessage:
|
|
121
|
-
'🖖 Hi there — I’m your assistant for finding documents, tracking access, and making sense of your files. \n\nHow can I help?',
|
|
122
|
-
bgChat: 'rgba(47, 47, 49, 0.90)',
|
|
123
|
-
gapMessageLine: 12,
|
|
124
|
-
paddingChat: 8,
|
|
125
|
-
fontFamily: 'MacPaw Fixel',
|
|
126
|
-
borderRadius: 'lg',
|
|
127
|
-
launchView: 'closed',
|
|
128
|
-
letterSpacing: 0,
|
|
129
|
-
logo: '',
|
|
130
|
-
},
|
|
131
|
-
sections: {
|
|
132
|
-
top: {
|
|
133
|
-
params: {
|
|
134
|
-
size: 'md',
|
|
135
|
-
},
|
|
136
|
-
chipWidgetTitle: {
|
|
137
|
-
color: '#BEB6E9',
|
|
138
|
-
bgColor: '#5E4AC6',
|
|
139
|
-
},
|
|
140
|
-
btnClose: {
|
|
141
|
-
color: '#BBBBBD',
|
|
142
|
-
bgColor: '#2F2F31',
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
inside: {
|
|
146
|
-
params: {
|
|
147
|
-
size: 'md',
|
|
148
|
-
},
|
|
149
|
-
messageUser: {
|
|
150
|
-
color: '#F9F8F8',
|
|
151
|
-
bgColor: '#F8F8F933',
|
|
152
|
-
},
|
|
153
|
-
messageBot: {
|
|
154
|
-
color: '#fff',
|
|
155
|
-
bgColor: '#5E4AC6',
|
|
156
|
-
},
|
|
157
|
-
welcomeMessage: {
|
|
158
|
-
color: '#fff',
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
bottom: {
|
|
162
|
-
params: {
|
|
163
|
-
size: 'sm',
|
|
164
|
-
},
|
|
165
|
-
inputSend: {
|
|
166
|
-
color: '#EEECEC',
|
|
167
|
-
bgColor: '#1E1E1E',
|
|
168
|
-
},
|
|
169
|
-
btnSend: {
|
|
170
|
-
color: '#1E1E1E',
|
|
171
|
-
bgColor: 'rgba(255, 255, 255, 0.50)',
|
|
172
|
-
type: 'both',
|
|
173
|
-
},
|
|
174
|
-
activeBtn: {
|
|
175
|
-
color: '#fff',
|
|
176
|
-
bgColor: '#F8F8F933',
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
}
|
|
181
|
-
|
|
182
194
|
// Утилитарный тип для глубоких частичных объектов
|
|
183
195
|
export type DeepPartial<T> = {
|
|
184
196
|
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
|
|
185
|
-
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const DEFAULT_CONFIG: WidgetConfig = config as WidgetConfig
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,28 @@
|
|
|
1
1
|
export * from './config.types'
|
|
2
2
|
export * from './utils'
|
|
3
|
+
|
|
4
|
+
// Система миграции конфигураций
|
|
5
|
+
// Явный реэкспорт с алиасом для конфликтующего имени ConfigSchema
|
|
6
|
+
export {
|
|
7
|
+
ConfigVersion,
|
|
8
|
+
MigrationResult,
|
|
9
|
+
MigrationContext,
|
|
10
|
+
MigrationOptions,
|
|
11
|
+
MigrationStrategy,
|
|
12
|
+
MigrationStepResult,
|
|
13
|
+
MigrationCommand,
|
|
14
|
+
VersionDetector,
|
|
15
|
+
ConfigValidator,
|
|
16
|
+
ConfigSchema as MigrationConfigSchema,
|
|
17
|
+
MigrationReport,
|
|
18
|
+
MigrationLogger,
|
|
19
|
+
ConfigV1,
|
|
20
|
+
ConfigV2,
|
|
21
|
+
ConfigByVersion,
|
|
22
|
+
ConfigFactory,
|
|
23
|
+
} from './migration/types'
|
|
24
|
+
export * from './migration/strategies'
|
|
25
|
+
export * from './migration/commands'
|
|
26
|
+
export * from './migration/migrator'
|
|
27
|
+
export * from './migration/facade'
|
|
28
|
+
export * from './migration/examples'
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Команды для системы миграции конфигураций
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MigrationCommand,
|
|
7
|
+
MigrationStepResult,
|
|
8
|
+
ConfigVersion,
|
|
9
|
+
VersionDetector,
|
|
10
|
+
ConfigValidator
|
|
11
|
+
} from './types'
|
|
12
|
+
// Команды для системы миграции конфигураций
|
|
13
|
+
|
|
14
|
+
/** Команда детекции версии конфигурации */
|
|
15
|
+
export class DetectVersionCommand implements MigrationCommand {
|
|
16
|
+
name = 'DetectVersion'
|
|
17
|
+
description = 'Определяет версию конфигурации'
|
|
18
|
+
|
|
19
|
+
execute(config: any): MigrationStepResult {
|
|
20
|
+
try {
|
|
21
|
+
const detector = new DefaultVersionDetector()
|
|
22
|
+
const version = detector.detect(config)
|
|
23
|
+
|
|
24
|
+
if (!version) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
errors: ['Не удалось определить версию конфигурации'],
|
|
28
|
+
warnings: [],
|
|
29
|
+
modified: false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
data: { version, config },
|
|
36
|
+
errors: [],
|
|
37
|
+
warnings: [],
|
|
38
|
+
modified: false
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
errors: [`Ошибка при детекции версии: ${error}`],
|
|
44
|
+
warnings: [],
|
|
45
|
+
modified: false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Команда валидации конфигурации */
|
|
52
|
+
export class ValidateConfigCommand implements MigrationCommand {
|
|
53
|
+
name = 'ValidateConfig'
|
|
54
|
+
description = 'Валидирует конфигурацию для указанной версии'
|
|
55
|
+
|
|
56
|
+
execute(config: any, options: { version: ConfigVersion }): MigrationStepResult {
|
|
57
|
+
try {
|
|
58
|
+
const validator = new DefaultConfigValidator()
|
|
59
|
+
const result = validator.validate(config, options.version)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
success: result.isValid,
|
|
63
|
+
data: { validationResult: result, config },
|
|
64
|
+
errors: result.errors,
|
|
65
|
+
warnings: result.warnings,
|
|
66
|
+
modified: false
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
errors: [`Ошибка при валидации: ${error}`],
|
|
72
|
+
warnings: [],
|
|
73
|
+
modified: false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Команда очистки конфигурации от неизвестных полей */
|
|
80
|
+
export class CleanConfigCommand implements MigrationCommand {
|
|
81
|
+
name = 'CleanConfig'
|
|
82
|
+
description = 'Очищает конфигурацию от неизвестных полей для указанной версии'
|
|
83
|
+
|
|
84
|
+
execute(config: any, options: { version: ConfigVersion; preserveUnknown?: boolean }): MigrationStepResult {
|
|
85
|
+
try {
|
|
86
|
+
if (options.preserveUnknown) {
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
data: config,
|
|
90
|
+
errors: [],
|
|
91
|
+
warnings: ['Очистка пропущена - preserveUnknown: true'],
|
|
92
|
+
modified: false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Здесь можно реализовать логику очистки на основе схем
|
|
97
|
+
const cleanedConfig = this.cleanConfigForVersion(config, options.version)
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
data: cleanedConfig,
|
|
102
|
+
errors: [],
|
|
103
|
+
warnings: ['Конфигурация очищена от неизвестных полей'],
|
|
104
|
+
modified: true
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
errors: [`Ошибка при очистке конфигурации: ${error}`],
|
|
110
|
+
warnings: [],
|
|
111
|
+
modified: false
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private cleanConfigForVersion(config: any, _version: ConfigVersion): any {
|
|
117
|
+
// Базовая реализация - возвращаем как есть
|
|
118
|
+
// В реальном проекте здесь была бы логика очистки на основе схем
|
|
119
|
+
return { ...config }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Команда создания резервной копии */
|
|
124
|
+
export class BackupConfigCommand implements MigrationCommand {
|
|
125
|
+
name = 'BackupConfig'
|
|
126
|
+
description = 'Создает резервную копию конфигурации'
|
|
127
|
+
|
|
128
|
+
execute(config: any, options?: { timestamp?: boolean }): MigrationStepResult {
|
|
129
|
+
try {
|
|
130
|
+
const timestamp = options?.timestamp ? Date.now() : undefined
|
|
131
|
+
const backup = {
|
|
132
|
+
original: JSON.parse(JSON.stringify(config)),
|
|
133
|
+
timestamp: timestamp || Date.now(),
|
|
134
|
+
version: new DefaultVersionDetector().detect(config)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
data: { backup, config },
|
|
140
|
+
errors: [],
|
|
141
|
+
warnings: [],
|
|
142
|
+
modified: false
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return {
|
|
146
|
+
success: false,
|
|
147
|
+
errors: [`Ошибка при создании резервной копии: ${error}`],
|
|
148
|
+
warnings: [],
|
|
149
|
+
modified: false
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Дефолтный детектор версий */
|
|
156
|
+
export class DefaultVersionDetector implements VersionDetector {
|
|
157
|
+
detect(config: any): ConfigVersion | null {
|
|
158
|
+
try {
|
|
159
|
+
// Проверяем структуру для определения версии
|
|
160
|
+
if (!config || !config.settings || !config.sections) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// V2 признаки: наличие новых полей
|
|
165
|
+
const hasV2Features = [
|
|
166
|
+
config.settings.loader !== undefined,
|
|
167
|
+
config.settings.buttonStyle !== undefined,
|
|
168
|
+
config.settings.buttonType !== undefined,
|
|
169
|
+
config.sections.top.params?.chipStyle !== undefined,
|
|
170
|
+
config.sections.inside.messageUser?.bgType !== undefined,
|
|
171
|
+
config.sections.inside.aprooveButton !== undefined,
|
|
172
|
+
config.sections.bottom.inputSend?.borderStyle !== undefined,
|
|
173
|
+
config.sections.bottom.warningAlert !== undefined,
|
|
174
|
+
config.sections.bottom.disclaimer !== undefined
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
// Если есть хотя бы 3 признака V2, считаем это V2
|
|
178
|
+
const v2FeaturesCount = hasV2Features.filter(Boolean).length
|
|
179
|
+
if (v2FeaturesCount >= 3) {
|
|
180
|
+
return '2.0'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Базовая проверка на V1
|
|
184
|
+
const hasV1Structure = [
|
|
185
|
+
config.settings.widgetTitle !== undefined,
|
|
186
|
+
config.settings.bgChat !== undefined,
|
|
187
|
+
config.sections.top !== undefined,
|
|
188
|
+
config.sections.inside !== undefined,
|
|
189
|
+
config.sections.bottom !== undefined
|
|
190
|
+
].every(Boolean)
|
|
191
|
+
|
|
192
|
+
if (hasV1Structure) {
|
|
193
|
+
return '1.0'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return null
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('Ошибка при детекции версии:', error)
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Дефолтный валидатор конфигураций */
|
|
205
|
+
export class DefaultConfigValidator implements ConfigValidator {
|
|
206
|
+
validate(config: any, version: ConfigVersion): { isValid: boolean; errors: string[]; warnings: string[] } {
|
|
207
|
+
const errors: string[] = []
|
|
208
|
+
const warnings: string[] = []
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
if (!config) {
|
|
212
|
+
errors.push('Конфигурация не может быть пустой')
|
|
213
|
+
return { isValid: false, errors, warnings }
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Базовая валидация структуры
|
|
217
|
+
if (!config.settings) {
|
|
218
|
+
errors.push('Отсутствует секция settings')
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!config.sections) {
|
|
222
|
+
errors.push('Отсутствует секция sections')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (config.sections) {
|
|
226
|
+
if (!config.sections.top) errors.push('Отсутствует секция sections.top')
|
|
227
|
+
if (!config.sections.inside) errors.push('Отсутствует секция sections.inside')
|
|
228
|
+
if (!config.sections.bottom) errors.push('Отсутствует секция sections.bottom')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Версионная валидация
|
|
232
|
+
switch (version) {
|
|
233
|
+
case '1.0':
|
|
234
|
+
this.validateV1(config, errors, warnings)
|
|
235
|
+
break
|
|
236
|
+
case '2.0':
|
|
237
|
+
this.validateV2(config, errors, warnings)
|
|
238
|
+
break
|
|
239
|
+
default:
|
|
240
|
+
warnings.push(`Валидация для версии ${version} не реализована`)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
isValid: errors.length === 0,
|
|
245
|
+
errors,
|
|
246
|
+
warnings
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
errors.push(`Ошибка при валидации: ${error}`)
|
|
250
|
+
return { isValid: false, errors, warnings }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private validateV1(config: any, errors: string[], _warnings: string[]): void {
|
|
255
|
+
// Проверяем обязательные поля V1
|
|
256
|
+
const requiredV1Fields = [
|
|
257
|
+
'settings.widgetTitle',
|
|
258
|
+
'settings.welcomeMessage',
|
|
259
|
+
'settings.bgChat'
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
for (const field of requiredV1Fields) {
|
|
263
|
+
if (!this.getNestedValue(config, field)) {
|
|
264
|
+
errors.push(`Отсутствует обязательное поле: ${field}`)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private validateV2(config: any, errors: string[], warnings: string[]): void {
|
|
270
|
+
// Сначала валидируем как V1
|
|
271
|
+
this.validateV1(config, errors, warnings)
|
|
272
|
+
|
|
273
|
+
// Дополнительные проверки для V2
|
|
274
|
+
const expectedV2Fields = [
|
|
275
|
+
'settings.loader',
|
|
276
|
+
'settings.buttonStyle',
|
|
277
|
+
'sections.inside.aprooveButton',
|
|
278
|
+
'sections.bottom.warningAlert'
|
|
279
|
+
]
|
|
280
|
+
|
|
281
|
+
for (const field of expectedV2Fields) {
|
|
282
|
+
if (!this.getNestedValue(config, field)) {
|
|
283
|
+
warnings.push(`Рекомендуется добавить поле V2: ${field}`)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private getNestedValue(obj: any, path: string): any {
|
|
289
|
+
return path.split('.').reduce((current, key) => current?.[key], obj)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Фабрика команд */
|
|
294
|
+
export class CommandFactory {
|
|
295
|
+
private static commands = new Map<string, () => MigrationCommand>([
|
|
296
|
+
['DetectVersion', () => new DetectVersionCommand()],
|
|
297
|
+
['ValidateConfig', () => new ValidateConfigCommand()],
|
|
298
|
+
['CleanConfig', () => new CleanConfigCommand()],
|
|
299
|
+
['BackupConfig', () => new BackupConfigCommand()]
|
|
300
|
+
])
|
|
301
|
+
|
|
302
|
+
static createCommand(name: string): MigrationCommand | null {
|
|
303
|
+
const factory = this.commands.get(name)
|
|
304
|
+
return factory ? factory() : null
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
static getAllCommands(): string[] {
|
|
308
|
+
return Array.from(this.commands.keys())
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
static registerCommand(name: string, factory: () => MigrationCommand): void {
|
|
312
|
+
this.commands.set(name, factory)
|
|
313
|
+
}
|
|
314
|
+
}
|