baseguard 1.0.0

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