baseguard 1.0.3 → 1.0.4

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.
Files changed (167) hide show
  1. package/.baseguardrc.example.json +63 -63
  2. package/.eslintrc.json +24 -24
  3. package/.prettierrc +7 -7
  4. package/CHANGELOG.md +195 -195
  5. package/DEPLOYMENT.md +624 -624
  6. package/DEPLOYMENT_CHECKLIST.md +239 -239
  7. package/DEPLOYMENT_SUMMARY_v1.0.2.md +202 -202
  8. package/QUICK_START.md +134 -134
  9. package/README.md +488 -488
  10. package/RELEASE_NOTES_v1.0.2.md +434 -434
  11. package/bin/base.js +627 -627
  12. package/dist/ai/fix-manager.d.ts.map +1 -1
  13. package/dist/ai/fix-manager.js +1 -1
  14. package/dist/ai/fix-manager.js.map +1 -1
  15. package/dist/ai/gemini-analyzer.d.ts.map +1 -1
  16. package/dist/ai/gemini-analyzer.js +29 -35
  17. package/dist/ai/gemini-analyzer.js.map +1 -1
  18. package/dist/ai/gemini-code-fixer.d.ts.map +1 -1
  19. package/dist/ai/gemini-code-fixer.js +58 -58
  20. package/dist/ai/gemini-code-fixer.js.map +1 -1
  21. package/dist/ai/jules-implementer.d.ts +3 -0
  22. package/dist/ai/jules-implementer.d.ts.map +1 -1
  23. package/dist/ai/jules-implementer.js +63 -32
  24. package/dist/ai/jules-implementer.js.map +1 -1
  25. package/dist/ai/unified-code-fixer.js.map +1 -1
  26. package/dist/commands/check.d.ts.map +1 -1
  27. package/dist/commands/check.js +1 -1
  28. package/dist/commands/check.js.map +1 -1
  29. package/dist/commands/config.js +2 -1
  30. package/dist/commands/config.js.map +1 -1
  31. package/dist/commands/fix.d.ts.map +1 -1
  32. package/dist/commands/fix.js +44 -15
  33. package/dist/commands/fix.js.map +1 -1
  34. package/dist/core/api-key-manager.js +2 -2
  35. package/dist/core/api-key-manager.js.map +1 -1
  36. package/dist/core/baseguard.d.ts +1 -0
  37. package/dist/core/baseguard.d.ts.map +1 -1
  38. package/dist/core/baseguard.js +13 -10
  39. package/dist/core/baseguard.js.map +1 -1
  40. package/dist/core/baseline-checker.d.ts.map +1 -1
  41. package/dist/core/baseline-checker.js +2 -1
  42. package/dist/core/baseline-checker.js.map +1 -1
  43. package/dist/core/configuration-recovery.d.ts.map +1 -1
  44. package/dist/core/configuration-recovery.js +1 -1
  45. package/dist/core/configuration-recovery.js.map +1 -1
  46. package/dist/core/debug-logger.d.ts.map +1 -1
  47. package/dist/core/debug-logger.js +1 -1
  48. package/dist/core/debug-logger.js.map +1 -1
  49. package/dist/core/error-handler.d.ts.map +1 -1
  50. package/dist/core/error-handler.js +2 -1
  51. package/dist/core/error-handler.js.map +1 -1
  52. package/dist/core/gitignore-manager.js +5 -5
  53. package/dist/core/graceful-degradation-manager.d.ts.map +1 -1
  54. package/dist/core/graceful-degradation-manager.js +16 -16
  55. package/dist/core/graceful-degradation-manager.js.map +1 -1
  56. package/dist/core/lazy-loader.d.ts.map +1 -1
  57. package/dist/core/lazy-loader.js +9 -2
  58. package/dist/core/lazy-loader.js.map +1 -1
  59. package/dist/core/memory-manager.d.ts +0 -3
  60. package/dist/core/memory-manager.d.ts.map +1 -1
  61. package/dist/core/memory-manager.js.map +1 -1
  62. package/dist/core/parser-worker.d.ts +2 -0
  63. package/dist/core/parser-worker.d.ts.map +1 -0
  64. package/dist/core/parser-worker.js +19 -0
  65. package/dist/core/parser-worker.js.map +1 -0
  66. package/dist/core/startup-optimizer.d.ts.map +1 -1
  67. package/dist/core/startup-optimizer.js +4 -8
  68. package/dist/core/startup-optimizer.js.map +1 -1
  69. package/dist/core/system-error-handler.d.ts.map +1 -1
  70. package/dist/core/system-error-handler.js.map +1 -1
  71. package/dist/git/automation-engine.d.ts.map +1 -1
  72. package/dist/git/automation-engine.js +5 -4
  73. package/dist/git/automation-engine.js.map +1 -1
  74. package/dist/git/github-manager.d.ts.map +1 -1
  75. package/dist/git/github-manager.js.map +1 -1
  76. package/dist/git/hook-manager.js +5 -5
  77. package/dist/git/hook-manager.js.map +1 -1
  78. package/dist/parsers/parser-manager.d.ts.map +1 -1
  79. package/dist/parsers/parser-manager.js +1 -1
  80. package/dist/parsers/parser-manager.js.map +1 -1
  81. package/dist/parsers/svelte-parser.js +1 -1
  82. package/dist/parsers/svelte-parser.js.map +1 -1
  83. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  84. package/dist/parsers/vanilla-parser.js.map +1 -1
  85. package/dist/parsers/vue-parser.d.ts.map +1 -1
  86. package/dist/parsers/vue-parser.js.map +1 -1
  87. package/dist/ui/components.d.ts +1 -1
  88. package/dist/ui/components.d.ts.map +1 -1
  89. package/dist/ui/components.js +11 -11
  90. package/dist/ui/components.js.map +1 -1
  91. package/dist/ui/terminal-header.js +14 -14
  92. package/package.json +105 -105
  93. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  94. package/src/ai/agentkit-orchestrator.ts +533 -533
  95. package/src/ai/fix-manager.ts +362 -362
  96. package/src/ai/gemini-analyzer.ts +665 -671
  97. package/src/ai/gemini-code-fixer.ts +539 -540
  98. package/src/ai/index.ts +3 -3
  99. package/src/ai/jules-implementer.ts +504 -460
  100. package/src/ai/unified-code-fixer.ts +347 -347
  101. package/src/commands/automation.ts +343 -343
  102. package/src/commands/check.ts +298 -299
  103. package/src/commands/config.ts +584 -583
  104. package/src/commands/fix.ts +264 -238
  105. package/src/commands/index.ts +6 -6
  106. package/src/commands/init.ts +155 -155
  107. package/src/commands/status.ts +306 -306
  108. package/src/core/api-key-manager.ts +298 -298
  109. package/src/core/baseguard.ts +757 -756
  110. package/src/core/baseline-checker.ts +564 -563
  111. package/src/core/cache-manager.ts +271 -271
  112. package/src/core/configuration-recovery.ts +672 -673
  113. package/src/core/configuration.ts +595 -595
  114. package/src/core/debug-logger.ts +590 -590
  115. package/src/core/directory-filter.ts +420 -420
  116. package/src/core/error-handler.ts +518 -517
  117. package/src/core/file-processor.ts +337 -337
  118. package/src/core/gitignore-manager.ts +168 -168
  119. package/src/core/graceful-degradation-manager.ts +596 -596
  120. package/src/core/index.ts +16 -16
  121. package/src/core/lazy-loader.ts +317 -307
  122. package/src/core/memory-manager.ts +290 -295
  123. package/src/core/parser-worker.ts +33 -0
  124. package/src/core/startup-optimizer.ts +246 -255
  125. package/src/core/system-error-handler.ts +755 -756
  126. package/src/git/automation-engine.ts +361 -361
  127. package/src/git/github-manager.ts +190 -192
  128. package/src/git/hook-manager.ts +210 -210
  129. package/src/git/index.ts +3 -3
  130. package/src/index.ts +7 -7
  131. package/src/parsers/feature-validator.ts +558 -558
  132. package/src/parsers/index.ts +7 -7
  133. package/src/parsers/parser-manager.ts +418 -419
  134. package/src/parsers/parser.ts +25 -25
  135. package/src/parsers/react-parser-optimized.ts +160 -160
  136. package/src/parsers/react-parser.ts +358 -358
  137. package/src/parsers/svelte-parser.ts +510 -510
  138. package/src/parsers/vanilla-parser.ts +685 -686
  139. package/src/parsers/vue-parser.ts +476 -478
  140. package/src/types/index.ts +95 -95
  141. package/src/ui/components.ts +567 -567
  142. package/src/ui/help.ts +192 -192
  143. package/src/ui/index.ts +3 -3
  144. package/src/ui/prompts.ts +680 -680
  145. package/src/ui/terminal-header.ts +58 -58
  146. package/test-build.js +40 -40
  147. package/test-config-commands.js +55 -55
  148. package/test-header-simple.js +32 -32
  149. package/test-terminal-header.js +11 -11
  150. package/test-ui.js +28 -28
  151. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  152. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  153. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  154. package/tests/fixtures/react-project/package.json +13 -13
  155. package/tests/fixtures/react-project/src/App.css +75 -75
  156. package/tests/fixtures/react-project/src/App.tsx +76 -76
  157. package/tests/fixtures/svelte-project/package.json +10 -10
  158. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  159. package/tests/fixtures/vanilla-project/index.html +75 -75
  160. package/tests/fixtures/vanilla-project/script.js +330 -330
  161. package/tests/fixtures/vanilla-project/styles.css +358 -358
  162. package/tests/fixtures/vue-project/package.json +11 -11
  163. package/tests/fixtures/vue-project/src/App.vue +215 -215
  164. package/tsconfig.json +34 -34
  165. package/vitest.config.ts +11 -11
  166. package/dist/terminal-header.d.ts +0 -12
  167. package/dist/terminal-header.js +0 -45
