baseguard 1.0.2 → 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 (169) 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 +628 -613
  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 +2 -0
  67. package/dist/core/startup-optimizer.d.ts.map +1 -1
  68. package/dist/core/startup-optimizer.js +19 -12
  69. package/dist/core/startup-optimizer.js.map +1 -1
  70. package/dist/core/system-error-handler.d.ts.map +1 -1
  71. package/dist/core/system-error-handler.js +18 -11
  72. package/dist/core/system-error-handler.js.map +1 -1
  73. package/dist/git/automation-engine.d.ts.map +1 -1
  74. package/dist/git/automation-engine.js +5 -4
  75. package/dist/git/automation-engine.js.map +1 -1
  76. package/dist/git/github-manager.d.ts.map +1 -1
  77. package/dist/git/github-manager.js.map +1 -1
  78. package/dist/git/hook-manager.js +5 -5
  79. package/dist/git/hook-manager.js.map +1 -1
  80. package/dist/parsers/parser-manager.d.ts.map +1 -1
  81. package/dist/parsers/parser-manager.js +1 -1
  82. package/dist/parsers/parser-manager.js.map +1 -1
  83. package/dist/parsers/svelte-parser.js +1 -1
  84. package/dist/parsers/svelte-parser.js.map +1 -1
  85. package/dist/parsers/vanilla-parser.d.ts.map +1 -1
  86. package/dist/parsers/vanilla-parser.js.map +1 -1
  87. package/dist/parsers/vue-parser.d.ts.map +1 -1
  88. package/dist/parsers/vue-parser.js.map +1 -1
  89. package/dist/ui/components.d.ts +1 -1
  90. package/dist/ui/components.d.ts.map +1 -1
  91. package/dist/ui/components.js +11 -11
  92. package/dist/ui/components.js.map +1 -1
  93. package/dist/ui/terminal-header.js +14 -14
  94. package/package.json +105 -105
  95. package/src/ai/__tests__/gemini-analyzer.test.ts +180 -180
  96. package/src/ai/agentkit-orchestrator.ts +533 -533
  97. package/src/ai/fix-manager.ts +362 -362
  98. package/src/ai/gemini-analyzer.ts +665 -671
  99. package/src/ai/gemini-code-fixer.ts +539 -540
  100. package/src/ai/index.ts +3 -3
  101. package/src/ai/jules-implementer.ts +504 -460
  102. package/src/ai/unified-code-fixer.ts +347 -347
  103. package/src/commands/automation.ts +343 -343
  104. package/src/commands/check.ts +298 -299
  105. package/src/commands/config.ts +584 -583
  106. package/src/commands/fix.ts +264 -238
  107. package/src/commands/index.ts +6 -6
  108. package/src/commands/init.ts +155 -155
  109. package/src/commands/status.ts +306 -306
  110. package/src/core/api-key-manager.ts +298 -298
  111. package/src/core/baseguard.ts +757 -756
  112. package/src/core/baseline-checker.ts +564 -563
  113. package/src/core/cache-manager.ts +271 -271
  114. package/src/core/configuration-recovery.ts +672 -673
  115. package/src/core/configuration.ts +595 -595
  116. package/src/core/debug-logger.ts +590 -590
  117. package/src/core/directory-filter.ts +420 -420
  118. package/src/core/error-handler.ts +518 -517
  119. package/src/core/file-processor.ts +337 -337
  120. package/src/core/gitignore-manager.ts +168 -168
  121. package/src/core/graceful-degradation-manager.ts +596 -596
  122. package/src/core/index.ts +16 -16
  123. package/src/core/lazy-loader.ts +317 -307
  124. package/src/core/memory-manager.ts +290 -295
  125. package/src/core/parser-worker.ts +33 -0
  126. package/src/core/startup-optimizer.ts +246 -243
  127. package/src/core/system-error-handler.ts +755 -750
  128. package/src/git/automation-engine.ts +361 -361
  129. package/src/git/github-manager.ts +190 -192
  130. package/src/git/hook-manager.ts +210 -210
  131. package/src/git/index.ts +3 -3
  132. package/src/index.ts +7 -7
  133. package/src/parsers/feature-validator.ts +558 -558
  134. package/src/parsers/index.ts +7 -7
  135. package/src/parsers/parser-manager.ts +418 -419
  136. package/src/parsers/parser.ts +25 -25
  137. package/src/parsers/react-parser-optimized.ts +160 -160
  138. package/src/parsers/react-parser.ts +358 -358
  139. package/src/parsers/svelte-parser.ts +510 -510
  140. package/src/parsers/vanilla-parser.ts +685 -686
  141. package/src/parsers/vue-parser.ts +476 -478
  142. package/src/types/index.ts +95 -95
  143. package/src/ui/components.ts +567 -567
  144. package/src/ui/help.ts +192 -192
  145. package/src/ui/index.ts +3 -3
  146. package/src/ui/prompts.ts +680 -680
  147. package/src/ui/terminal-header.ts +58 -58
  148. package/test-build.js +40 -40
  149. package/test-config-commands.js +55 -55
  150. package/test-header-simple.js +32 -32
  151. package/test-terminal-header.js +11 -11
  152. package/test-ui.js +28 -28
  153. package/tests/e2e/baseguard.e2e.test.ts +515 -515
  154. package/tests/e2e/cross-platform.e2e.test.ts +419 -419
  155. package/tests/e2e/git-integration.e2e.test.ts +486 -486
  156. package/tests/fixtures/react-project/package.json +13 -13
  157. package/tests/fixtures/react-project/src/App.css +75 -75
  158. package/tests/fixtures/react-project/src/App.tsx +76 -76
  159. package/tests/fixtures/svelte-project/package.json +10 -10
  160. package/tests/fixtures/svelte-project/src/App.svelte +368 -368
  161. package/tests/fixtures/vanilla-project/index.html +75 -75
  162. package/tests/fixtures/vanilla-project/script.js +330 -330
  163. package/tests/fixtures/vanilla-project/styles.css +358 -358
  164. package/tests/fixtures/vue-project/package.json +11 -11
  165. package/tests/fixtures/vue-project/src/App.vue +215 -215
  166. package/tsconfig.json +34 -34
  167. package/vitest.config.ts +11 -11
  168. package/dist/terminal-header.d.ts +0 -12
  169. 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
+ }