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.
- package/.eslintrc.json +25 -0
- package/.prettierrc +8 -0
- package/README.md +94 -0
- package/bin/base.js +494 -0
- package/dist/ai/fix-manager.d.ts +67 -0
- package/dist/ai/fix-manager.d.ts.map +1 -0
- package/dist/ai/fix-manager.js +326 -0
- package/dist/ai/fix-manager.js.map +1 -0
- package/dist/ai/gemini-analyzer.d.ts +116 -0
- package/dist/ai/gemini-analyzer.d.ts.map +1 -0
- package/dist/ai/gemini-analyzer.js +572 -0
- package/dist/ai/gemini-analyzer.js.map +1 -0
- package/dist/ai/index.d.ts +4 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +5 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/jules-implementer.d.ts +115 -0
- package/dist/ai/jules-implementer.d.ts.map +1 -0
- package/dist/ai/jules-implementer.js +387 -0
- package/dist/ai/jules-implementer.js.map +1 -0
- package/dist/commands/automation.d.ts +5 -0
- package/dist/commands/automation.d.ts.map +1 -0
- package/dist/commands/automation.js +305 -0
- package/dist/commands/automation.js.map +1 -0
- package/dist/commands/check.d.ts +9 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +113 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/config.d.ts +11 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +324 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/fix.d.ts +9 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +207 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/commands/index.d.ts +6 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +125 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/core/api-key-manager.d.ts +83 -0
- package/dist/core/api-key-manager.d.ts.map +1 -0
- package/dist/core/api-key-manager.js +244 -0
- package/dist/core/api-key-manager.js.map +1 -0
- package/dist/core/baseguard.d.ts +46 -0
- package/dist/core/baseguard.d.ts.map +1 -0
- package/dist/core/baseguard.js +132 -0
- package/dist/core/baseguard.js.map +1 -0
- package/dist/core/baseline-checker.d.ts +63 -0
- package/dist/core/baseline-checker.d.ts.map +1 -0
- package/dist/core/baseline-checker.js +502 -0
- package/dist/core/baseline-checker.js.map +1 -0
- package/dist/core/cache-manager.d.ts +88 -0
- package/dist/core/cache-manager.d.ts.map +1 -0
- package/dist/core/cache-manager.js +213 -0
- package/dist/core/cache-manager.js.map +1 -0
- package/dist/core/configuration.d.ts +140 -0
- package/dist/core/configuration.d.ts.map +1 -0
- package/dist/core/configuration.js +474 -0
- package/dist/core/configuration.js.map +1 -0
- package/dist/core/directory-filter.d.ts +90 -0
- package/dist/core/directory-filter.d.ts.map +1 -0
- package/dist/core/directory-filter.js +319 -0
- package/dist/core/directory-filter.js.map +1 -0
- package/dist/core/error-handler.d.ts +110 -0
- package/dist/core/error-handler.d.ts.map +1 -0
- package/dist/core/error-handler.js +392 -0
- package/dist/core/error-handler.js.map +1 -0
- package/dist/core/file-processor.d.ts +80 -0
- package/dist/core/file-processor.d.ts.map +1 -0
- package/dist/core/file-processor.js +259 -0
- package/dist/core/file-processor.js.map +1 -0
- package/dist/core/gitignore-manager.d.ts +44 -0
- package/dist/core/gitignore-manager.d.ts.map +1 -0
- package/dist/core/gitignore-manager.js +147 -0
- package/dist/core/gitignore-manager.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lazy-loader.d.ts +68 -0
- package/dist/core/lazy-loader.d.ts.map +1 -0
- package/dist/core/lazy-loader.js +260 -0
- package/dist/core/lazy-loader.js.map +1 -0
- package/dist/core/memory-manager.d.ts +1 -0
- package/dist/core/memory-manager.d.ts.map +1 -0
- package/dist/core/memory-manager.js +2 -0
- package/dist/core/memory-manager.js.map +1 -0
- package/dist/core/startup-optimizer.d.ts +45 -0
- package/dist/core/startup-optimizer.d.ts.map +1 -0
- package/dist/core/startup-optimizer.js +140 -0
- package/dist/core/startup-optimizer.js.map +1 -0
- package/dist/git/automation-engine.d.ts +58 -0
- package/dist/git/automation-engine.d.ts.map +1 -0
- package/dist/git/automation-engine.js +318 -0
- package/dist/git/automation-engine.js.map +1 -0
- package/dist/git/github-manager.d.ts +71 -0
- package/dist/git/github-manager.d.ts.map +1 -0
- package/dist/git/github-manager.js +226 -0
- package/dist/git/github-manager.js.map +1 -0
- package/dist/git/hook-manager.d.ts +43 -0
- package/dist/git/hook-manager.d.ts.map +1 -0
- package/dist/git/hook-manager.js +191 -0
- package/dist/git/hook-manager.js.map +1 -0
- package/dist/git/index.d.ts +4 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +5 -0
- package/dist/git/index.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/feature-validator.d.ts +60 -0
- package/dist/parsers/feature-validator.d.ts.map +1 -0
- package/dist/parsers/feature-validator.js +483 -0
- package/dist/parsers/feature-validator.js.map +1 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +9 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/parser-manager.d.ts +103 -0
- package/dist/parsers/parser-manager.d.ts.map +1 -0
- package/dist/parsers/parser-manager.js +321 -0
- package/dist/parsers/parser-manager.js.map +1 -0
- package/dist/parsers/parser.d.ts +23 -0
- package/dist/parsers/parser.d.ts.map +1 -0
- package/dist/parsers/parser.js +6 -0
- package/dist/parsers/parser.js.map +1 -0
- package/dist/parsers/react-parser.d.ts +22 -0
- package/dist/parsers/react-parser.d.ts.map +1 -0
- package/dist/parsers/react-parser.js +307 -0
- package/dist/parsers/react-parser.js.map +1 -0
- package/dist/parsers/svelte-parser.d.ts +33 -0
- package/dist/parsers/svelte-parser.d.ts.map +1 -0
- package/dist/parsers/svelte-parser.js +408 -0
- package/dist/parsers/svelte-parser.js.map +1 -0
- package/dist/parsers/vanilla-parser.d.ts +31 -0
- package/dist/parsers/vanilla-parser.d.ts.map +1 -0
- package/dist/parsers/vanilla-parser.js +590 -0
- package/dist/parsers/vanilla-parser.js.map +1 -0
- package/dist/parsers/vue-parser.d.ts +9 -0
- package/dist/parsers/vue-parser.d.ts.map +1 -0
- package/dist/parsers/vue-parser.js +16 -0
- package/dist/parsers/vue-parser.js.map +1 -0
- package/dist/terminal-header.d.ts +12 -0
- package/dist/terminal-header.js +45 -0
- package/dist/types/index.d.ts +83 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/components.d.ts +133 -0
- package/dist/ui/components.d.ts.map +1 -0
- package/dist/ui/components.js +482 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/help.d.ts +11 -0
- package/dist/ui/help.d.ts.map +1 -0
- package/dist/ui/help.js +161 -0
- package/dist/ui/help.js.map +1 -0
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +5 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/prompts.d.ts +63 -0
- package/dist/ui/prompts.d.ts.map +1 -0
- package/dist/ui/prompts.js +611 -0
- package/dist/ui/prompts.js.map +1 -0
- package/dist/ui/terminal-header.d.ts +13 -0
- package/dist/ui/terminal-header.d.ts.map +1 -0
- package/dist/ui/terminal-header.js +46 -0
- package/dist/ui/terminal-header.js.map +1 -0
- package/package.json +80 -0
- package/src/ai/__tests__/gemini-analyzer.test.ts +181 -0
- package/src/ai/fix-manager.ts +362 -0
- package/src/ai/gemini-analyzer.ts +671 -0
- package/src/ai/index.ts +4 -0
- package/src/ai/jules-implementer.ts +459 -0
- package/src/commands/automation.ts +344 -0
- package/src/commands/check.ts +299 -0
- package/src/commands/config.ts +365 -0
- package/src/commands/fix.ts +234 -0
- package/src/commands/index.ts +6 -0
- package/src/commands/init.ts +142 -0
- package/src/commands/status.ts +0 -0
- package/src/core/api-key-manager.ts +298 -0
- package/src/core/baseguard.ts +742 -0
- package/src/core/baseline-checker.ts +563 -0
- package/src/core/cache-manager.ts +270 -0
- package/src/core/configuration-recovery.ts +676 -0
- package/src/core/configuration.ts +559 -0
- package/src/core/debug-logger.ts +590 -0
- package/src/core/directory-filter.ts +421 -0
- package/src/core/error-handler.ts +517 -0
- package/src/core/file-processor.ts +331 -0
- package/src/core/gitignore-manager.ts +169 -0
- package/src/core/graceful-degradation-manager.ts +596 -0
- package/src/core/index.ts +13 -0
- package/src/core/lazy-loader.ts +307 -0
- package/src/core/logger.ts +0 -0
- package/src/core/memory-manager.ts +294 -0
- package/src/core/startup-optimizer.ts +173 -0
- package/src/core/system-error-handler.ts +746 -0
- package/src/git/automation-engine.ts +361 -0
- package/src/git/github-manager.ts +260 -0
- package/src/git/hook-manager.ts +210 -0
- package/src/git/index.ts +4 -0
- package/src/index.ts +8 -0
- package/src/parsers/feature-validator.ts +559 -0
- package/src/parsers/index.ts +8 -0
- package/src/parsers/parser-manager.ts +419 -0
- package/src/parsers/parser.ts +26 -0
- package/src/parsers/react-parser-optimized.ts +161 -0
- package/src/parsers/react-parser.ts +359 -0
- package/src/parsers/svelte-parser.ts +506 -0
- package/src/parsers/vanilla-parser.ts +682 -0
- package/src/parsers/vue-parser.ts +472 -0
- package/src/types/index.ts +92 -0
- package/src/ui/components.ts +567 -0
- package/src/ui/help.ts +193 -0
- package/src/ui/index.ts +4 -0
- package/src/ui/prompts.ts +688 -0
- package/src/ui/terminal-header.ts +59 -0
- package/test-config-commands.js +56 -0
- package/test-header-simple.js +33 -0
- package/test-terminal-header.js +12 -0
- package/test-ui.js +29 -0
- package/tests/e2e/baseguard.e2e.test.ts +516 -0
- package/tests/e2e/cross-platform.e2e.test.ts +420 -0
- package/tests/e2e/git-integration.e2e.test.ts +487 -0
- package/tests/fixtures/react-project/package.json +14 -0
- package/tests/fixtures/react-project/src/App.css +76 -0
- package/tests/fixtures/react-project/src/App.tsx +77 -0
- package/tests/fixtures/svelte-project/package.json +11 -0
- package/tests/fixtures/svelte-project/src/App.svelte +369 -0
- package/tests/fixtures/vanilla-project/index.html +76 -0
- package/tests/fixtures/vanilla-project/script.js +331 -0
- package/tests/fixtures/vanilla-project/styles.css +359 -0
- package/tests/fixtures/vue-project/package.json +12 -0
- package/tests/fixtures/vue-project/src/App.vue +216 -0
- package/tsconfig.json +36 -0
- 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
|
+
}
|