@@ -1,673 +1,672 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
3
- import chalk from 'chalk';
4
- import { ConfigurationManager } from './configuration.js';
5
- import { SystemErrorHandler } from './system-error-handler.js';
6
- import { logger } from './debug-logger.js';
7
- import { UIComponents } from '../ui/components.js';
8
- import type { Configuration } from '../types/index.js';
9
-
10
- export interface ConfigurationBackup {
11
- timestamp: Date;
12
- version: string;
13
- config: Configuration;
14
- source: 'auto' | 'manual' | 'recovery';
15
- checksum: string;
16
- }
17
-
18
- export interface RecoveryOptions {
19
- createBackup: boolean;
20
- validateConfig: boolean;
21
- migrateVersion: boolean;
22
- repairCorruption: boolean;
23
- useDefaults: boolean;
24
- }
25
-
26
- /**
27
- * Enhanced configuration recovery and backup system
28
- */
29
- export class ConfigurationRecovery {
30
- private static readonly CONFIG_FILE = '.baseguardrc.json';
31
- private static readonly BACKUP_DIR = path.join('.baseguard', 'backups');
32
- private static readonly MAX_BACKUPS = 10;
33
- private static readonly RECOVERY_LOG = path.join('.baseguard', 'recovery.log');
34
-
35
- /**
36
- * Attempt to recover corrupted configuration
37
- */
38
- static async recoverConfiguration(options: Partial<RecoveryOptions> = {}): Promise<{
39
- success: boolean;
40
- config?: Configuration;
41
- backupCreated?: string;
42
- errors: string[];
43
- warnings: string[];
44
- }> {
45
- const recoveryOptions: RecoveryOptions = {
46
- createBackup: true,
47
- validateConfig: true,
48
- migrateVersion: true,
49
- repairCorruption: true,
50
- useDefaults: false,
51
- ...options
52
- };
53
-
54
- const result = {
55
- success: false,
56
- config: undefined as Configuration | undefined,
57
- backupCreated: undefined as string | undefined,
58
- errors: [] as string[],
59
- warnings: [] as string[]
60
- };
61
-
62
- const categoryLogger = logger.createCategoryLogger('config-recovery');
63
- categoryLogger.info('Starting configuration recovery', { options: recoveryOptions });
64
-
65
- try {
66
- // Step 1: Check if config file exists
67
- const configExists = await this.configFileExists();
68
-
69
- if (!configExists) {
70
- categoryLogger.info('Configuration file does not exist, creating default');
71
- result.config = await this.createDefaultConfiguration();
72
- result.success = true;
73
- return result;
74
- }
75
-
76
- // Step 2: Try to read and parse existing config
77
- let currentConfig: any = null;
78
- let configContent = '';
79
-
80
- try {
81
- configContent = await fs.readFile(this.CONFIG_FILE, 'utf-8');
82
- currentConfig = JSON.parse(configContent);
83
- categoryLogger.debug('Successfully read configuration file');
84
- } catch (parseError) {
85
- categoryLogger.error('Failed to parse configuration file', { error: parseError });
86
- result.errors.push(`Configuration file is corrupted: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
87
-
88
- if (recoveryOptions.repairCorruption) {
89
- const repairResult = await this.repairCorruptedConfig(configContent);
90
- if (repairResult.success) {
91
- currentConfig = repairResult.config;
92
- result.warnings.push('Configuration was repaired from corrupted state');
93
- }
94
- }
95
- }
96
-
97
- // Step 3: Create backup if requested and config exists
98
- if (recoveryOptions.createBackup && currentConfig) {
99
- try {
100
- result.backupCreated = await this.createBackup(currentConfig, 'recovery');
101
- categoryLogger.info('Created configuration backup', { backupFile: result.backupCreated });
102
- } catch (backupError) {
103
- result.warnings.push(`Failed to create backup: ${backupError instanceof Error ? backupError.message : 'Unknown error'}`);
104
- }
105
- }
106
-
107
- // Step 4: Validate configuration structure
108
- if (recoveryOptions.validateConfig && currentConfig) {
109
- const validation = ConfigurationManager.validateConfiguration(currentConfig);
110
- if (!validation.valid) {
111
- categoryLogger.warn('Configuration validation failed', { errors: validation.errors });
112
- result.errors.push(...validation.errors);
113
-
114
- if (recoveryOptions.repairCorruption) {
115
- currentConfig = await this.repairValidationErrors(currentConfig, validation.errors);
116
- result.warnings.push('Configuration was repaired to fix validation errors');
117
- }
118
- }
119
- }
120
-
121
- // Step 5: Migrate configuration version if needed
122
- if (recoveryOptions.migrateVersion && currentConfig) {
123
- const migrationResult = await this.migrateConfiguration(currentConfig);
124
- if (migrationResult.migrated) {
125
- currentConfig = migrationResult.config;
126
- result.warnings.push(`Configuration migrated from version ${migrationResult.fromVersion} to ${migrationResult.toVersion}`);
127
- categoryLogger.info('Configuration migrated', migrationResult);
128
- }
129
- }
130
-
131
- // Step 6: Final validation and save
132
- if (currentConfig) {
133
- try {
134
- // Use the public migrateConfiguration method instead
135
- const finalConfig = ConfigurationManager.migrateConfiguration(currentConfig);
136
-
137
- await ConfigurationManager.save(finalConfig);
138
- result.config = finalConfig;
139
- result.success = true;
140
- categoryLogger.info('Configuration recovery completed successfully');
141
- } catch (saveError) {
142
- result.errors.push(`Failed to save recovered configuration: ${saveError instanceof Error ? saveError.message : 'Unknown error'}`);
143
- }
144
- }
145
-
146
- // Step 7: Use defaults as last resort
147
- if (!result.success && recoveryOptions.useDefaults) {
148
- categoryLogger.warn('Using default configuration as last resort');
149
- result.config = await this.createDefaultConfiguration();
150
- result.success = true;
151
- result.warnings.push('Used default configuration due to unrecoverable errors');
152
- }
153
-
154
- } catch (error) {
155
- categoryLogger.error('Configuration recovery failed', { error });
156
- result.errors.push(`Recovery process failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
157
- }
158
-
159
- // Log recovery result
160
- await this.logRecoveryAttempt(result);
161
-
162
- return result;
163
- }
164
-
165
- /**
166
- * Create automatic backup of configuration
167
- */
168
- static async createAutoBackup(): Promise<string | null> {
169
- try {
170
- const config = await ConfigurationManager.load();
171
- return await this.createBackup(config, 'auto');
172
- } catch (error) {
173
- logger.warn('config-backup', 'Failed to create automatic backup', { error: error instanceof Error ? error.message : 'Unknown error' });
174
- return null;
175
- }
176
- }
177
-
178
- /**
179
- * Create manual backup of configuration
180
- */
181
- static async createManualBackup(): Promise<string> {
182
- const config = await ConfigurationManager.load();
183
- return await this.createBackup(config, 'manual');
184
- }
185
-
186
- /**
187
- * Create backup with metadata
188
- */
189
- private static async createBackup(config: Configuration, source: 'auto' | 'manual' | 'recovery'): Promise<string> {
190
- await fs.mkdir(this.BACKUP_DIR, { recursive: true });
191
-
192
- const timestamp = new Date();
193
- const backupId = `${timestamp.toISOString().replace(/[:.]/g, '-')}-${source}`;
194
- const backupFile = path.join(this.BACKUP_DIR, `config-${backupId}.json`);
195
-
196
- const backup: ConfigurationBackup = {
197
- timestamp,
198
- version: config.version,
199
- config,
200
- source,
201
- checksum: this.calculateChecksum(config)
202
- };
203
-
204
- await fs.writeFile(backupFile, JSON.stringify(backup, null, 2));
205
-
206
- // Clean up old backups
207
- await this.cleanupOldBackups();
208
-
209
- return backupFile;
210
- }
211
-
212
- /**
213
- * Restore configuration from backup
214
- */
215
- static async restoreFromBackup(backupFile: string): Promise<{
216
- success: boolean;
217
- config?: Configuration;
218
- errors: string[];
219
- }> {
220
- const result = {
221
- success: false,
222
- config: undefined as Configuration | undefined,
223
- errors: [] as string[]
224
- };
225
-
226
- try {
227
- const backupContent = await fs.readFile(backupFile, 'utf-8');
228
- const backup: ConfigurationBackup = JSON.parse(backupContent);
229
-
230
- // Validate backup integrity
231
- const currentChecksum = this.calculateChecksum(backup.config);
232
- if (currentChecksum !== backup.checksum) {
233
- result.errors.push('Backup file integrity check failed');
234
- return result;
235
- }
236
-
237
- // Validate configuration
238
- const validation = ConfigurationManager.validateConfiguration(backup.config);
239
- if (!validation.valid) {
240
- result.errors.push(...validation.errors);
241
- return result;
242
- }
243
-
244
- // Save restored configuration
245
- await ConfigurationManager.save(backup.config);
246
- result.config = backup.config;
247
- result.success = true;
248
-
249
- logger.info('config-recovery', 'Configuration restored from backup', {
250
- backupFile,
251
- backupTimestamp: backup.timestamp,
252
- backupSource: backup.source
253
- });
254
-
255
- } catch (error) {
256
- result.errors.push(`Failed to restore backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
257
- }
258
-
259
- return result;
260
- }
261
-
262
- /**
263
- * List available backups
264
- */
265
- static async listBackups(): Promise<{
266
- file: string;
267
- timestamp: Date;
268
- version: string;
269
- source: string;
270
- size: number;
271
- }[]> {
272
- try {
273
- const backups: any[] = [];
274
- const files = await fs.readdir(this.BACKUP_DIR);
275
-
276
- for (const file of files) {
277
- if (!file.startsWith('config-') || !file.endsWith('.json')) {
278
- continue;
279
- }
280
-
281
- try {
282
- const filePath = path.join(this.BACKUP_DIR, file);
283
- const stats = await fs.stat(filePath);
284
- const content = await fs.readFile(filePath, 'utf-8');
285
- const backup: ConfigurationBackup = JSON.parse(content);
286
-
287
- backups.push({
288
- file: filePath,
289
- timestamp: new Date(backup.timestamp),
290
- version: backup.version,
291
- source: backup.source,
292
- size: stats.size
293
- });
294
- } catch (error) {
295
- // Skip invalid backup files
296
- logger.warn('config-recovery', `Invalid backup file: ${file}`, { error: error instanceof Error ? error.message : 'Unknown error' });
297
- }
298
- }
299
-
300
- return backups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
301
- } catch (error) {
302
- logger.error('config-recovery', 'Failed to list backups', { error });
303
- return [];
304
- }
305
- }
306
-
307
- /**
308
- * Repair corrupted configuration content
309
- */
310
- private static async repairCorruptedConfig(content: string): Promise<{
311
- success: boolean;
312
- config?: any;
313
- repairs: string[];
314
- }> {
315
- const result = {
316
- success: false,
317
- config: undefined as any,
318
- repairs: [] as string[]
319
- };
320
-
321
- try {
322
- // Try to fix common JSON issues
323
- let repairedContent = content;
324
-
325
- // Fix trailing commas
326
- repairedContent = repairedContent.replace(/,(\s*[}\]])/g, '$1');
327
- result.repairs.push('Removed trailing commas');
328
-
329
- // Fix missing quotes around keys
330
- repairedContent = repairedContent.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, '$1"$2":');
331
- result.repairs.push('Added quotes around object keys');
332
-
333
- // Fix single quotes to double quotes
334
- repairedContent = repairedContent.replace(/'/g, '"');
335
- result.repairs.push('Converted single quotes to double quotes');
336
-
337
- // Try to parse repaired content
338
- try {
339
- result.config = JSON.parse(repairedContent);
340
- result.success = true;
341
- } catch (parseError) {
342
- // If still can't parse, try to extract valid parts
343
- const extractResult = await this.extractValidConfigParts(content);
344
- if (extractResult.success) {
345
- result.config = extractResult.config;
346
- result.success = true;
347
- result.repairs.push(...extractResult.repairs);
348
- }
349
- }
350
-
351
- } catch (error) {
352
- logger.error('config-recovery', 'Failed to repair corrupted config', { error });
353
- }
354
-
355
- return result;
356
- }
357
-
358
- /**
359
- * Extract valid configuration parts from corrupted content
360
- */
361
- private static async extractValidConfigParts(content: string): Promise<{
362
- success: boolean;
363
- config?: any;
364
- repairs: string[];
365
- }> {
366
- const result = {
367
- success: false,
368
- config: undefined as any,
369
- repairs: [] as string[]
370
- };
371
-
372
- try {
373
- // Create minimal valid configuration
374
- const defaultConfig = ConfigurationManager.createDefault();
375
- const extractedConfig = { ...defaultConfig };
376
-
377
- // Try to extract specific fields using regex
378
- const patterns = {
379
- version: /"version"\s*:\s*"([^"]+)"/,
380
- targets: /"targets"\s*:\s*(\[[^\]]*\])/,
381
- apiKeys: /"apiKeys"\s*:\s*(\{[^}]*\})/,
382
- automation: /"automation"\s*:\s*(\{[^}]*\})/
383
- };
384
-
385
- for (const [field, pattern] of Object.entries(patterns)) {
386
- const match = content.match(pattern);
387
- if (match && match[1]) {
388
- try {
389
- if (field === 'version') {
390
- extractedConfig.version = match[1];
391
- } else {
392
- const parsed = JSON.parse(match[1]);
393
- (extractedConfig as any)[field] = parsed;
394
- }
395
- result.repairs.push(`Extracted ${field} field`);
396
- } catch (error) {
397
- // Skip invalid field
398
- }
399
- }
400
- }
401
-
402
- result.config = extractedConfig;
403
- result.success = true;
404
- result.repairs.push('Created configuration from extracted valid parts');
405
-
406
- } catch (error) {
407
- logger.error('config-recovery', 'Failed to extract config parts', { error });
408
- }
409
-
410
- return result;
411
- }
412
-
413
- /**
414
- * Repair configuration validation errors
415
- */
416
- private static async repairValidationErrors(config: any, errors: string[]): Promise<any> {
417
- const repairedConfig = { ...config };
418
- const defaultConfig = ConfigurationManager.createDefault();
419
-
420
- for (const error of errors) {
421
- if (error.includes('version')) {
422
- repairedConfig.version = defaultConfig.version;
423
- } else if (error.includes('targets')) {
424
- repairedConfig.targets = defaultConfig.targets;
425
- } else if (error.includes('apiKeys')) {
426
- repairedConfig.apiKeys = defaultConfig.apiKeys;
427
- } else if (error.includes('automation')) {
428
- repairedConfig.automation = defaultConfig.automation;
429
- }
430
- }
431
-
432
- return repairedConfig;
433
- }
434
-
435
- /**
436
- * Migrate configuration to current version
437
- */
438
- private static async migrateConfiguration(config: any): Promise<{
439
- migrated: boolean;
440
- config: any;
441
- fromVersion?: string;
442
- toVersion: string;
443
- }> {
444
- const currentVersion = '1.0.0';
445
- const configVersion = config.version || '0.0.0';
446
-
447
- if (configVersion === currentVersion) {
448
- return {
449
- migrated: false,
450
- config,
451
- toVersion: currentVersion
452
- };
453
- }
454
-
455
- // Perform migration
456
- const migratedConfig = ConfigurationManager.migrateConfiguration(config);
457
-
458
- return {
459
- migrated: true,
460
- config: migratedConfig,
461
- fromVersion: configVersion,
462
- toVersion: currentVersion
463
- };
464
- }
465
-
466
- /**
467
- * Create default configuration with recovery metadata
468
- */
469
- private static async createDefaultConfiguration(): Promise<Configuration> {
470
- const config = ConfigurationManager.createDefault();
471
- await ConfigurationManager.save(config);
472
-
473
- logger.info('config-recovery', 'Created default configuration');
474
- return config;
475
- }
476
-
477
- /**
478
- * Check if configuration file exists
479
- */
480
- private static async configFileExists(): Promise<boolean> {
481
- try {
482
- await fs.access(this.CONFIG_FILE);
483
- return true;
484
- } catch {
485
- return false;
486
- }
487
- }
488
-
489
- /**
490
- * Calculate configuration checksum for integrity verification
491
- */
492
- private static calculateChecksum(config: Configuration): string {
493
- const { createHash } = require('crypto');
494
- const configString = JSON.stringify(config, Object.keys(config).sort());
495
- return createHash('sha256').update(configString).digest('hex');
496
- }
497
-
498
- /**
499
- * Clean up old backup files
500
- */
501
- private static async cleanupOldBackups(): Promise<void> {
502
- try {
503
- const backups = await this.listBackups();
504
-
505
- if (backups.length > this.MAX_BACKUPS) {
506
- const toDelete = backups.slice(this.MAX_BACKUPS);
507
-
508
- for (const backup of toDelete) {
509
- try {
510
- await fs.unlink(backup.file);
511
- logger.debug('config-recovery', `Deleted old backup: ${path.basename(backup.file)}`);
512
- } catch (error) {
513
- // Ignore individual deletion errors
514
- }
515
- }
516
- }
517
- } catch (error) {
518
- logger.warn('config-recovery', 'Failed to cleanup old backups', { error: error instanceof Error ? error.message : 'Unknown error' });
519
- }
520
- }
521
-
522
- /**
523
- * Log recovery attempt for debugging
524
- */
525
- private static async logRecoveryAttempt(result: any): Promise<void> {
526
- try {
527
- await fs.mkdir(path.dirname(this.RECOVERY_LOG), { recursive: true });
528
-
529
- const logEntry = {
530
- timestamp: new Date().toISOString(),
531
- success: result.success,
532
- errors: result.errors,
533
- warnings: result.warnings,
534
- backupCreated: result.backupCreated
535
- };
536
-
537
- await fs.appendFile(this.RECOVERY_LOG, JSON.stringify(logEntry) + '\n');
538
- } catch (error) {
539
- // Ignore logging errors
540
- }
541
- }
542
-
543
- /**
544
- * Validate configuration file integrity
545
- */
546
- static async validateIntegrity(): Promise<{
547
- valid: boolean;
548
- readable: boolean;
549
- parseable: boolean;
550
- structureValid: boolean;
551
- errors: string[];
552
- suggestions: string[];
553
- }> {
554
- const result = {
555
- valid: false,
556
- readable: false,
557
- parseable: false,
558
- structureValid: false,
559
- errors: [] as string[],
560
- suggestions: [] as string[]
561
- };
562
-
563
- try {
564
- // Check if file is readable
565
- const content = await fs.readFile(this.CONFIG_FILE, 'utf-8');
566
- result.readable = true;
567
-
568
- // Check if content is parseable JSON
569
- let config: any;
570
- try {
571
- config = JSON.parse(content);
572
- result.parseable = true;
573
- } catch (parseError) {
574
- result.errors.push(`Configuration file contains invalid JSON: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
575
- result.suggestions.push('Run "base init" to recreate the configuration file');
576
- return result;
577
- }
578
-
579
- // Check configuration structure
580
- const validation = ConfigurationManager.validateConfiguration(config);
581
- result.structureValid = validation.valid;
582
-
583
- if (!validation.valid) {
584
- result.errors.push(...validation.errors);
585
- result.suggestions.push('Run "base config" to fix configuration issues');
586
- }
587
-
588
- result.valid = result.readable && result.parseable && result.structureValid;
589
-
590
- } catch (error) {
591
- if ((error as any).code === 'ENOENT') {
592
- result.errors.push('Configuration file does not exist');
593
- result.suggestions.push('Run "base init" to create a new configuration file');
594
- } else {
595
- result.errors.push(`Cannot read configuration file: ${error instanceof Error ? error.message : 'Unknown error'}`);
596
- result.suggestions.push('Check file permissions and try again');
597
- }
598
- }
599
-
600
- return result;
601
- }
602
-
603
- /**
604
- * Interactive recovery wizard
605
- */
606
- static async runRecoveryWizard(): Promise<void> {
607
- console.log(chalk.cyan('šŸ”§ BaseGuard Configuration Recovery Wizard'));
608
- console.log(chalk.dim('This wizard will help you recover or repair your BaseGuard configuration.\n'));
609
-
610
- // Step 1: Validate current configuration
611
- console.log(chalk.cyan('Step 1: Validating current configuration...'));
612
- const integrity = await this.validateIntegrity();
613
-
614
- if (integrity.valid) {
615
- UIComponents.showSuccessBox('Configuration is valid and healthy');
616
- return;
617
- }
618
-
619
- // Step 2: Show issues
620
- console.log(chalk.red('\nāŒ Configuration issues found:'));
621
- integrity.errors.forEach(error => {
622
- console.log(chalk.red(` • ${error}`));
623
- });
624
-
625
- console.log(chalk.cyan('\nšŸ’” Suggestions:'));
626
- integrity.suggestions.forEach(suggestion => {
627
- console.log(chalk.cyan(` • ${suggestion}`));
628
- });
629
-
630
- // Step 3: Offer recovery options
631
- console.log(chalk.cyan('\nšŸ”„ Recovery Options:'));
632
- console.log(chalk.cyan(' 1. Automatic repair (recommended)'));
633
- console.log(chalk.cyan(' 2. Restore from backup'));
634
- console.log(chalk.cyan(' 3. Create new configuration'));
635
- console.log(chalk.cyan(' 4. Manual repair guidance'));
636
-
637
- // For now, just run automatic repair
638
- console.log(chalk.cyan('\nRunning automatic repair...'));
639
-
640
- const recoveryResult = await this.recoverConfiguration({
641
- createBackup: true,
642
- validateConfig: true,
643
- migrateVersion: true,
644
- repairCorruption: true,
645
- useDefaults: true
646
- });
647
-
648
- if (recoveryResult.success) {
649
- UIComponents.showSuccessBox('Configuration recovered successfully');
650
-
651
- if (recoveryResult.backupCreated) {
652
- console.log(chalk.dim(`Backup created: ${recoveryResult.backupCreated}`));
653
- }
654
-
655
- if (recoveryResult.warnings.length > 0) {
656
- console.log(chalk.yellow('\nāš ļø Warnings:'));
657
- recoveryResult.warnings.forEach(warning => {
658
- console.log(chalk.yellow(` • ${warning}`));
659
- });
660
- }
661
- } else {
662
- console.log(chalk.red('\nāŒ Recovery failed:'));
663
- recoveryResult.errors.forEach(error => {
664
- console.log(chalk.red(` • ${error}`));
665
- });
666
-
667
- console.log(chalk.cyan('\nšŸ’” Next steps:'));
668
- console.log(chalk.cyan(' • Run "base init" to create a fresh configuration'));
669
- console.log(chalk.cyan(' • Check file permissions in your project directory'));
670
- console.log(chalk.cyan(' • Contact support if the issue persists'));
671
- }
672
- }
673
- }
1
+ import { promises as fs } from 'fs';
2
+ import { createHash } from 'crypto';
3
+ import path from 'path';
4
+ import chalk from 'chalk';
5
+ import { ConfigurationManager } from './configuration.js';
6
+ import { logger } from './debug-logger.js';
7
+ import { UIComponents } from '../ui/components.js';
8
+ import type { Configuration } from '../types/index.js';
9
+
10
+ export interface ConfigurationBackup {
11
+ timestamp: Date;
12
+ version: string;
13
+ config: Configuration;
14
+ source: 'auto' | 'manual' | 'recovery';
15
+ checksum: string;
16
+ }
17
+
18
+ export interface RecoveryOptions {
19
+ createBackup: boolean;
20
+ validateConfig: boolean;
21
+ migrateVersion: boolean;
22
+ repairCorruption: boolean;
23
+ useDefaults: boolean;
24
+ }
25
+
26
+ /**
27
+ * Enhanced configuration recovery and backup system
28
+ */
29
+ export class ConfigurationRecovery {
30
+ private static readonly CONFIG_FILE = '.baseguardrc.json';
31
+ private static readonly BACKUP_DIR = path.join('.baseguard', 'backups');
32
+ private static readonly MAX_BACKUPS = 10;
33
+ private static readonly RECOVERY_LOG = path.join('.baseguard', 'recovery.log');
34
+
35
+ /**
36
+ * Attempt to recover corrupted configuration
37
+ */
38
+ static async recoverConfiguration(options: Partial<RecoveryOptions> = {}): Promise<{
39
+ success: boolean;
40
+ config?: Configuration;
41
+ backupCreated?: string;
42
+ errors: string[];
43
+ warnings: string[];
44
+ }> {
45
+ const recoveryOptions: RecoveryOptions = {
46
+ createBackup: true,
47
+ validateConfig: true,
48
+ migrateVersion: true,
49
+ repairCorruption: true,
50
+ useDefaults: false,
51
+ ...options
52
+ };
53
+
54
+ const result = {
55
+ success: false,
56
+ config: undefined as Configuration | undefined,
57
+ backupCreated: undefined as string | undefined,
58
+ errors: [] as string[],
59
+ warnings: [] as string[]
60
+ };
61
+
62
+ const categoryLogger = logger.createCategoryLogger('config-recovery');
63
+ categoryLogger.info('Starting configuration recovery', { options: recoveryOptions });
64
+
65
+ try {
66
+ // Step 1: Check if config file exists
67
+ const configExists = await this.configFileExists();
68
+
69
+ if (!configExists) {
70
+ categoryLogger.info('Configuration file does not exist, creating default');
71
+ result.config = await this.createDefaultConfiguration();
72
+ result.success = true;
73
+ return result;
74
+ }
75
+
76
+ // Step 2: Try to read and parse existing config
77
+ let currentConfig: any = null;
78
+ let configContent = '';
79
+
80
+ try {
81
+ configContent = await fs.readFile(this.CONFIG_FILE, 'utf-8');
82
+ currentConfig = JSON.parse(configContent);
83
+ categoryLogger.debug('Successfully read configuration file');
84
+ } catch (parseError) {
85
+ categoryLogger.error('Failed to parse configuration file', { error: parseError });
86
+ result.errors.push(`Configuration file is corrupted: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
87
+
88
+ if (recoveryOptions.repairCorruption) {
89
+ const repairResult = await this.repairCorruptedConfig(configContent);
90
+ if (repairResult.success) {
91
+ currentConfig = repairResult.config;
92
+ result.warnings.push('Configuration was repaired from corrupted state');
93
+ }
94
+ }
95
+ }
96
+
97
+ // Step 3: Create backup if requested and config exists
98
+ if (recoveryOptions.createBackup && currentConfig) {
99
+ try {
100
+ result.backupCreated = await this.createBackup(currentConfig, 'recovery');
101
+ categoryLogger.info('Created configuration backup', { backupFile: result.backupCreated });
102
+ } catch (backupError) {
103
+ result.warnings.push(`Failed to create backup: ${backupError instanceof Error ? backupError.message : 'Unknown error'}`);
104
+ }
105
+ }
106
+
107
+ // Step 4: Validate configuration structure
108
+ if (recoveryOptions.validateConfig && currentConfig) {
109
+ const validation = ConfigurationManager.validateConfiguration(currentConfig);
110
+ if (!validation.valid) {
111
+ categoryLogger.warn('Configuration validation failed', { errors: validation.errors });
112
+ result.errors.push(...validation.errors);
113
+
114
+ if (recoveryOptions.repairCorruption) {
115
+ currentConfig = await this.repairValidationErrors(currentConfig, validation.errors);
116
+ result.warnings.push('Configuration was repaired to fix validation errors');
117
+ }
118
+ }
119
+ }
120
+
121
+ // Step 5: Migrate configuration version if needed
122
+ if (recoveryOptions.migrateVersion && currentConfig) {
123
+ const migrationResult = await this.migrateConfiguration(currentConfig);
124
+ if (migrationResult.migrated) {
125
+ currentConfig = migrationResult.config;
126
+ result.warnings.push(`Configuration migrated from version ${migrationResult.fromVersion} to ${migrationResult.toVersion}`);
127
+ categoryLogger.info('Configuration migrated', migrationResult);
128
+ }
129
+ }
130
+
131
+ // Step 6: Final validation and save
132
+ if (currentConfig) {
133
+ try {
134
+ // Use the public migrateConfiguration method instead
135
+ const finalConfig = ConfigurationManager.migrateConfiguration(currentConfig);
136
+
137
+ await ConfigurationManager.save(finalConfig);
138
+ result.config = finalConfig;
139
+ result.success = true;
140
+ categoryLogger.info('Configuration recovery completed successfully');
141
+ } catch (saveError) {
142
+ result.errors.push(`Failed to save recovered configuration: ${saveError instanceof Error ? saveError.message : 'Unknown error'}`);
143
+ }
144
+ }
145
+
146
+ // Step 7: Use defaults as last resort
147
+ if (!result.success && recoveryOptions.useDefaults) {
148
+ categoryLogger.warn('Using default configuration as last resort');
149
+ result.config = await this.createDefaultConfiguration();
150
+ result.success = true;
151
+ result.warnings.push('Used default configuration due to unrecoverable errors');
152
+ }
153
+
154
+ } catch (error) {
155
+ categoryLogger.error('Configuration recovery failed', { error });
156
+ result.errors.push(`Recovery process failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
157
+ }
158
+
159
+ // Log recovery result
160
+ await this.logRecoveryAttempt(result);
161
+
162
+ return result;
163
+ }
164
+
165
+ /**
166
+ * Create automatic backup of configuration
167
+ */
168
+ static async createAutoBackup(): Promise<string | null> {
169
+ try {
170
+ const config = await ConfigurationManager.load();
171
+ return await this.createBackup(config, 'auto');
172
+ } catch (error) {
173
+ logger.warn('config-backup', 'Failed to create automatic backup', { error: error instanceof Error ? error.message : 'Unknown error' });
174
+ return null;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Create manual backup of configuration
180
+ */
181
+ static async createManualBackup(): Promise<string> {
182
+ const config = await ConfigurationManager.load();
183
+ return await this.createBackup(config, 'manual');
184
+ }
185
+
186
+ /**
187
+ * Create backup with metadata
188
+ */
189
+ private static async createBackup(config: Configuration, source: 'auto' | 'manual' | 'recovery'): Promise<string> {
190
+ await fs.mkdir(this.BACKUP_DIR, { recursive: true });
191
+
192
+ const timestamp = new Date();
193
+ const backupId = `${timestamp.toISOString().replace(/[:.]/g, '-')}-${source}`;
194
+ const backupFile = path.join(this.BACKUP_DIR, `config-${backupId}.json`);
195
+
196
+ const backup: ConfigurationBackup = {
197
+ timestamp,
198
+ version: config.version,
199
+ config,
200
+ source,
201
+ checksum: this.calculateChecksum(config)
202
+ };
203
+
204
+ await fs.writeFile(backupFile, JSON.stringify(backup, null, 2));
205
+
206
+ // Clean up old backups
207
+ await this.cleanupOldBackups();
208
+
209
+ return backupFile;
210
+ }
211
+
212
+ /**
213
+ * Restore configuration from backup
214
+ */
215
+ static async restoreFromBackup(backupFile: string): Promise<{
216
+ success: boolean;
217
+ config?: Configuration;
218
+ errors: string[];
219
+ }> {
220
+ const result = {
221
+ success: false,
222
+ config: undefined as Configuration | undefined,
223
+ errors: [] as string[]
224
+ };
225
+
226
+ try {
227
+ const backupContent = await fs.readFile(backupFile, 'utf-8');
228
+ const backup: ConfigurationBackup = JSON.parse(backupContent);
229
+
230
+ // Validate backup integrity
231
+ const currentChecksum = this.calculateChecksum(backup.config);
232
+ if (currentChecksum !== backup.checksum) {
233
+ result.errors.push('Backup file integrity check failed');
234
+ return result;
235
+ }
236
+
237
+ // Validate configuration
238
+ const validation = ConfigurationManager.validateConfiguration(backup.config);
239
+ if (!validation.valid) {
240
+ result.errors.push(...validation.errors);
241
+ return result;
242
+ }
243
+
244
+ // Save restored configuration
245
+ await ConfigurationManager.save(backup.config);
246
+ result.config = backup.config;
247
+ result.success = true;
248
+
249
+ logger.info('config-recovery', 'Configuration restored from backup', {
250
+ backupFile,
251
+ backupTimestamp: backup.timestamp,
252
+ backupSource: backup.source
253
+ });
254
+
255
+ } catch (error) {
256
+ result.errors.push(`Failed to restore backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
257
+ }
258
+
259
+ return result;
260
+ }
261
+
262
+ /**
263
+ * List available backups
264
+ */
265
+ static async listBackups(): Promise<{
266
+ file: string;
267
+ timestamp: Date;
268
+ version: string;
269
+ source: string;
270
+ size: number;
271
+ }[]> {
272
+ try {
273
+ const backups: any[] = [];
274
+ const files = await fs.readdir(this.BACKUP_DIR);
275
+
276
+ for (const file of files) {
277
+ if (!file.startsWith('config-') || !file.endsWith('.json')) {
278
+ continue;
279
+ }
280
+
281
+ try {
282
+ const filePath = path.join(this.BACKUP_DIR, file);
283
+ const stats = await fs.stat(filePath);
284
+ const content = await fs.readFile(filePath, 'utf-8');
285
+ const backup: ConfigurationBackup = JSON.parse(content);
286
+
287
+ backups.push({
288
+ file: filePath,
289
+ timestamp: new Date(backup.timestamp),
290
+ version: backup.version,
291
+ source: backup.source,
292
+ size: stats.size
293
+ });
294
+ } catch (error) {
295
+ // Skip invalid backup files
296
+ logger.warn('config-recovery', `Invalid backup file: ${file}`, { error: error instanceof Error ? error.message : 'Unknown error' });
297
+ }
298
+ }
299
+
300
+ return backups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
301
+ } catch (error) {
302
+ logger.error('config-recovery', 'Failed to list backups', { error });
303
+ return [];
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Repair corrupted configuration content
309
+ */
310
+ private static async repairCorruptedConfig(content: string): Promise<{
311
+ success: boolean;
312
+ config?: any;
313
+ repairs: string[];
314
+ }> {
315
+ const result = {
316
+ success: false,
317
+ config: undefined as any,
318
+ repairs: [] as string[]
319
+ };
320
+
321
+ try {
322
+ // Try to fix common JSON issues
323
+ let repairedContent = content;
324
+
325
+ // Fix trailing commas
326
+ repairedContent = repairedContent.replace(/,(\s*[}\]])/g, '$1');
327
+ result.repairs.push('Removed trailing commas');
328
+
329
+ // Fix missing quotes around keys
330
+ repairedContent = repairedContent.replace(/([{,]\s*)([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, '$1"$2":');
331
+ result.repairs.push('Added quotes around object keys');
332
+
333
+ // Fix single quotes to double quotes
334
+ repairedContent = repairedContent.replace(/'/g, '"');
335
+ result.repairs.push('Converted single quotes to double quotes');
336
+
337
+ // Try to parse repaired content
338
+ try {
339
+ result.config = JSON.parse(repairedContent);
340
+ result.success = true;
341
+ } catch (parseError) {
342
+ // If still can't parse, try to extract valid parts
343
+ const extractResult = await this.extractValidConfigParts(content);
344
+ if (extractResult.success) {
345
+ result.config = extractResult.config;
346
+ result.success = true;
347
+ result.repairs.push(...extractResult.repairs);
348
+ }
349
+ }
350
+
351
+ } catch (error) {
352
+ logger.error('config-recovery', 'Failed to repair corrupted config', { error });
353
+ }
354
+
355
+ return result;
356
+ }
357
+
358
+ /**
359
+ * Extract valid configuration parts from corrupted content
360
+ */
361
+ private static async extractValidConfigParts(content: string): Promise<{
362
+ success: boolean;
363
+ config?: any;
364
+ repairs: string[];
365
+ }> {
366
+ const result = {
367
+ success: false,
368
+ config: undefined as any,
369
+ repairs: [] as string[]
370
+ };
371
+
372
+ try {
373
+ // Create minimal valid configuration
374
+ const defaultConfig = ConfigurationManager.createDefault();
375
+ const extractedConfig = { ...defaultConfig };
376
+
377
+ // Try to extract specific fields using regex
378
+ const patterns = {
379
+ version: /"version"\s*:\s*"([^"]+)"/,
380
+ targets: /"targets"\s*:\s*(\[[^\]]*\])/,
381
+ apiKeys: /"apiKeys"\s*:\s*(\{[^}]*\})/,
382
+ automation: /"automation"\s*:\s*(\{[^}]*\})/
383
+ };
384
+
385
+ for (const [field, pattern] of Object.entries(patterns)) {
386
+ const match = content.match(pattern);
387
+ if (match && match[1]) {
388
+ try {
389
+ if (field === 'version') {
390
+ extractedConfig.version = match[1];
391
+ } else {
392
+ const parsed = JSON.parse(match[1]);
393
+ (extractedConfig as any)[field] = parsed;
394
+ }
395
+ result.repairs.push(`Extracted ${field} field`);
396
+ } catch (error) {
397
+ // Skip invalid field
398
+ }
399
+ }
400
+ }
401
+
402
+ result.config = extractedConfig;
403
+ result.success = true;
404
+ result.repairs.push('Created configuration from extracted valid parts');
405
+
406
+ } catch (error) {
407
+ logger.error('config-recovery', 'Failed to extract config parts', { error });
408
+ }
409
+
410
+ return result;
411
+ }
412
+
413
+ /**
414
+ * Repair configuration validation errors
415
+ */
416
+ private static async repairValidationErrors(config: any, errors: string[]): Promise<any> {
417
+ const repairedConfig = { ...config };
418
+ const defaultConfig = ConfigurationManager.createDefault();
419
+
420
+ for (const error of errors) {
421
+ if (error.includes('version')) {
422
+ repairedConfig.version = defaultConfig.version;
423
+ } else if (error.includes('targets')) {
424
+ repairedConfig.targets = defaultConfig.targets;
425
+ } else if (error.includes('apiKeys')) {
426
+ repairedConfig.apiKeys = defaultConfig.apiKeys;
427
+ } else if (error.includes('automation')) {
428
+ repairedConfig.automation = defaultConfig.automation;
429
+ }
430
+ }
431
+
432
+ return repairedConfig;
433
+ }
434
+
435
+ /**
436
+ * Migrate configuration to current version
437
+ */
438
+ private static async migrateConfiguration(config: any): Promise<{
439
+ migrated: boolean;
440
+ config: any;
441
+ fromVersion?: string;
442
+ toVersion: string;
443
+ }> {
444
+ const currentVersion = '1.0.0';
445
+ const configVersion = config.version || '0.0.0';
446
+
447
+ if (configVersion === currentVersion) {
448
+ return {
449
+ migrated: false,
450
+ config,
451
+ toVersion: currentVersion
452
+ };
453
+ }
454
+
455
+ // Perform migration
456
+ const migratedConfig = ConfigurationManager.migrateConfiguration(config);
457
+
458
+ return {
459
+ migrated: true,
460
+ config: migratedConfig,
461
+ fromVersion: configVersion,
462
+ toVersion: currentVersion
463
+ };
464
+ }
465
+
466
+ /**
467
+ * Create default configuration with recovery metadata
468
+ */
469
+ private static async createDefaultConfiguration(): Promise<Configuration> {
470
+ const config = ConfigurationManager.createDefault();
471
+ await ConfigurationManager.save(config);
472
+
473
+ logger.info('config-recovery', 'Created default configuration');
474
+ return config;
475
+ }
476
+
477
+ /**
478
+ * Check if configuration file exists
479
+ */
480
+ private static async configFileExists(): Promise<boolean> {
481
+ try {
482
+ await fs.access(this.CONFIG_FILE);
483
+ return true;
484
+ } catch {
485
+ return false;
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Calculate configuration checksum for integrity verification
491
+ */
492
+ private static calculateChecksum(config: Configuration): string {
493
+ const configString = JSON.stringify(config, Object.keys(config).sort());
494
+ return createHash('sha256').update(configString).digest('hex');
495
+ }
496
+
497
+ /**
498
+ * Clean up old backup files
499
+ */
500
+ private static async cleanupOldBackups(): Promise<void> {
501
+ try {
502
+ const backups = await this.listBackups();
503
+
504
+ if (backups.length > this.MAX_BACKUPS) {
505
+ const toDelete = backups.slice(this.MAX_BACKUPS);
506
+
507
+ for (const backup of toDelete) {
508
+ try {
509
+ await fs.unlink(backup.file);
510
+ logger.debug('config-recovery', `Deleted old backup: ${path.basename(backup.file)}`);
511
+ } catch (error) {
512
+ // Ignore individual deletion errors
513
+ }
514
+ }
515
+ }
516
+ } catch (error) {
517
+ logger.warn('config-recovery', 'Failed to cleanup old backups', { error: error instanceof Error ? error.message : 'Unknown error' });
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Log recovery attempt for debugging
523
+ */
524
+ private static async logRecoveryAttempt(result: any): Promise<void> {
525
+ try {
526
+ await fs.mkdir(path.dirname(this.RECOVERY_LOG), { recursive: true });
527
+
528
+ const logEntry = {
529
+ timestamp: new Date().toISOString(),
530
+ success: result.success,
531
+ errors: result.errors,
532
+ warnings: result.warnings,
533
+ backupCreated: result.backupCreated
534
+ };
535
+
536
+ await fs.appendFile(this.RECOVERY_LOG, JSON.stringify(logEntry) + '\n');
537
+ } catch (error) {
538
+ // Ignore logging errors
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Validate configuration file integrity
544
+ */
545
+ static async validateIntegrity(): Promise<{
546
+ valid: boolean;
547
+ readable: boolean;
548
+ parseable: boolean;
549
+ structureValid: boolean;
550
+ errors: string[];
551
+ suggestions: string[];
552
+ }> {
553
+ const result = {
554
+ valid: false,
555
+ readable: false,
556
+ parseable: false,
557
+ structureValid: false,
558
+ errors: [] as string[],
559
+ suggestions: [] as string[]
560
+ };
561
+
562
+ try {
563
+ // Check if file is readable
564
+ const content = await fs.readFile(this.CONFIG_FILE, 'utf-8');
565
+ result.readable = true;
566
+
567
+ // Check if content is parseable JSON
568
+ let config: any;
569
+ try {
570
+ config = JSON.parse(content);
571
+ result.parseable = true;
572
+ } catch (parseError) {
573
+ result.errors.push(`Configuration file contains invalid JSON: ${parseError instanceof Error ? parseError.message : 'Unknown error'}`);
574
+ result.suggestions.push('Run "base init" to recreate the configuration file');
575
+ return result;
576
+ }
577
+
578
+ // Check configuration structure
579
+ const validation = ConfigurationManager.validateConfiguration(config);
580
+ result.structureValid = validation.valid;
581
+
582
+ if (!validation.valid) {
583
+ result.errors.push(...validation.errors);
584
+ result.suggestions.push('Run "base config" to fix configuration issues');
585
+ }
586
+
587
+ result.valid = result.readable && result.parseable && result.structureValid;
588
+
589
+ } catch (error) {
590
+ if ((error as any).code === 'ENOENT') {
591
+ result.errors.push('Configuration file does not exist');
592
+ result.suggestions.push('Run "base init" to create a new configuration file');
593
+ } else {
594
+ result.errors.push(`Cannot read configuration file: ${error instanceof Error ? error.message : 'Unknown error'}`);
595
+ result.suggestions.push('Check file permissions and try again');
596
+ }
597
+ }
598
+
599
+ return result;
600
+ }
601
+
602
+ /**
603
+ * Interactive recovery wizard
604
+ */
605
+ static async runRecoveryWizard(): Promise<void> {
606
+ console.log(chalk.cyan('šŸ”§ BaseGuard Configuration Recovery Wizard'));
607
+ console.log(chalk.dim('This wizard will help you recover or repair your BaseGuard configuration.\n'));
608
+
609
+ // Step 1: Validate current configuration
610
+ console.log(chalk.cyan('Step 1: Validating current configuration...'));
611
+ const integrity = await this.validateIntegrity();
612
+
613
+ if (integrity.valid) {
614
+ UIComponents.showSuccessBox('Configuration is valid and healthy');
615
+ return;
616
+ }
617
+
618
+ // Step 2: Show issues
619
+ console.log(chalk.red('\nāŒ Configuration issues found:'));
620
+ integrity.errors.forEach(error => {
621
+ console.log(chalk.red(` • ${error}`));
622
+ });
623
+
624
+ console.log(chalk.cyan('\nšŸ’” Suggestions:'));
625
+ integrity.suggestions.forEach(suggestion => {
626
+ console.log(chalk.cyan(` • ${suggestion}`));
627
+ });
628
+
629
+ // Step 3: Offer recovery options
630
+ console.log(chalk.cyan('\nšŸ”„ Recovery Options:'));
631
+ console.log(chalk.cyan(' 1. Automatic repair (recommended)'));
632
+ console.log(chalk.cyan(' 2. Restore from backup'));
633
+ console.log(chalk.cyan(' 3. Create new configuration'));
634
+ console.log(chalk.cyan(' 4. Manual repair guidance'));
635
+
636
+ // For now, just run automatic repair
637
+ console.log(chalk.cyan('\nRunning automatic repair...'));
638
+
639
+ const recoveryResult = await this.recoverConfiguration({
640
+ createBackup: true,
641
+ validateConfig: true,
642
+ migrateVersion: true,
643
+ repairCorruption: true,
644
+ useDefaults: true
645
+ });
646
+
647
+ if (recoveryResult.success) {
648
+ UIComponents.showSuccessBox('Configuration recovered successfully');
649
+
650
+ if (recoveryResult.backupCreated) {
651
+ console.log(chalk.dim(`Backup created: ${recoveryResult.backupCreated}`));
652
+ }
653
+
654
+ if (recoveryResult.warnings.length > 0) {
655
+ console.log(chalk.yellow('\nāš ļø Warnings:'));
656
+ recoveryResult.warnings.forEach(warning => {
657
+ console.log(chalk.yellow(` • ${warning}`));
658
+ });
659
+ }
660
+ } else {
661
+ console.log(chalk.red('\nāŒ Recovery failed:'));
662
+ recoveryResult.errors.forEach(error => {
663
+ console.log(chalk.red(` • ${error}`));
664
+ });
665
+
666
+ console.log(chalk.cyan('\nšŸ’” Next steps:'));
667
+ console.log(chalk.cyan(' • Run "base init" to create a fresh configuration'));
668
+ console.log(chalk.cyan(' • Check file permissions in your project directory'));
669
+ console.log(chalk.cyan(' • Contact support if the issue persists'));
670
+ }
671
+ }
672
+ }