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,596 +1,596 @@
1
- import { readFile, writeFile, access } from 'fs/promises';
2
- import { constants } from 'fs';
3
- import { GitignoreManager } from './gitignore-manager.js';
4
- import { UIComponents } from '../ui/components.js';
5
- import type { Configuration, BrowserTarget } from '../types/index.js';
6
-
7
- // Preset browser target configurations
8
- export const BROWSER_TARGET_PRESETS = {
9
- 'baseline-widely': [
10
- { browser: 'chrome', minVersion: 'baseline' },
11
- { browser: 'firefox', minVersion: 'baseline' },
12
- { browser: 'safari', minVersion: 'baseline' },
13
- { browser: 'edge', minVersion: 'baseline' }
14
- ] as BrowserTarget[],
15
- 'baseline-newly': [
16
- { browser: 'chrome', minVersion: 'baseline-newly' },
17
- { browser: 'firefox', minVersion: 'baseline-newly' },
18
- { browser: 'safari', minVersion: 'baseline-newly' },
19
- { browser: 'edge', minVersion: 'baseline-newly' }
20
- ] as BrowserTarget[],
21
- 'last-2-years': [
22
- { browser: 'chrome', minVersion: '100' },
23
- { browser: 'firefox', minVersion: '100' },
24
- { browser: 'safari', minVersion: '15' },
25
- { browser: 'edge', minVersion: '100' }
26
- ] as BrowserTarget[]
27
- };
28
-
29
- export type PresetName = 'baseline-widely' | 'baseline-newly' | 'last-2-years';
30
-
31
- /**
32
- * Configuration manager for BaseGuard settings
33
- */
34
- export class ConfigurationManager {
35
- private static readonly CONFIG_FILE = '.baseguardrc.json';
36
-
37
- /**
38
- * Load configuration from file or create default
39
- */
40
- static async load(): Promise<Configuration> {
41
- try {
42
- await access(this.CONFIG_FILE, constants.F_OK);
43
- const content = await readFile(this.CONFIG_FILE, 'utf-8');
44
- const config = JSON.parse(content) as Configuration;
45
-
46
- // Validate and migrate configuration if needed
47
- return this.validateAndMigrate(config);
48
- } catch (error) {
49
- // File doesn't exist or is invalid, return default
50
- return this.createDefault();
51
- }
52
- }
53
-
54
- /**
55
- * Save configuration to file
56
- */
57
- static async save(config: Configuration): Promise<void> {
58
- const validatedConfig = this.validateAndMigrate(config);
59
- const content = JSON.stringify(validatedConfig, null, 2);
60
- await writeFile(this.CONFIG_FILE, content, 'utf-8');
61
-
62
- // Ensure config file is in .gitignore for security
63
- const gitignoreUpdated = await GitignoreManager.ensureConfigIgnored();
64
- if (gitignoreUpdated) {
65
- UIComponents.showInfoBox('Added .baseguardrc.json to .gitignore for security');
66
- }
67
- }
68
-
69
- /**
70
- * Create default configuration
71
- */
72
- static createDefault(): Configuration {
73
- return {
74
- version: '1.0.0',
75
- targets: BROWSER_TARGET_PRESETS['baseline-widely'],
76
- apiKeys: {
77
- jules: null,
78
- gemini: null
79
- },
80
- codingAgent: {
81
- primary: 'gemini', // 'jules' or 'gemini'
82
- fallback: 'gemini' // fallback when primary fails
83
- },
84
- automation: {
85
- enabled: false,
86
- trigger: 'pre-commit',
87
- autoAnalyze: true,
88
- autoFix: false,
89
- blockCommit: true
90
- }
91
- };
92
- }
93
-
94
- /**
95
- * Create configuration with preset browser targets
96
- */
97
- static createWithPreset(preset: PresetName): Configuration {
98
- const config = this.createDefault();
99
- config.targets = [...BROWSER_TARGET_PRESETS[preset]];
100
- return config;
101
- }
102
-
103
- /**
104
- * Create configuration with custom browser targets
105
- */
106
- static createWithCustomTargets(targets: BrowserTarget[]): Configuration {
107
- const config = this.createDefault();
108
- config.targets = this.validateBrowserTargets(targets);
109
- return config;
110
- }
111
-
112
- /**
113
- * Validate and migrate configuration
114
- */
115
- private static validateAndMigrate(config: any): Configuration {
116
- const defaultConfig = this.createDefault();
117
-
118
- // Ensure all required fields exist
119
- const validatedConfig: Configuration = {
120
- version: config.version || defaultConfig.version,
121
- targets: this.validateBrowserTargets(config.targets || defaultConfig.targets),
122
- apiKeys: {
123
- jules: config.apiKeys?.jules || null,
124
- gemini: config.apiKeys?.gemini || null
125
- },
126
- codingAgent: {
127
- primary: this.validateCodingAgent(config.codingAgent?.primary) || defaultConfig.codingAgent.primary,
128
- fallback: this.validateCodingAgent(config.codingAgent?.fallback) || defaultConfig.codingAgent.fallback
129
- },
130
- automation: {
131
- enabled: config.automation?.enabled ?? defaultConfig.automation.enabled,
132
- trigger: this.validateTrigger(config.automation?.trigger) || defaultConfig.automation.trigger,
133
- autoAnalyze: config.automation?.autoAnalyze ?? defaultConfig.automation.autoAnalyze,
134
- autoFix: config.automation?.autoFix ?? defaultConfig.automation.autoFix,
135
- blockCommit: config.automation?.blockCommit ?? defaultConfig.automation.blockCommit
136
- }
137
- };
138
-
139
- return validatedConfig;
140
- }
141
-
142
- /**
143
- * Validate browser targets
144
- */
145
- private static validateBrowserTargets(targets: any[]): BrowserTarget[] {
146
- if (!Array.isArray(targets) || targets.length === 0) {
147
- return BROWSER_TARGET_PRESETS['baseline-widely'];
148
- }
149
-
150
- const validTargets: BrowserTarget[] = [];
151
- const supportedBrowsers = ['chrome', 'firefox', 'safari', 'edge', 'opera', 'samsung'];
152
-
153
- for (const target of targets) {
154
- if (typeof target !== 'object' || !target.browser || !target.minVersion) {
155
- continue;
156
- }
157
-
158
- const browser = target.browser.toLowerCase();
159
- if (!supportedBrowsers.includes(browser)) {
160
- continue;
161
- }
162
-
163
- const minVersion = this.validateMinVersion(target.minVersion);
164
- if (!minVersion) {
165
- continue;
166
- }
167
-
168
- validTargets.push({ browser, minVersion });
169
- }
170
-
171
- return validTargets.length > 0 ? validTargets : BROWSER_TARGET_PRESETS['baseline-widely'];
172
- }
173
-
174
- /**
175
- * Validate minimum version
176
- */
177
- private static validateMinVersion(minVersion: any): string | null {
178
- if (typeof minVersion !== 'string') {
179
- return null;
180
- }
181
-
182
- // Special baseline keywords
183
- if (minVersion === 'baseline' || minVersion === 'baseline-newly') {
184
- return minVersion;
185
- }
186
-
187
- // Version number validation (basic)
188
- if (/^\d+(\.\d+)*$/.test(minVersion)) {
189
- return minVersion;
190
- }
191
-
192
- return null;
193
- }
194
-
195
- /**
196
- * Validate automation trigger
197
- */
198
- private static validateTrigger(trigger: any): 'pre-commit' | 'pre-push' | null {
199
- if (trigger === 'pre-commit' || trigger === 'pre-push') {
200
- return trigger;
201
- }
202
- return null;
203
- }
204
-
205
- /**
206
- * Validate coding agent selection
207
- */
208
- private static validateCodingAgent(agent: any): 'jules' | 'gemini' | null {
209
- if (agent === 'jules' || agent === 'gemini') {
210
- return agent;
211
- }
212
- return null;
213
- }
214
-
215
- /**
216
- * Parse browser target string (e.g., "chrome 100", "safari baseline")
217
- */
218
- static parseBrowserTarget(targetString: string): BrowserTarget | null {
219
- const parts = targetString.trim().toLowerCase().split(/\s+/);
220
-
221
- if (parts.length !== 2) {
222
- return null;
223
- }
224
-
225
- const browser = parts[0];
226
- const version = parts[1];
227
-
228
- if (!browser || !version) {
229
- return null;
230
- }
231
-
232
- const supportedBrowsers = ['chrome', 'firefox', 'safari', 'edge', 'opera', 'samsung'];
233
-
234
- if (!supportedBrowsers.includes(browser)) {
235
- return null;
236
- }
237
-
238
- const minVersion = this.validateMinVersion(version);
239
- if (!minVersion) {
240
- return null;
241
- }
242
-
243
- return { browser, minVersion };
244
- }
245
-
246
- /**
247
- * Parse multiple browser targets from strings
248
- */
249
- static parseBrowserTargets(targetStrings: string[]): BrowserTarget[] {
250
- const targets: BrowserTarget[] = [];
251
-
252
- for (const targetString of targetStrings) {
253
- const target = this.parseBrowserTarget(targetString);
254
- if (target) {
255
- targets.push(target);
256
- }
257
- }
258
-
259
- return targets;
260
- }
261
-
262
- /**
263
- * Get available preset names
264
- */
265
- static getAvailablePresets(): PresetName[] {
266
- return Object.keys(BROWSER_TARGET_PRESETS) as PresetName[];
267
- }
268
-
269
- /**
270
- * Get preset description
271
- */
272
- static getPresetDescription(preset: PresetName): string {
273
- switch (preset) {
274
- case 'baseline-widely':
275
- return 'Features supported across all major browsers for 30+ months (Baseline Widely Available)';
276
- case 'baseline-newly':
277
- return 'Features newly available across all major browsers (Baseline Newly Available)';
278
- case 'last-2-years':
279
- return 'Browser versions from the last 2 years (Chrome 100+, Firefox 100+, Safari 15+, Edge 100+)';
280
- default:
281
- return 'Unknown preset';
282
- }
283
- }
284
-
285
- /**
286
- * Check if configuration file exists
287
- */
288
- static async exists(): Promise<boolean> {
289
- try {
290
- await access(this.CONFIG_FILE, constants.F_OK);
291
- return true;
292
- } catch {
293
- return false;
294
- }
295
- }
296
-
297
- /**
298
- * Get configuration file path
299
- */
300
- static getConfigFilePath(): string {
301
- return this.CONFIG_FILE;
302
- }
303
-
304
- /**
305
- * Add browser target to existing configuration
306
- */
307
- static async addBrowserTarget(target: BrowserTarget): Promise<void> {
308
- const config = await this.load();
309
-
310
- // Remove existing target for the same browser
311
- config.targets = config.targets.filter(t => t.browser !== target.browser);
312
-
313
- // Add new target
314
- config.targets.push(target);
315
-
316
- await this.save(config);
317
- }
318
-
319
- /**
320
- * Remove browser target from existing configuration
321
- */
322
- static async removeBrowserTarget(browser: string): Promise<void> {
323
- const config = await this.load();
324
- config.targets = config.targets.filter(t => t.browser !== browser.toLowerCase());
325
-
326
- // Ensure at least one target remains
327
- if (config.targets.length === 0) {
328
- config.targets = BROWSER_TARGET_PRESETS['baseline-widely'];
329
- }
330
-
331
- await this.save(config);
332
- }
333
-
334
- /**
335
- * Update browser targets with preset
336
- */
337
- static async updateWithPreset(preset: PresetName): Promise<void> {
338
- const config = await this.load();
339
- config.targets = [...BROWSER_TARGET_PRESETS[preset]];
340
- await this.save(config);
341
- }
342
-
343
- /**
344
- * Update browser targets with custom targets
345
- */
346
- static async updateWithCustomTargets(targets: BrowserTarget[]): Promise<void> {
347
- const config = await this.load();
348
- config.targets = this.validateBrowserTargets(targets);
349
- await this.save(config);
350
- }
351
-
352
- /**
353
- * Update automation configuration
354
- */
355
- static async updateAutomation(automationConfig: Partial<Configuration['automation']>): Promise<void> {
356
- const config = await this.load();
357
-
358
- config.automation = {
359
- ...config.automation,
360
- ...automationConfig
361
- };
362
-
363
- // Validate trigger
364
- if (automationConfig.trigger) {
365
- const validatedTrigger = this.validateTrigger(automationConfig.trigger);
366
- if (validatedTrigger) {
367
- config.automation.trigger = validatedTrigger;
368
- }
369
- }
370
-
371
- await this.save(config);
372
- }
373
-
374
- /**
375
- * Enable automation
376
- */
377
- static async enableAutomation(trigger?: 'pre-commit' | 'pre-push'): Promise<void> {
378
- const config = await this.load();
379
- config.automation.enabled = true;
380
-
381
- if (trigger) {
382
- config.automation.trigger = trigger;
383
- }
384
-
385
- await this.save(config);
386
- }
387
-
388
- /**
389
- * Disable automation
390
- */
391
- static async disableAutomation(): Promise<void> {
392
- const config = await this.load();
393
- config.automation.enabled = false;
394
- await this.save(config);
395
- }
396
-
397
- /**
398
- * Update API keys
399
- */
400
- static async updateApiKeys(apiKeys: Partial<Configuration['apiKeys']>): Promise<void> {
401
- const config = await this.load();
402
-
403
- config.apiKeys = {
404
- ...config.apiKeys,
405
- ...apiKeys
406
- };
407
-
408
- await this.save(config);
409
- }
410
-
411
- /**
412
- * Validate configuration structure and data
413
- */
414
- static validateConfiguration(config: any): { valid: boolean; errors: string[] } {
415
- const errors: string[] = [];
416
-
417
- // Check required fields
418
- if (!config || typeof config !== 'object') {
419
- errors.push('Configuration must be an object');
420
- return { valid: false, errors };
421
- }
422
-
423
- // Validate version
424
- if (!config.version || typeof config.version !== 'string') {
425
- errors.push('Configuration version is required and must be a string');
426
- }
427
-
428
- // Validate targets
429
- if (!Array.isArray(config.targets)) {
430
- errors.push('Browser targets must be an array');
431
- } else {
432
- config.targets.forEach((target: any, index: number) => {
433
- if (!target || typeof target !== 'object') {
434
- errors.push(`Target ${index} must be an object`);
435
- return;
436
- }
437
- if (!target.browser || typeof target.browser !== 'string') {
438
- errors.push(`Target ${index} must have a valid browser string`);
439
- }
440
- if (!target.minVersion || typeof target.minVersion !== 'string') {
441
- errors.push(`Target ${index} must have a valid minVersion string`);
442
- }
443
- });
444
- }
445
-
446
- // Validate API keys
447
- if (!config.apiKeys || typeof config.apiKeys !== 'object') {
448
- errors.push('API keys configuration must be an object');
449
- } else {
450
- if (config.apiKeys.jules !== null && typeof config.apiKeys.jules !== 'string') {
451
- errors.push('Jules API key must be a string or null');
452
- }
453
- if (config.apiKeys.gemini !== null && typeof config.apiKeys.gemini !== 'string') {
454
- errors.push('Gemini API key must be a string or null');
455
- }
456
- }
457
-
458
- // Validate coding agent
459
- if (!config.codingAgent || typeof config.codingAgent !== 'object') {
460
- errors.push('Coding agent configuration must be an object');
461
- } else {
462
- if (config.codingAgent.primary !== 'jules' && config.codingAgent.primary !== 'gemini') {
463
- errors.push('Primary coding agent must be "jules" or "gemini"');
464
- }
465
- if (config.codingAgent.fallback !== 'jules' && config.codingAgent.fallback !== 'gemini') {
466
- errors.push('Fallback coding agent must be "jules" or "gemini"');
467
- }
468
- }
469
-
470
- // Validate automation
471
- if (!config.automation || typeof config.automation !== 'object') {
472
- errors.push('Automation configuration must be an object');
473
- } else {
474
- const automation = config.automation;
475
- if (typeof automation.enabled !== 'boolean') {
476
- errors.push('Automation enabled must be a boolean');
477
- }
478
- if (automation.trigger !== 'pre-commit' && automation.trigger !== 'pre-push') {
479
- errors.push('Automation trigger must be "pre-commit" or "pre-push"');
480
- }
481
- if (typeof automation.autoAnalyze !== 'boolean') {
482
- errors.push('Automation autoAnalyze must be a boolean');
483
- }
484
- if (typeof automation.autoFix !== 'boolean') {
485
- errors.push('Automation autoFix must be a boolean');
486
- }
487
- if (typeof automation.blockCommit !== 'boolean') {
488
- errors.push('Automation blockCommit must be a boolean');
489
- }
490
- }
491
-
492
- return { valid: errors.length === 0, errors };
493
- }
494
-
495
- /**
496
- * Migrate configuration from older versions
497
- */
498
- static migrateConfiguration(config: any): Configuration {
499
- // Handle migration from version 0.x to 1.x
500
- if (!config.version || config.version.startsWith('0.')) {
501
- // Migrate old structure to new structure
502
- const migratedConfig = this.createDefault();
503
-
504
- // Preserve existing settings where possible
505
- if (config.targets && Array.isArray(config.targets)) {
506
- migratedConfig.targets = this.validateBrowserTargets(config.targets);
507
- }
508
-
509
- if (config.apiKeys) {
510
- migratedConfig.apiKeys.jules = config.apiKeys.jules || null;
511
- migratedConfig.apiKeys.gemini = config.apiKeys.gemini || null;
512
- }
513
-
514
- if (config.codingAgent) {
515
- migratedConfig.codingAgent = {
516
- primary: this.validateCodingAgent(config.codingAgent.primary) || 'gemini',
517
- fallback: this.validateCodingAgent(config.codingAgent.fallback) || 'gemini'
518
- };
519
- }
520
-
521
- if (config.automation) {
522
- migratedConfig.automation = {
523
- ...migratedConfig.automation,
524
- ...config.automation
525
- };
526
- }
527
-
528
- migratedConfig.version = '1.0.0';
529
- return migratedConfig;
530
- }
531
-
532
- return config as Configuration;
533
- }
534
-
535
- /**
536
- * Get configuration display information
537
- */
538
- static async getConfigurationDisplay(): Promise<{
539
- config: Configuration;
540
- security: {
541
- gitignoreExists: boolean;
542
- configIgnored: boolean;
543
- recommendations: string[];
544
- };
545
- validation: {
546
- valid: boolean;
547
- errors: string[];
548
- };
549
- }> {
550
- const config = await this.load();
551
- const security = await GitignoreManager.isConfigSecure();
552
- const validation = this.validateConfiguration(config);
553
-
554
- return {
555
- config,
556
- security,
557
- validation
558
- };
559
- }
560
-
561
- /**
562
- * Backup current configuration
563
- */
564
- static async backupConfiguration(): Promise<string> {
565
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
566
- const backupFile = `.baseguardrc.backup.${timestamp}.json`;
567
-
568
- try {
569
- const config = await this.load();
570
- const content = JSON.stringify(config, null, 2);
571
- await writeFile(backupFile, content, 'utf-8');
572
- return backupFile;
573
- } catch (error) {
574
- throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
575
- }
576
- }
577
-
578
- /**
579
- * Restore configuration from backup
580
- */
581
- static async restoreConfiguration(backupFile: string): Promise<void> {
582
- try {
583
- const content = await readFile(backupFile, 'utf-8');
584
- const config = JSON.parse(content);
585
-
586
- const validation = this.validateConfiguration(config);
587
- if (!validation.valid) {
588
- throw new Error(`Invalid backup configuration: ${validation.errors.join(', ')}`);
589
- }
590
-
591
- await this.save(config);
592
- } catch (error) {
593
- throw new Error(`Failed to restore backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
594
- }
595
- }
1
+ import { readFile, writeFile, access } from 'fs/promises';
2
+ import { constants } from 'fs';
3
+ import { GitignoreManager } from './gitignore-manager.js';
4
+ import { UIComponents } from '../ui/components.js';
5
+ import type { Configuration, BrowserTarget } from '../types/index.js';
6
+
7
+ // Preset browser target configurations
8
+ export const BROWSER_TARGET_PRESETS = {
9
+ 'baseline-widely': [
10
+ { browser: 'chrome', minVersion: 'baseline' },
11
+ { browser: 'firefox', minVersion: 'baseline' },
12
+ { browser: 'safari', minVersion: 'baseline' },
13
+ { browser: 'edge', minVersion: 'baseline' }
14
+ ] as BrowserTarget[],
15
+ 'baseline-newly': [
16
+ { browser: 'chrome', minVersion: 'baseline-newly' },
17
+ { browser: 'firefox', minVersion: 'baseline-newly' },
18
+ { browser: 'safari', minVersion: 'baseline-newly' },
19
+ { browser: 'edge', minVersion: 'baseline-newly' }
20
+ ] as BrowserTarget[],
21
+ 'last-2-years': [
22
+ { browser: 'chrome', minVersion: '100' },
23
+ { browser: 'firefox', minVersion: '100' },
24
+ { browser: 'safari', minVersion: '15' },
25
+ { browser: 'edge', minVersion: '100' }
26
+ ] as BrowserTarget[]
27
+ };
28
+
29
+ export type PresetName = 'baseline-widely' | 'baseline-newly' | 'last-2-years';
30
+
31
+ /**
32
+ * Configuration manager for BaseGuard settings
33
+ */
34
+ export class ConfigurationManager {
35
+ private static readonly CONFIG_FILE = '.baseguardrc.json';
36
+
37
+ /**
38
+ * Load configuration from file or create default
39
+ */
40
+ static async load(): Promise<Configuration> {
41
+ try {
42
+ await access(this.CONFIG_FILE, constants.F_OK);
43
+ const content = await readFile(this.CONFIG_FILE, 'utf-8');
44
+ const config = JSON.parse(content) as Configuration;
45
+
46
+ // Validate and migrate configuration if needed
47
+ return this.validateAndMigrate(config);
48
+ } catch (error) {
49
+ // File doesn't exist or is invalid, return default
50
+ return this.createDefault();
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Save configuration to file
56
+ */
57
+ static async save(config: Configuration): Promise<void> {
58
+ const validatedConfig = this.validateAndMigrate(config);
59
+ const content = JSON.stringify(validatedConfig, null, 2);
60
+ await writeFile(this.CONFIG_FILE, content, 'utf-8');
61
+
62
+ // Ensure config file is in .gitignore for security
63
+ const gitignoreUpdated = await GitignoreManager.ensureConfigIgnored();
64
+ if (gitignoreUpdated) {
65
+ UIComponents.showInfoBox('Added .baseguardrc.json to .gitignore for security');
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Create default configuration
71
+ */
72
+ static createDefault(): Configuration {
73
+ return {
74
+ version: '1.0.0',
75
+ targets: BROWSER_TARGET_PRESETS['baseline-widely'],
76
+ apiKeys: {
77
+ jules: null,
78
+ gemini: null
79
+ },
80
+ codingAgent: {
81
+ primary: 'gemini', // 'jules' or 'gemini'
82
+ fallback: 'gemini' // fallback when primary fails
83
+ },
84
+ automation: {
85
+ enabled: false,
86
+ trigger: 'pre-commit',
87
+ autoAnalyze: true,
88
+ autoFix: false,
89
+ blockCommit: true
90
+ }
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Create configuration with preset browser targets
96
+ */
97
+ static createWithPreset(preset: PresetName): Configuration {
98
+ const config = this.createDefault();
99
+ config.targets = [...BROWSER_TARGET_PRESETS[preset]];
100
+ return config;
101
+ }
102
+
103
+ /**
104
+ * Create configuration with custom browser targets
105
+ */
106
+ static createWithCustomTargets(targets: BrowserTarget[]): Configuration {
107
+ const config = this.createDefault();
108
+ config.targets = this.validateBrowserTargets(targets);
109
+ return config;
110
+ }
111
+
112
+ /**
113
+ * Validate and migrate configuration
114
+ */
115
+ private static validateAndMigrate(config: any): Configuration {
116
+ const defaultConfig = this.createDefault();
117
+
118
+ // Ensure all required fields exist
119
+ const validatedConfig: Configuration = {
120
+ version: config.version || defaultConfig.version,
121
+ targets: this.validateBrowserTargets(config.targets || defaultConfig.targets),
122
+ apiKeys: {
123
+ jules: config.apiKeys?.jules || null,
124
+ gemini: config.apiKeys?.gemini || null
125
+ },
126
+ codingAgent: {
127
+ primary: this.validateCodingAgent(config.codingAgent?.primary) || defaultConfig.codingAgent.primary,
128
+ fallback: this.validateCodingAgent(config.codingAgent?.fallback) || defaultConfig.codingAgent.fallback
129
+ },
130
+ automation: {
131
+ enabled: config.automation?.enabled ?? defaultConfig.automation.enabled,
132
+ trigger: this.validateTrigger(config.automation?.trigger) || defaultConfig.automation.trigger,
133
+ autoAnalyze: config.automation?.autoAnalyze ?? defaultConfig.automation.autoAnalyze,
134
+ autoFix: config.automation?.autoFix ?? defaultConfig.automation.autoFix,
135
+ blockCommit: config.automation?.blockCommit ?? defaultConfig.automation.blockCommit
136
+ }
137
+ };
138
+
139
+ return validatedConfig;
140
+ }
141
+
142
+ /**
143
+ * Validate browser targets
144
+ */
145
+ private static validateBrowserTargets(targets: any[]): BrowserTarget[] {
146
+ if (!Array.isArray(targets) || targets.length === 0) {
147
+ return BROWSER_TARGET_PRESETS['baseline-widely'];
148
+ }
149
+
150
+ const validTargets: BrowserTarget[] = [];
151
+ const supportedBrowsers = ['chrome', 'firefox', 'safari', 'edge', 'opera', 'samsung'];
152
+
153
+ for (const target of targets) {
154
+ if (typeof target !== 'object' || !target.browser || !target.minVersion) {
155
+ continue;
156
+ }
157
+
158
+ const browser = target.browser.toLowerCase();
159
+ if (!supportedBrowsers.includes(browser)) {
160
+ continue;
161
+ }
162
+
163
+ const minVersion = this.validateMinVersion(target.minVersion);
164
+ if (!minVersion) {
165
+ continue;
166
+ }
167
+
168
+ validTargets.push({ browser, minVersion });
169
+ }
170
+
171
+ return validTargets.length > 0 ? validTargets : BROWSER_TARGET_PRESETS['baseline-widely'];
172
+ }
173
+
174
+ /**
175
+ * Validate minimum version
176
+ */
177
+ private static validateMinVersion(minVersion: any): string | null {
178
+ if (typeof minVersion !== 'string') {
179
+ return null;
180
+ }
181
+
182
+ // Special baseline keywords
183
+ if (minVersion === 'baseline' || minVersion === 'baseline-newly') {
184
+ return minVersion;
185
+ }
186
+
187
+ // Version number validation (basic)
188
+ if (/^\d+(\.\d+)*$/.test(minVersion)) {
189
+ return minVersion;
190
+ }
191
+
192
+ return null;
193
+ }
194
+
195
+ /**
196
+ * Validate automation trigger
197
+ */
198
+ private static validateTrigger(trigger: any): 'pre-commit' | 'pre-push' | null {
199
+ if (trigger === 'pre-commit' || trigger === 'pre-push') {
200
+ return trigger;
201
+ }
202
+ return null;
203
+ }
204
+
205
+ /**
206
+ * Validate coding agent selection
207
+ */
208
+ private static validateCodingAgent(agent: any): 'jules' | 'gemini' | null {
209
+ if (agent === 'jules' || agent === 'gemini') {
210
+ return agent;
211
+ }
212
+ return null;
213
+ }
214
+
215
+ /**
216
+ * Parse browser target string (e.g., "chrome 100", "safari baseline")
217
+ */
218
+ static parseBrowserTarget(targetString: string): BrowserTarget | null {
219
+ const parts = targetString.trim().toLowerCase().split(/\s+/);
220
+
221
+ if (parts.length !== 2) {
222
+ return null;
223
+ }
224
+
225
+ const browser = parts[0];
226
+ const version = parts[1];
227
+
228
+ if (!browser || !version) {
229
+ return null;
230
+ }
231
+
232
+ const supportedBrowsers = ['chrome', 'firefox', 'safari', 'edge', 'opera', 'samsung'];
233
+
234
+ if (!supportedBrowsers.includes(browser)) {
235
+ return null;
236
+ }
237
+
238
+ const minVersion = this.validateMinVersion(version);
239
+ if (!minVersion) {
240
+ return null;
241
+ }
242
+
243
+ return { browser, minVersion };
244
+ }
245
+
246
+ /**
247
+ * Parse multiple browser targets from strings
248
+ */
249
+ static parseBrowserTargets(targetStrings: string[]): BrowserTarget[] {
250
+ const targets: BrowserTarget[] = [];
251
+
252
+ for (const targetString of targetStrings) {
253
+ const target = this.parseBrowserTarget(targetString);
254
+ if (target) {
255
+ targets.push(target);
256
+ }
257
+ }
258
+
259
+ return targets;
260
+ }
261
+
262
+ /**
263
+ * Get available preset names
264
+ */
265
+ static getAvailablePresets(): PresetName[] {
266
+ return Object.keys(BROWSER_TARGET_PRESETS) as PresetName[];
267
+ }
268
+
269
+ /**
270
+ * Get preset description
271
+ */
272
+ static getPresetDescription(preset: PresetName): string {
273
+ switch (preset) {
274
+ case 'baseline-widely':
275
+ return 'Features supported across all major browsers for 30+ months (Baseline Widely Available)';
276
+ case 'baseline-newly':
277
+ return 'Features newly available across all major browsers (Baseline Newly Available)';
278
+ case 'last-2-years':
279
+ return 'Browser versions from the last 2 years (Chrome 100+, Firefox 100+, Safari 15+, Edge 100+)';
280
+ default:
281
+ return 'Unknown preset';
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Check if configuration file exists
287
+ */
288
+ static async exists(): Promise<boolean> {
289
+ try {
290
+ await access(this.CONFIG_FILE, constants.F_OK);
291
+ return true;
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Get configuration file path
299
+ */
300
+ static getConfigFilePath(): string {
301
+ return this.CONFIG_FILE;
302
+ }
303
+
304
+ /**
305
+ * Add browser target to existing configuration
306
+ */
307
+ static async addBrowserTarget(target: BrowserTarget): Promise<void> {
308
+ const config = await this.load();
309
+
310
+ // Remove existing target for the same browser
311
+ config.targets = config.targets.filter(t => t.browser !== target.browser);
312
+
313
+ // Add new target
314
+ config.targets.push(target);
315
+
316
+ await this.save(config);
317
+ }
318
+
319
+ /**
320
+ * Remove browser target from existing configuration
321
+ */
322
+ static async removeBrowserTarget(browser: string): Promise<void> {
323
+ const config = await this.load();
324
+ config.targets = config.targets.filter(t => t.browser !== browser.toLowerCase());
325
+
326
+ // Ensure at least one target remains
327
+ if (config.targets.length === 0) {
328
+ config.targets = BROWSER_TARGET_PRESETS['baseline-widely'];
329
+ }
330
+
331
+ await this.save(config);
332
+ }
333
+
334
+ /**
335
+ * Update browser targets with preset
336
+ */
337
+ static async updateWithPreset(preset: PresetName): Promise<void> {
338
+ const config = await this.load();
339
+ config.targets = [...BROWSER_TARGET_PRESETS[preset]];
340
+ await this.save(config);
341
+ }
342
+
343
+ /**
344
+ * Update browser targets with custom targets
345
+ */
346
+ static async updateWithCustomTargets(targets: BrowserTarget[]): Promise<void> {
347
+ const config = await this.load();
348
+ config.targets = this.validateBrowserTargets(targets);
349
+ await this.save(config);
350
+ }
351
+
352
+ /**
353
+ * Update automation configuration
354
+ */
355
+ static async updateAutomation(automationConfig: Partial<Configuration['automation']>): Promise<void> {
356
+ const config = await this.load();
357
+
358
+ config.automation = {
359
+ ...config.automation,
360
+ ...automationConfig
361
+ };
362
+
363
+ // Validate trigger
364
+ if (automationConfig.trigger) {
365
+ const validatedTrigger = this.validateTrigger(automationConfig.trigger);
366
+ if (validatedTrigger) {
367
+ config.automation.trigger = validatedTrigger;
368
+ }
369
+ }
370
+
371
+ await this.save(config);
372
+ }
373
+
374
+ /**
375
+ * Enable automation
376
+ */
377
+ static async enableAutomation(trigger?: 'pre-commit' | 'pre-push'): Promise<void> {
378
+ const config = await this.load();
379
+ config.automation.enabled = true;
380
+
381
+ if (trigger) {
382
+ config.automation.trigger = trigger;
383
+ }
384
+
385
+ await this.save(config);
386
+ }
387
+
388
+ /**
389
+ * Disable automation
390
+ */
391
+ static async disableAutomation(): Promise<void> {
392
+ const config = await this.load();
393
+ config.automation.enabled = false;
394
+ await this.save(config);
395
+ }
396
+
397
+ /**
398
+ * Update API keys
399
+ */
400
+ static async updateApiKeys(apiKeys: Partial<Configuration['apiKeys']>): Promise<void> {
401
+ const config = await this.load();
402
+
403
+ config.apiKeys = {
404
+ ...config.apiKeys,
405
+ ...apiKeys
406
+ };
407
+
408
+ await this.save(config);
409
+ }
410
+
411
+ /**
412
+ * Validate configuration structure and data
413
+ */
414
+ static validateConfiguration(config: any): { valid: boolean; errors: string[] } {
415
+ const errors: string[] = [];
416
+
417
+ // Check required fields
418
+ if (!config || typeof config !== 'object') {
419
+ errors.push('Configuration must be an object');
420
+ return { valid: false, errors };
421
+ }
422
+
423
+ // Validate version
424
+ if (!config.version || typeof config.version !== 'string') {
425
+ errors.push('Configuration version is required and must be a string');
426
+ }
427
+
428
+ // Validate targets
429
+ if (!Array.isArray(config.targets)) {
430
+ errors.push('Browser targets must be an array');
431
+ } else {
432
+ config.targets.forEach((target: any, index: number) => {
433
+ if (!target || typeof target !== 'object') {
434
+ errors.push(`Target ${index} must be an object`);
435
+ return;
436
+ }
437
+ if (!target.browser || typeof target.browser !== 'string') {
438
+ errors.push(`Target ${index} must have a valid browser string`);
439
+ }
440
+ if (!target.minVersion || typeof target.minVersion !== 'string') {
441
+ errors.push(`Target ${index} must have a valid minVersion string`);
442
+ }
443
+ });
444
+ }
445
+
446
+ // Validate API keys
447
+ if (!config.apiKeys || typeof config.apiKeys !== 'object') {
448
+ errors.push('API keys configuration must be an object');
449
+ } else {
450
+ if (config.apiKeys.jules !== null && typeof config.apiKeys.jules !== 'string') {
451
+ errors.push('Jules API key must be a string or null');
452
+ }
453
+ if (config.apiKeys.gemini !== null && typeof config.apiKeys.gemini !== 'string') {
454
+ errors.push('Gemini API key must be a string or null');
455
+ }
456
+ }
457
+
458
+ // Validate coding agent
459
+ if (!config.codingAgent || typeof config.codingAgent !== 'object') {
460
+ errors.push('Coding agent configuration must be an object');
461
+ } else {
462
+ if (config.codingAgent.primary !== 'jules' && config.codingAgent.primary !== 'gemini') {
463
+ errors.push('Primary coding agent must be "jules" or "gemini"');
464
+ }
465
+ if (config.codingAgent.fallback !== 'jules' && config.codingAgent.fallback !== 'gemini') {
466
+ errors.push('Fallback coding agent must be "jules" or "gemini"');
467
+ }
468
+ }
469
+
470
+ // Validate automation
471
+ if (!config.automation || typeof config.automation !== 'object') {
472
+ errors.push('Automation configuration must be an object');
473
+ } else {
474
+ const automation = config.automation;
475
+ if (typeof automation.enabled !== 'boolean') {
476
+ errors.push('Automation enabled must be a boolean');
477
+ }
478
+ if (automation.trigger !== 'pre-commit' && automation.trigger !== 'pre-push') {
479
+ errors.push('Automation trigger must be "pre-commit" or "pre-push"');
480
+ }
481
+ if (typeof automation.autoAnalyze !== 'boolean') {
482
+ errors.push('Automation autoAnalyze must be a boolean');
483
+ }
484
+ if (typeof automation.autoFix !== 'boolean') {
485
+ errors.push('Automation autoFix must be a boolean');
486
+ }
487
+ if (typeof automation.blockCommit !== 'boolean') {
488
+ errors.push('Automation blockCommit must be a boolean');
489
+ }
490
+ }
491
+
492
+ return { valid: errors.length === 0, errors };
493
+ }
494
+
495
+ /**
496
+ * Migrate configuration from older versions
497
+ */
498
+ static migrateConfiguration(config: any): Configuration {
499
+ // Handle migration from version 0.x to 1.x
500
+ if (!config.version || config.version.startsWith('0.')) {
501
+ // Migrate old structure to new structure
502
+ const migratedConfig = this.createDefault();
503
+
504
+ // Preserve existing settings where possible
505
+ if (config.targets && Array.isArray(config.targets)) {
506
+ migratedConfig.targets = this.validateBrowserTargets(config.targets);
507
+ }
508
+
509
+ if (config.apiKeys) {
510
+ migratedConfig.apiKeys.jules = config.apiKeys.jules || null;
511
+ migratedConfig.apiKeys.gemini = config.apiKeys.gemini || null;
512
+ }
513
+
514
+ if (config.codingAgent) {
515
+ migratedConfig.codingAgent = {
516
+ primary: this.validateCodingAgent(config.codingAgent.primary) || 'gemini',
517
+ fallback: this.validateCodingAgent(config.codingAgent.fallback) || 'gemini'
518
+ };
519
+ }
520
+
521
+ if (config.automation) {
522
+ migratedConfig.automation = {
523
+ ...migratedConfig.automation,
524
+ ...config.automation
525
+ };
526
+ }
527
+
528
+ migratedConfig.version = '1.0.0';
529
+ return migratedConfig;
530
+ }
531
+
532
+ return config as Configuration;
533
+ }
534
+
535
+ /**
536
+ * Get configuration display information
537
+ */
538
+ static async getConfigurationDisplay(): Promise<{
539
+ config: Configuration;
540
+ security: {
541
+ gitignoreExists: boolean;
542
+ configIgnored: boolean;
543
+ recommendations: string[];
544
+ };
545
+ validation: {
546
+ valid: boolean;
547
+ errors: string[];
548
+ };
549
+ }> {
550
+ const config = await this.load();
551
+ const security = await GitignoreManager.isConfigSecure();
552
+ const validation = this.validateConfiguration(config);
553
+
554
+ return {
555
+ config,
556
+ security,
557
+ validation
558
+ };
559
+ }
560
+
561
+ /**
562
+ * Backup current configuration
563
+ */
564
+ static async backupConfiguration(): Promise<string> {
565
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
566
+ const backupFile = `.baseguardrc.backup.${timestamp}.json`;
567
+
568
+ try {
569
+ const config = await this.load();
570
+ const content = JSON.stringify(config, null, 2);
571
+ await writeFile(backupFile, content, 'utf-8');
572
+ return backupFile;
573
+ } catch (error) {
574
+ throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Restore configuration from backup
580
+ */
581
+ static async restoreConfiguration(backupFile: string): Promise<void> {
582
+ try {
583
+ const content = await readFile(backupFile, 'utf-8');
584
+ const config = JSON.parse(content);
585
+
586
+ const validation = this.validateConfiguration(config);
587
+ if (!validation.valid) {
588
+ throw new Error(`Invalid backup configuration: ${validation.errors.join(', ')}`);
589
+ }
590
+
591
+ await this.save(config);
592
+ } catch (error) {
593
+ throw new Error(`Failed to restore backup: ${error instanceof Error ? error.message : 'Unknown error'}`);
594
+ }
595
+ }
596
596
  }