@wundr.io/cli 1.0.0 → 1.0.1
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/README.md +696 -280
- package/package.json +4 -4
- package/src/commands/computer-setup-commands.ts +160 -0
- package/src/tests/computer-setup-integration.test.ts +439 -0
- package/src/utils/backup-rollback-manager.ts +361 -0
- package/src/utils/claude-config-installer.ts +732 -0
- package/dist/ai/ai-service.d.ts +0 -152
- package/dist/ai/ai-service.d.ts.map +0 -1
- package/dist/ai/ai-service.js +0 -430
- package/dist/ai/ai-service.js.map +0 -1
- package/dist/ai/claude-client.d.ts +0 -130
- package/dist/ai/claude-client.d.ts.map +0 -1
- package/dist/ai/claude-client.js +0 -339
- package/dist/ai/claude-client.js.map +0 -1
- package/dist/ai/conversation-manager.d.ts +0 -164
- package/dist/ai/conversation-manager.d.ts.map +0 -1
- package/dist/ai/conversation-manager.js +0 -612
- package/dist/ai/conversation-manager.js.map +0 -1
- package/dist/ai/index.d.ts +0 -5
- package/dist/ai/index.d.ts.map +0 -1
- package/dist/ai/index.js +0 -8
- package/dist/ai/index.js.map +0 -1
- package/dist/cli.d.ts +0 -36
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -173
- package/dist/cli.js.map +0 -1
- package/dist/commands/ai.d.ts +0 -89
- package/dist/commands/ai.d.ts.map +0 -1
- package/dist/commands/ai.js +0 -735
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/analyze-optimized.d.ts +0 -14
- package/dist/commands/analyze-optimized.d.ts.map +0 -1
- package/dist/commands/analyze-optimized.js +0 -437
- package/dist/commands/analyze-optimized.js.map +0 -1
- package/dist/commands/analyze.d.ts +0 -65
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js +0 -435
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/batch.d.ts +0 -71
- package/dist/commands/batch.d.ts.map +0 -1
- package/dist/commands/batch.js +0 -738
- package/dist/commands/batch.js.map +0 -1
- package/dist/commands/chat.d.ts +0 -71
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/chat.js +0 -674
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/claude-init.d.ts +0 -28
- package/dist/commands/claude-init.d.ts.map +0 -1
- package/dist/commands/claude-init.js +0 -587
- package/dist/commands/claude-init.js.map +0 -1
- package/dist/commands/claude-setup.d.ts +0 -32
- package/dist/commands/claude-setup.d.ts.map +0 -1
- package/dist/commands/claude-setup.js +0 -570
- package/dist/commands/claude-setup.js.map +0 -1
- package/dist/commands/computer-setup-commands.d.ts +0 -39
- package/dist/commands/computer-setup-commands.d.ts.map +0 -1
- package/dist/commands/computer-setup-commands.js +0 -563
- package/dist/commands/computer-setup-commands.js.map +0 -1
- package/dist/commands/computer-setup.d.ts +0 -7
- package/dist/commands/computer-setup.d.ts.map +0 -1
- package/dist/commands/computer-setup.js +0 -481
- package/dist/commands/computer-setup.js.map +0 -1
- package/dist/commands/create-command.d.ts +0 -7
- package/dist/commands/create-command.d.ts.map +0 -1
- package/dist/commands/create-command.js +0 -158
- package/dist/commands/create-command.js.map +0 -1
- package/dist/commands/create.d.ts +0 -74
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -556
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/dashboard.d.ts +0 -91
- package/dist/commands/dashboard.d.ts.map +0 -1
- package/dist/commands/dashboard.js +0 -537
- package/dist/commands/dashboard.js.map +0 -1
- package/dist/commands/govern.d.ts +0 -70
- package/dist/commands/govern.d.ts.map +0 -1
- package/dist/commands/govern.js +0 -480
- package/dist/commands/govern.js.map +0 -1
- package/dist/commands/init.d.ts +0 -55
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -584
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/performance-optimizer.d.ts +0 -30
- package/dist/commands/performance-optimizer.d.ts.map +0 -1
- package/dist/commands/performance-optimizer.js +0 -649
- package/dist/commands/performance-optimizer.js.map +0 -1
- package/dist/commands/plugins.d.ts +0 -87
- package/dist/commands/plugins.d.ts.map +0 -1
- package/dist/commands/plugins.js +0 -685
- package/dist/commands/plugins.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -29
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -399
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/test-init.d.ts +0 -9
- package/dist/commands/test-init.d.ts.map +0 -1
- package/dist/commands/test-init.js +0 -222
- package/dist/commands/test-init.js.map +0 -1
- package/dist/commands/test.d.ts +0 -25
- package/dist/commands/test.d.ts.map +0 -1
- package/dist/commands/test.js +0 -217
- package/dist/commands/test.js.map +0 -1
- package/dist/commands/watch.d.ts +0 -76
- package/dist/commands/watch.d.ts.map +0 -1
- package/dist/commands/watch.js +0 -610
- package/dist/commands/watch.js.map +0 -1
- package/dist/context/context-manager.d.ts +0 -155
- package/dist/context/context-manager.d.ts.map +0 -1
- package/dist/context/context-manager.js +0 -383
- package/dist/context/context-manager.js.map +0 -1
- package/dist/context/index.d.ts +0 -3
- package/dist/context/index.d.ts.map +0 -1
- package/dist/context/index.js +0 -6
- package/dist/context/index.js.map +0 -1
- package/dist/context/session-manager.d.ts +0 -207
- package/dist/context/session-manager.d.ts.map +0 -1
- package/dist/context/session-manager.js +0 -682
- package/dist/context/session-manager.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -51
- package/dist/index.js.map +0 -1
- package/dist/interactive/interactive-mode.d.ts +0 -76
- package/dist/interactive/interactive-mode.d.ts.map +0 -1
- package/dist/interactive/interactive-mode.js +0 -730
- package/dist/interactive/interactive-mode.js.map +0 -1
- package/dist/nlp/command-mapper.d.ts +0 -174
- package/dist/nlp/command-mapper.d.ts.map +0 -1
- package/dist/nlp/command-mapper.js +0 -623
- package/dist/nlp/command-mapper.js.map +0 -1
- package/dist/nlp/command-parser.d.ts +0 -106
- package/dist/nlp/command-parser.d.ts.map +0 -1
- package/dist/nlp/command-parser.js +0 -416
- package/dist/nlp/command-parser.js.map +0 -1
- package/dist/nlp/index.d.ts +0 -5
- package/dist/nlp/index.d.ts.map +0 -1
- package/dist/nlp/index.js +0 -8
- package/dist/nlp/index.js.map +0 -1
- package/dist/nlp/intent-classifier.d.ts +0 -59
- package/dist/nlp/intent-classifier.d.ts.map +0 -1
- package/dist/nlp/intent-classifier.js +0 -384
- package/dist/nlp/intent-classifier.js.map +0 -1
- package/dist/nlp/intent-parser.d.ts +0 -152
- package/dist/nlp/intent-parser.d.ts.map +0 -1
- package/dist/nlp/intent-parser.js +0 -739
- package/dist/nlp/intent-parser.js.map +0 -1
- package/dist/plugins/plugin-manager.d.ts +0 -120
- package/dist/plugins/plugin-manager.d.ts.map +0 -1
- package/dist/plugins/plugin-manager.js +0 -595
- package/dist/plugins/plugin-manager.js.map +0 -1
- package/dist/types/index.d.ts +0 -224
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/utils/config-manager.d.ts +0 -73
- package/dist/utils/config-manager.d.ts.map +0 -1
- package/dist/utils/config-manager.js +0 -339
- package/dist/utils/config-manager.js.map +0 -1
- package/dist/utils/error-handler.d.ts +0 -46
- package/dist/utils/error-handler.d.ts.map +0 -1
- package/dist/utils/error-handler.js +0 -169
- package/dist/utils/error-handler.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -25
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -94
- package/dist/utils/logger.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wundr.io/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Unified CLI framework for the Wundr platform",
|
|
5
5
|
"keywords": ["wundr", "cli", "commander", "interactive", "tui", "automation"],
|
|
6
6
|
"homepage": "https://wundr.io",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"@oclif/plugin-plugins": "^4.1.0",
|
|
43
43
|
"@types/axios": "^0.9.36",
|
|
44
44
|
"@types/open": "^6.2.1",
|
|
45
|
-
"@wundr.io/computer-setup": "
|
|
46
|
-
"@wundr.io/config": "
|
|
47
|
-
"@wundr.io/core": "
|
|
45
|
+
"@wundr.io/computer-setup": "^1.0.0",
|
|
46
|
+
"@wundr.io/config": "^1.0.0",
|
|
47
|
+
"@wundr.io/core": "^1.0.0",
|
|
48
48
|
"axios": "^1.11.0",
|
|
49
49
|
"blessed": "^0.1.81",
|
|
50
50
|
"blessed-contrib": "^4.11.0",
|
|
@@ -10,6 +10,8 @@ import inquirer from 'inquirer';
|
|
|
10
10
|
import { ConfigManager } from '../utils/config-manager';
|
|
11
11
|
import { PluginManager } from '../plugins/plugin-manager';
|
|
12
12
|
import { logger } from '../utils/logger';
|
|
13
|
+
import { BackupRollbackManager } from '../utils/backup-rollback-manager';
|
|
14
|
+
import { ClaudeConfigInstaller } from '../utils/claude-config-installer';
|
|
13
15
|
import { execSync } from 'child_process';
|
|
14
16
|
import * as os from 'os';
|
|
15
17
|
import * as fs from 'fs/promises';
|
|
@@ -38,6 +40,8 @@ interface LocalSetupOptions {
|
|
|
38
40
|
export class ComputerSetupCommands {
|
|
39
41
|
private orchestrator: RealSetupOrchestrator;
|
|
40
42
|
private platform: SetupPlatform;
|
|
43
|
+
private backupManager: BackupRollbackManager;
|
|
44
|
+
private claudeInstaller: ClaudeConfigInstaller;
|
|
41
45
|
|
|
42
46
|
constructor(
|
|
43
47
|
private program: Command,
|
|
@@ -46,6 +50,8 @@ export class ComputerSetupCommands {
|
|
|
46
50
|
) {
|
|
47
51
|
this.platform = this.detectPlatform();
|
|
48
52
|
this.orchestrator = new RealSetupOrchestrator(this.platform);
|
|
53
|
+
this.backupManager = new BackupRollbackManager();
|
|
54
|
+
this.claudeInstaller = new ClaudeConfigInstaller();
|
|
49
55
|
this.registerCommands();
|
|
50
56
|
}
|
|
51
57
|
|
|
@@ -165,6 +171,41 @@ Examples:
|
|
|
165
171
|
.action(async (tool, options) => {
|
|
166
172
|
await this.installTool(tool, options);
|
|
167
173
|
});
|
|
174
|
+
|
|
175
|
+
// Claude Code configuration commands
|
|
176
|
+
computerSetup
|
|
177
|
+
.command('claude-config')
|
|
178
|
+
.description('Install Claude Code configuration')
|
|
179
|
+
.option('--dry-run', 'Show what would be installed')
|
|
180
|
+
.option('--skip-backup', 'Skip backup creation')
|
|
181
|
+
.option('--overwrite', 'Overwrite existing configurations')
|
|
182
|
+
.option('--verbose', 'Show detailed output')
|
|
183
|
+
.action(async options => {
|
|
184
|
+
await this.installClaudeConfig(options);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Backup management commands
|
|
188
|
+
computerSetup
|
|
189
|
+
.command('backup')
|
|
190
|
+
.description('Manage configuration backups')
|
|
191
|
+
.option('-l, --list', 'List all backups')
|
|
192
|
+
.option('-c, --create', 'Create new backup')
|
|
193
|
+
.option('-v, --verify <id>', 'Verify backup integrity')
|
|
194
|
+
.option('--cleanup', 'Clean up old backups')
|
|
195
|
+
.action(async options => {
|
|
196
|
+
await this.manageBackups(options);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Rollback command
|
|
200
|
+
computerSetup
|
|
201
|
+
.command('rollback')
|
|
202
|
+
.description('Restore from backup')
|
|
203
|
+
.option('--backup <id>', 'Specific backup to restore')
|
|
204
|
+
.option('--dry-run', 'Show what would be restored')
|
|
205
|
+
.option('--verbose', 'Show detailed output')
|
|
206
|
+
.action(async options => {
|
|
207
|
+
await this.rollbackConfiguration(options);
|
|
208
|
+
});
|
|
168
209
|
}
|
|
169
210
|
|
|
170
211
|
private async runSetup(options: any): Promise<void> {
|
|
@@ -706,4 +747,123 @@ Examples:
|
|
|
706
747
|
console.log(chalk.gray(` Fixing ${failure.name}...`));
|
|
707
748
|
}
|
|
708
749
|
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Install Claude Code configuration
|
|
753
|
+
*/
|
|
754
|
+
private async installClaudeConfig(options: any): Promise<void> {
|
|
755
|
+
console.log(chalk.cyan('\n🔧 Claude Code Configuration Installer\n'));
|
|
756
|
+
|
|
757
|
+
try {
|
|
758
|
+
// Initialize managers
|
|
759
|
+
await this.backupManager.initialize();
|
|
760
|
+
await this.claudeInstaller.initialize();
|
|
761
|
+
|
|
762
|
+
// Install configurations
|
|
763
|
+
const result = await this.claudeInstaller.install({
|
|
764
|
+
dryRun: options.dryRun,
|
|
765
|
+
skipBackup: options.skipBackup,
|
|
766
|
+
overwrite: options.overwrite,
|
|
767
|
+
verbose: options.verbose,
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
if (!result.success) {
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
} catch (error) {
|
|
774
|
+
console.error(chalk.red('❌ Installation failed:'), error);
|
|
775
|
+
logger.error('Claude config installation failed', error);
|
|
776
|
+
process.exit(1);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Manage backups
|
|
782
|
+
*/
|
|
783
|
+
private async manageBackups(options: any): Promise<void> {
|
|
784
|
+
await this.backupManager.initialize();
|
|
785
|
+
|
|
786
|
+
if (options.list) {
|
|
787
|
+
const backups = await this.backupManager.listBackups();
|
|
788
|
+
|
|
789
|
+
if (backups.length === 0) {
|
|
790
|
+
console.log(chalk.yellow('No backups found'));
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
console.log(chalk.cyan('\n📦 Available Backups\n'));
|
|
795
|
+
backups.forEach(backup => {
|
|
796
|
+
const date = new Date(backup.timestamp).toLocaleString();
|
|
797
|
+
const status = backup.success
|
|
798
|
+
? chalk.green('✅ Success')
|
|
799
|
+
: chalk.red('❌ Failed');
|
|
800
|
+
|
|
801
|
+
console.log(chalk.white(`ID: ${backup.backupId}`));
|
|
802
|
+
console.log(chalk.gray(` Date: ${date}`));
|
|
803
|
+
console.log(chalk.gray(` Reason: ${backup.reason}`));
|
|
804
|
+
console.log(chalk.gray(` Status: ${status}`));
|
|
805
|
+
console.log(chalk.gray(` Files: ${backup.files.length}`));
|
|
806
|
+
console.log();
|
|
807
|
+
});
|
|
808
|
+
} else if (options.create) {
|
|
809
|
+
const { files } = await inquirer.prompt([
|
|
810
|
+
{
|
|
811
|
+
type: 'input',
|
|
812
|
+
name: 'files',
|
|
813
|
+
message: 'Enter file paths (comma-separated):',
|
|
814
|
+
validate: input => input.length > 0,
|
|
815
|
+
},
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
const fileList = files.split(',').map((f: string) => f.trim());
|
|
819
|
+
const backup = await this.backupManager.createBackup(
|
|
820
|
+
fileList,
|
|
821
|
+
'Manual backup'
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
this.backupManager.displayBackupInfo(backup);
|
|
825
|
+
} else if (options.verify) {
|
|
826
|
+
const isValid = await this.backupManager.verifyBackup(options.verify);
|
|
827
|
+
|
|
828
|
+
if (isValid) {
|
|
829
|
+
console.log(chalk.green(`✅ Backup ${options.verify} is valid`));
|
|
830
|
+
} else {
|
|
831
|
+
console.log(chalk.red(`❌ Backup ${options.verify} is invalid`));
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
} else if (options.cleanup) {
|
|
835
|
+
await this.backupManager.cleanupOldBackups();
|
|
836
|
+
console.log(chalk.green('✅ Old backups cleaned up'));
|
|
837
|
+
} else {
|
|
838
|
+
console.log(chalk.yellow('Please specify an option. Use --help for details.'));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Rollback configuration
|
|
844
|
+
*/
|
|
845
|
+
private async rollbackConfiguration(options: any): Promise<void> {
|
|
846
|
+
console.log(chalk.cyan('\n🔄 Configuration Rollback\n'));
|
|
847
|
+
|
|
848
|
+
await this.backupManager.initialize();
|
|
849
|
+
|
|
850
|
+
const success = await this.backupManager.rollback({
|
|
851
|
+
backupId: options.backup,
|
|
852
|
+
dryRun: options.dryRun,
|
|
853
|
+
verbose: options.verbose,
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
if (!success) {
|
|
857
|
+
console.log(chalk.red('\n❌ Rollback failed'));
|
|
858
|
+
process.exit(1);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
if (!options.dryRun) {
|
|
862
|
+
console.log(chalk.green('\n✅ Rollback completed successfully'));
|
|
863
|
+
console.log(chalk.cyan('\nNext steps:'));
|
|
864
|
+
console.log(chalk.white(' 1. Verify restored configurations'));
|
|
865
|
+
console.log(chalk.white(' 2. Restart terminal if needed'));
|
|
866
|
+
console.log(chalk.white(' 3. Run validation script'));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
709
869
|
}
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for computer-setup with Claude Code configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { existsSync } from 'fs';
|
|
9
|
+
import { BackupRollbackManager } from '../utils/backup-rollback-manager';
|
|
10
|
+
import { ClaudeConfigInstaller } from '../utils/claude-config-installer';
|
|
11
|
+
|
|
12
|
+
describe('Computer Setup Integration Tests', () => {
|
|
13
|
+
const testDir = path.join(__dirname, '.test-temp');
|
|
14
|
+
const backupDir = path.join(testDir, 'backups');
|
|
15
|
+
const claudeDir = path.join(testDir, '.claude');
|
|
16
|
+
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
// Create test directories
|
|
19
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
20
|
+
await fs.mkdir(backupDir, { recursive: true });
|
|
21
|
+
await fs.mkdir(claudeDir, { recursive: true });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
afterEach(async () => {
|
|
25
|
+
// Clean up test directories
|
|
26
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('BackupRollbackManager', () => {
|
|
30
|
+
let backupManager: BackupRollbackManager;
|
|
31
|
+
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
backupManager = new BackupRollbackManager(backupDir);
|
|
34
|
+
await backupManager.initialize();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should initialize backup directory', async () => {
|
|
38
|
+
expect(existsSync(backupDir)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should create backup of files', async () => {
|
|
42
|
+
// Create test files
|
|
43
|
+
const testFile1 = path.join(testDir, 'test1.txt');
|
|
44
|
+
const testFile2 = path.join(testDir, 'test2.txt');
|
|
45
|
+
await fs.writeFile(testFile1, 'content1');
|
|
46
|
+
await fs.writeFile(testFile2, 'content2');
|
|
47
|
+
|
|
48
|
+
// Create backup
|
|
49
|
+
const metadata = await backupManager.createBackup(
|
|
50
|
+
[testFile1, testFile2],
|
|
51
|
+
'Test backup'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(metadata.success).toBe(true);
|
|
55
|
+
expect(metadata.files).toHaveLength(2);
|
|
56
|
+
expect(metadata.backupId).toBeTruthy();
|
|
57
|
+
|
|
58
|
+
// Verify backup files exist
|
|
59
|
+
for (const file of metadata.files) {
|
|
60
|
+
expect(existsSync(file.backupPath)).toBe(true);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should list backups', async () => {
|
|
65
|
+
// Create test file and backup
|
|
66
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
67
|
+
await fs.writeFile(testFile, 'content');
|
|
68
|
+
|
|
69
|
+
await backupManager.createBackup([testFile], 'Backup 1');
|
|
70
|
+
await backupManager.createBackup([testFile], 'Backup 2');
|
|
71
|
+
|
|
72
|
+
const backups = await backupManager.listBackups();
|
|
73
|
+
expect(backups.length).toBeGreaterThanOrEqual(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should restore files from backup', async () => {
|
|
77
|
+
// Create original file
|
|
78
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
79
|
+
await fs.writeFile(testFile, 'original content');
|
|
80
|
+
|
|
81
|
+
// Create backup
|
|
82
|
+
const metadata = await backupManager.createBackup([testFile], 'Test backup');
|
|
83
|
+
|
|
84
|
+
// Modify file
|
|
85
|
+
await fs.writeFile(testFile, 'modified content');
|
|
86
|
+
|
|
87
|
+
// Restore from backup
|
|
88
|
+
const success = await backupManager.rollback({
|
|
89
|
+
backupId: metadata.backupId,
|
|
90
|
+
dryRun: false,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(success).toBe(true);
|
|
94
|
+
|
|
95
|
+
// Verify content restored
|
|
96
|
+
const content = await fs.readFile(testFile, 'utf-8');
|
|
97
|
+
expect(content).toBe('original content');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should verify backup integrity', async () => {
|
|
101
|
+
// Create test file and backup
|
|
102
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
103
|
+
await fs.writeFile(testFile, 'content');
|
|
104
|
+
|
|
105
|
+
const metadata = await backupManager.createBackup([testFile], 'Test backup');
|
|
106
|
+
|
|
107
|
+
const isValid = await backupManager.verifyBackup(metadata.backupId);
|
|
108
|
+
expect(isValid).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should clean up old backups', async () => {
|
|
112
|
+
// Create multiple backups
|
|
113
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
114
|
+
await fs.writeFile(testFile, 'content');
|
|
115
|
+
|
|
116
|
+
for (let i = 0; i < 10; i++) {
|
|
117
|
+
await backupManager.createBackup([testFile], `Backup ${i}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Clean up, keeping only 5
|
|
121
|
+
await backupManager.cleanupOldBackups(5);
|
|
122
|
+
|
|
123
|
+
const backups = await backupManager.listBackups();
|
|
124
|
+
expect(backups.length).toBeLessThanOrEqual(5);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle dry-run rollback', async () => {
|
|
128
|
+
const testFile = path.join(testDir, 'test.txt');
|
|
129
|
+
await fs.writeFile(testFile, 'original');
|
|
130
|
+
|
|
131
|
+
const metadata = await backupManager.createBackup([testFile], 'Test');
|
|
132
|
+
|
|
133
|
+
await fs.writeFile(testFile, 'modified');
|
|
134
|
+
|
|
135
|
+
const success = await backupManager.rollback({
|
|
136
|
+
backupId: metadata.backupId,
|
|
137
|
+
dryRun: true,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(success).toBe(true);
|
|
141
|
+
|
|
142
|
+
// File should not be restored in dry-run
|
|
143
|
+
const content = await fs.readFile(testFile, 'utf-8');
|
|
144
|
+
expect(content).toBe('modified');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('ClaudeConfigInstaller', () => {
|
|
149
|
+
let installer: ClaudeConfigInstaller;
|
|
150
|
+
|
|
151
|
+
beforeEach(async () => {
|
|
152
|
+
installer = new ClaudeConfigInstaller({
|
|
153
|
+
claudeDir,
|
|
154
|
+
sourceDir: testDir,
|
|
155
|
+
});
|
|
156
|
+
await installer.initialize();
|
|
157
|
+
|
|
158
|
+
// Create mock CLAUDE.md
|
|
159
|
+
await fs.writeFile(
|
|
160
|
+
path.join(testDir, 'CLAUDE.md'),
|
|
161
|
+
'# Claude Configuration\nTest content'
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should initialize directory structure', async () => {
|
|
166
|
+
expect(existsSync(claudeDir)).toBe(true);
|
|
167
|
+
expect(existsSync(path.join(claudeDir, 'hooks'))).toBe(true);
|
|
168
|
+
expect(existsSync(path.join(claudeDir, 'agents'))).toBe(true);
|
|
169
|
+
expect(existsSync(path.join(claudeDir, 'workflows'))).toBe(true);
|
|
170
|
+
expect(existsSync(path.join(claudeDir, 'scripts'))).toBe(true);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should install CLAUDE.md', async () => {
|
|
174
|
+
const result = await installer.install({
|
|
175
|
+
dryRun: false,
|
|
176
|
+
skipBackup: true,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
expect(result.success).toBe(true);
|
|
180
|
+
expect(result.installed).toContain('CLAUDE.md');
|
|
181
|
+
expect(existsSync(path.join(claudeDir, 'CLAUDE.md'))).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should install hooks', async () => {
|
|
185
|
+
const result = await installer.install({
|
|
186
|
+
dryRun: false,
|
|
187
|
+
skipBackup: true,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(result.success).toBe(true);
|
|
191
|
+
|
|
192
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
193
|
+
expect(existsSync(path.join(hooksDir, 'pre-commit'))).toBe(true);
|
|
194
|
+
expect(existsSync(path.join(hooksDir, 'post-checkout'))).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should install conventions', async () => {
|
|
198
|
+
const result = await installer.install({
|
|
199
|
+
dryRun: false,
|
|
200
|
+
skipBackup: true,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(result.success).toBe(true);
|
|
204
|
+
expect(result.installed).toContain('conventions.json');
|
|
205
|
+
|
|
206
|
+
const conventionsPath = path.join(claudeDir, 'conventions.json');
|
|
207
|
+
expect(existsSync(conventionsPath)).toBe(true);
|
|
208
|
+
|
|
209
|
+
const conventions = JSON.parse(await fs.readFile(conventionsPath, 'utf-8'));
|
|
210
|
+
expect(conventions).toHaveProperty('fileNaming');
|
|
211
|
+
expect(conventions).toHaveProperty('codeStyle');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should install agent templates', async () => {
|
|
215
|
+
const result = await installer.install({
|
|
216
|
+
dryRun: false,
|
|
217
|
+
skipBackup: true,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(result.success).toBe(true);
|
|
221
|
+
|
|
222
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
223
|
+
expect(existsSync(path.join(agentsDir, 'backend-developer.json'))).toBe(true);
|
|
224
|
+
expect(existsSync(path.join(agentsDir, 'frontend-developer.json'))).toBe(true);
|
|
225
|
+
expect(existsSync(path.join(agentsDir, 'fullstack-developer.json'))).toBe(true);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should install git-worktree workflows', async () => {
|
|
229
|
+
const result = await installer.install({
|
|
230
|
+
dryRun: false,
|
|
231
|
+
skipBackup: true,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(result.success).toBe(true);
|
|
235
|
+
|
|
236
|
+
const workflowsDir = path.join(claudeDir, 'workflows');
|
|
237
|
+
expect(
|
|
238
|
+
existsSync(path.join(workflowsDir, 'feature-development.json'))
|
|
239
|
+
).toBe(true);
|
|
240
|
+
expect(existsSync(path.join(workflowsDir, 'bug-fix.json'))).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should install validation scripts', async () => {
|
|
244
|
+
const result = await installer.install({
|
|
245
|
+
dryRun: false,
|
|
246
|
+
skipBackup: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(result.success).toBe(true);
|
|
250
|
+
|
|
251
|
+
const scriptsDir = path.join(claudeDir, 'scripts');
|
|
252
|
+
expect(existsSync(path.join(scriptsDir, 'validate-setup.sh'))).toBe(true);
|
|
253
|
+
expect(existsSync(path.join(scriptsDir, 'check-config.sh'))).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should handle dry-run installation', async () => {
|
|
257
|
+
const result = await installer.install({
|
|
258
|
+
dryRun: true,
|
|
259
|
+
skipBackup: true,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(result.success).toBe(true);
|
|
263
|
+
expect(result.installed.every(f => f.includes('dry-run'))).toBe(true);
|
|
264
|
+
|
|
265
|
+
// Files should not exist in dry-run
|
|
266
|
+
expect(existsSync(path.join(claudeDir, 'CLAUDE.md'))).toBe(false);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should skip existing files without overwrite', async () => {
|
|
270
|
+
// Create existing file
|
|
271
|
+
await fs.writeFile(path.join(claudeDir, 'CLAUDE.md'), 'existing content');
|
|
272
|
+
|
|
273
|
+
const result = await installer.install({
|
|
274
|
+
dryRun: false,
|
|
275
|
+
skipBackup: true,
|
|
276
|
+
overwrite: false,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(result.skipped).toContain('CLAUDE.md');
|
|
280
|
+
|
|
281
|
+
// Content should remain unchanged
|
|
282
|
+
const content = await fs.readFile(path.join(claudeDir, 'CLAUDE.md'), 'utf-8');
|
|
283
|
+
expect(content).toBe('existing content');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should overwrite existing files with overwrite flag', async () => {
|
|
287
|
+
// Create existing file
|
|
288
|
+
await fs.writeFile(path.join(claudeDir, 'CLAUDE.md'), 'existing content');
|
|
289
|
+
|
|
290
|
+
const result = await installer.install({
|
|
291
|
+
dryRun: false,
|
|
292
|
+
skipBackup: true,
|
|
293
|
+
overwrite: true,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(result.installed).toContain('CLAUDE.md');
|
|
297
|
+
|
|
298
|
+
// Content should be updated
|
|
299
|
+
const content = await fs.readFile(path.join(claudeDir, 'CLAUDE.md'), 'utf-8');
|
|
300
|
+
expect(content).toContain('Test content');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should create backup before installation', async () => {
|
|
304
|
+
// Create existing configurations
|
|
305
|
+
await fs.writeFile(path.join(claudeDir, 'CLAUDE.md'), 'existing');
|
|
306
|
+
await fs.writeFile(
|
|
307
|
+
path.join(claudeDir, 'conventions.json'),
|
|
308
|
+
JSON.stringify({ test: true })
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const result = await installer.install({
|
|
312
|
+
dryRun: false,
|
|
313
|
+
skipBackup: false,
|
|
314
|
+
overwrite: true,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
expect(result.backupId).toBeTruthy();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe('End-to-End Integration', () => {
|
|
322
|
+
it('should complete full installation workflow', async () => {
|
|
323
|
+
const backupManager = new BackupRollbackManager(backupDir);
|
|
324
|
+
const installer = new ClaudeConfigInstaller({
|
|
325
|
+
claudeDir,
|
|
326
|
+
sourceDir: testDir,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
await backupManager.initialize();
|
|
330
|
+
await installer.initialize();
|
|
331
|
+
|
|
332
|
+
// Create mock source file
|
|
333
|
+
await fs.writeFile(
|
|
334
|
+
path.join(testDir, 'CLAUDE.md'),
|
|
335
|
+
'# Enhanced Claude Configuration'
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Install configurations
|
|
339
|
+
const installResult = await installer.install({
|
|
340
|
+
dryRun: false,
|
|
341
|
+
skipBackup: false,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
expect(installResult.success).toBe(true);
|
|
345
|
+
expect(installResult.installed.length).toBeGreaterThan(0);
|
|
346
|
+
|
|
347
|
+
// Verify all components installed
|
|
348
|
+
expect(existsSync(path.join(claudeDir, 'CLAUDE.md'))).toBe(true);
|
|
349
|
+
expect(existsSync(path.join(claudeDir, 'conventions.json'))).toBe(true);
|
|
350
|
+
expect(existsSync(path.join(claudeDir, 'hooks', 'pre-commit'))).toBe(true);
|
|
351
|
+
expect(existsSync(path.join(claudeDir, 'agents', 'backend-developer.json'))).toBe(
|
|
352
|
+
true
|
|
353
|
+
);
|
|
354
|
+
expect(
|
|
355
|
+
existsSync(path.join(claudeDir, 'workflows', 'feature-development.json'))
|
|
356
|
+
).toBe(true);
|
|
357
|
+
expect(existsSync(path.join(claudeDir, 'scripts', 'validate-setup.sh'))).toBe(
|
|
358
|
+
true
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should support complete backup and rollback cycle', async () => {
|
|
363
|
+
const backupManager = new BackupRollbackManager(backupDir);
|
|
364
|
+
const installer = new ClaudeConfigInstaller({
|
|
365
|
+
claudeDir,
|
|
366
|
+
sourceDir: testDir,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await backupManager.initialize();
|
|
370
|
+
await installer.initialize();
|
|
371
|
+
|
|
372
|
+
// Create initial config
|
|
373
|
+
const initialConfig = path.join(claudeDir, 'CLAUDE.md');
|
|
374
|
+
await fs.writeFile(initialConfig, 'Initial configuration');
|
|
375
|
+
|
|
376
|
+
// Create backup
|
|
377
|
+
const backup = await backupManager.createBackup(
|
|
378
|
+
[initialConfig],
|
|
379
|
+
'Pre-update backup'
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
// Update configuration
|
|
383
|
+
await fs.writeFile(initialConfig, 'Updated configuration');
|
|
384
|
+
|
|
385
|
+
// Rollback to original
|
|
386
|
+
const success = await backupManager.rollback({
|
|
387
|
+
backupId: backup.backupId,
|
|
388
|
+
dryRun: false,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
expect(success).toBe(true);
|
|
392
|
+
|
|
393
|
+
// Verify rollback
|
|
394
|
+
const content = await fs.readFile(initialConfig, 'utf-8');
|
|
395
|
+
expect(content).toBe('Initial configuration');
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('Error Handling', () => {
|
|
400
|
+
it('should handle missing source files gracefully', async () => {
|
|
401
|
+
const installer = new ClaudeConfigInstaller({
|
|
402
|
+
claudeDir,
|
|
403
|
+
sourceDir: testDir,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
await installer.initialize();
|
|
407
|
+
|
|
408
|
+
// Don't create CLAUDE.md source
|
|
409
|
+
const result = await installer.install({
|
|
410
|
+
dryRun: false,
|
|
411
|
+
skipBackup: true,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Should skip missing files
|
|
415
|
+
expect(result.skipped).toContain('CLAUDE.md');
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('should handle permission errors', async () => {
|
|
419
|
+
const installer = new ClaudeConfigInstaller({
|
|
420
|
+
claudeDir: '/root/unauthorized', // Likely no permission
|
|
421
|
+
sourceDir: testDir,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
await expect(installer.initialize()).rejects.toThrow();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should handle corrupted backup metadata', async () => {
|
|
428
|
+
const backupManager = new BackupRollbackManager(backupDir);
|
|
429
|
+
await backupManager.initialize();
|
|
430
|
+
|
|
431
|
+
// Corrupt metadata file
|
|
432
|
+
const metadataPath = path.join(backupDir, 'metadata.json');
|
|
433
|
+
await fs.writeFile(metadataPath, 'invalid json {');
|
|
434
|
+
|
|
435
|
+
const backups = await backupManager.listBackups();
|
|
436
|
+
expect(backups).toEqual([]);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|