mcp-ticketer 0.1.8__py3-none-any.whl → 0.1.12__py3-none-any.whl

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.

Potentially problematic release.


This version of mcp-ticketer might be problematic. Click here for more details.

@@ -0,0 +1,204 @@
1
+ """Configuration migration utilities."""
2
+
3
+ import json
4
+ import shutil
5
+ from pathlib import Path
6
+ from typing import Dict, Any
7
+ from datetime import datetime
8
+
9
+ from rich.console import Console
10
+ from rich.prompt import Confirm
11
+
12
+ from ..core.project_config import (
13
+ ConfigResolver,
14
+ TicketerConfig,
15
+ AdapterConfig,
16
+ AdapterType
17
+ )
18
+
19
+ console = Console()
20
+
21
+
22
+ def migrate_config_command(dry_run: bool = False) -> None:
23
+ """Migrate from old config format to new format.
24
+
25
+ Args:
26
+ dry_run: If True, show what would be done without making changes
27
+ """
28
+ resolver = ConfigResolver()
29
+
30
+ # Check if old config exists
31
+ if not resolver.GLOBAL_CONFIG_PATH.exists():
32
+ console.print("[yellow]No configuration found to migrate[/yellow]")
33
+ return
34
+
35
+ # Load old config
36
+ try:
37
+ with open(resolver.GLOBAL_CONFIG_PATH, 'r') as f:
38
+ old_config = json.load(f)
39
+ except Exception as e:
40
+ console.print(f"[red]Failed to load config: {e}[/red]")
41
+ return
42
+
43
+ # Check if already in new format
44
+ if "adapters" in old_config and isinstance(old_config.get("adapters"), dict):
45
+ # Check if it looks like new format
46
+ if any("adapter" in v for v in old_config["adapters"].values()):
47
+ console.print("[green]Configuration already in new format[/green]")
48
+ return
49
+
50
+ console.print("[bold]Configuration Migration[/bold]\n")
51
+ console.print("Old format detected. This will migrate to the new schema.\n")
52
+
53
+ if dry_run:
54
+ console.print("[yellow]DRY RUN - No changes will be made[/yellow]\n")
55
+
56
+ # Show current config
57
+ console.print("[bold]Current Configuration:[/bold]")
58
+ console.print(json.dumps(old_config, indent=2))
59
+ console.print()
60
+
61
+ # Migrate
62
+ new_config = _migrate_old_to_new(old_config)
63
+
64
+ # Show new config
65
+ console.print("[bold]Migrated Configuration:[/bold]")
66
+ console.print(json.dumps(new_config.to_dict(), indent=2))
67
+ console.print()
68
+
69
+ if dry_run:
70
+ console.print("[yellow]This was a dry run. No changes were made.[/yellow]")
71
+ return
72
+
73
+ # Confirm migration
74
+ if not Confirm.ask("Apply migration?", default=True):
75
+ console.print("[yellow]Migration cancelled[/yellow]")
76
+ return
77
+
78
+ # Backup old config
79
+ backup_path = resolver.GLOBAL_CONFIG_PATH.with_suffix('.json.bak')
80
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
81
+ backup_path = resolver.GLOBAL_CONFIG_PATH.parent / f"config.{timestamp}.bak"
82
+
83
+ try:
84
+ shutil.copy(resolver.GLOBAL_CONFIG_PATH, backup_path)
85
+ console.print(f"[green]✓[/green] Backed up old config to: {backup_path}")
86
+ except Exception as e:
87
+ console.print(f"[red]Failed to backup config: {e}[/red]")
88
+ return
89
+
90
+ # Save new config
91
+ try:
92
+ resolver.save_global_config(new_config)
93
+ console.print(f"[green]✓[/green] Migration complete!")
94
+ console.print(f"[dim]New config saved to: {resolver.GLOBAL_CONFIG_PATH}[/dim]")
95
+ except Exception as e:
96
+ console.print(f"[red]Failed to save new config: {e}[/red]")
97
+ console.print(f"[yellow]Old config backed up at: {backup_path}[/yellow]")
98
+
99
+
100
+ def _migrate_old_to_new(old_config: Dict[str, Any]) -> TicketerConfig:
101
+ """Migrate old configuration format to new format.
102
+
103
+ Old format examples:
104
+ {
105
+ "adapter": "linear",
106
+ "config": {"api_key": "...", "team_id": "..."}
107
+ }
108
+
109
+ or
110
+
111
+ {
112
+ "default_adapter": "linear",
113
+ "adapters": {
114
+ "linear": {"api_key": "...", "team_id": "..."}
115
+ }
116
+ }
117
+
118
+ New format:
119
+ {
120
+ "default_adapter": "linear",
121
+ "adapters": {
122
+ "linear": {
123
+ "adapter": "linear",
124
+ "api_key": "...",
125
+ "team_id": "..."
126
+ }
127
+ }
128
+ }
129
+
130
+ Args:
131
+ old_config: Old configuration dictionary
132
+
133
+ Returns:
134
+ New TicketerConfig object
135
+ """
136
+ adapters = {}
137
+ default_adapter = "aitrackdown"
138
+
139
+ # Case 1: Single adapter with "adapter" and "config" fields (legacy format)
140
+ if "adapter" in old_config and "config" in old_config:
141
+ adapter_type = old_config["adapter"]
142
+ adapter_config = old_config["config"]
143
+
144
+ # Merge type into config
145
+ adapter_config["adapter"] = adapter_type
146
+
147
+ # Create AdapterConfig
148
+ adapters[adapter_type] = AdapterConfig.from_dict(adapter_config)
149
+ default_adapter = adapter_type
150
+
151
+ # Case 2: New-ish format with "adapters" dict but missing "adapter" field
152
+ elif "adapters" in old_config:
153
+ default_adapter = old_config.get("default_adapter", "aitrackdown")
154
+
155
+ for name, config in old_config["adapters"].items():
156
+ # If config doesn't have "adapter" field, infer from name
157
+ if "adapter" not in config:
158
+ config["adapter"] = name
159
+
160
+ adapters[name] = AdapterConfig.from_dict(config)
161
+
162
+ # Case 3: Already in new format (shouldn't happen but handle it)
163
+ else:
164
+ default_adapter = old_config.get("default_adapter", "aitrackdown")
165
+
166
+ # Create new config
167
+ new_config = TicketerConfig(
168
+ default_adapter=default_adapter,
169
+ adapters=adapters
170
+ )
171
+
172
+ return new_config
173
+
174
+
175
+ def validate_migrated_config(config: TicketerConfig) -> bool:
176
+ """Validate migrated configuration.
177
+
178
+ Args:
179
+ config: Migrated configuration
180
+
181
+ Returns:
182
+ True if valid, False otherwise
183
+ """
184
+ from ..core.project_config import ConfigValidator
185
+
186
+ if not config.adapters:
187
+ console.print("[yellow]Warning: No adapters configured[/yellow]")
188
+ return True
189
+
190
+ all_valid = True
191
+
192
+ for name, adapter_config in config.adapters.items():
193
+ adapter_dict = adapter_config.to_dict()
194
+ adapter_type = adapter_dict.get("adapter")
195
+
196
+ is_valid, error = ConfigValidator.validate(adapter_type, adapter_dict)
197
+
198
+ if not is_valid:
199
+ console.print(f"[red]✗[/red] {name}: {error}")
200
+ all_valid = False
201
+ else:
202
+ console.print(f"[green]✓[/green] {name}: Valid")
203
+
204
+ return all_valid