Fixed installer.py

Fixed Update issue added missing function update_components() to installer.py

Signed-off-by: Mithun Gowda B <mithungowda.b7411@gmail.com>
This commit is contained in:
Mithun Gowda B
2025-07-23 17:36:15 +05:30
committed by GitHub
parent 1ae2c57726
commit f7a9e19a9a

View File

@@ -9,10 +9,13 @@ import tempfile
from datetime import datetime from datetime import datetime
from .component import Component from .component import Component
class Installer: class Installer:
"""Main installer orchestrator""" """Main installer orchestrator"""
def __init__(self, install_dir: Optional[Path] = None, dry_run: bool = False): def __init__(self,
install_dir: Optional[Path] = None,
dry_run: bool = False):
""" """
Initialize installer Initialize installer
@@ -25,10 +28,12 @@ class Installer:
self.dry_run = dry_run self.dry_run = dry_run
self.components: Dict[str, Component] = {} self.components: Dict[str, Component] = {}
self.installed_components: Set[str] = set() self.installed_components: Set[str] = set()
self.updated_components: Set[str] = set()
self.failed_components: Set[str] = set() self.failed_components: Set[str] = set()
self.skipped_components: Set[str] = set() self.skipped_components: Set[str] = set()
self.backup_path: Optional[Path] = None self.backup_path: Optional[Path] = None
def register_component(self, component: Component) -> None: def register_component(self, component: Component) -> None:
""" """
Register a component for installation Register a component for installation
@@ -38,7 +43,7 @@ class Installer:
""" """
metadata = component.get_metadata() metadata = component.get_metadata()
self.components[metadata['name']] = component self.components[metadata['name']] = component
def register_components(self, components: List[Component]) -> None: def register_components(self, components: List[Component]) -> None:
""" """
Register multiple components Register multiple components
@@ -48,7 +53,7 @@ class Installer:
""" """
for component in components: for component in components:
self.register_component(component) self.register_component(component)
def resolve_dependencies(self, component_names: List[str]) -> List[str]: def resolve_dependencies(self, component_names: List[str]) -> List[str]:
""" """
Resolve component dependencies in correct installation order Resolve component dependencies in correct installation order
@@ -64,32 +69,33 @@ class Installer:
""" """
resolved = [] resolved = []
resolving = set() resolving = set()
def resolve(name: str): def resolve(name: str):
if name in resolved: if name in resolved:
return return
if name in resolving: if name in resolving:
raise ValueError(f"Circular dependency detected involving {name}") raise ValueError(
f"Circular dependency detected involving {name}")
if name not in self.components: if name not in self.components:
raise ValueError(f"Unknown component: {name}") raise ValueError(f"Unknown component: {name}")
resolving.add(name) resolving.add(name)
# Resolve dependencies first # Resolve dependencies first
for dep in self.components[name].get_dependencies(): for dep in self.components[name].get_dependencies():
resolve(dep) resolve(dep)
resolving.remove(name) resolving.remove(name)
resolved.append(name) resolved.append(name)
# Resolve each requested component # Resolve each requested component
for name in component_names: for name in component_names:
resolve(name) resolve(name)
return resolved return resolved
def validate_system_requirements(self) -> Tuple[bool, List[str]]: def validate_system_requirements(self) -> Tuple[bool, List[str]]:
""" """
Validate system requirements for all registered components Validate system requirements for all registered components
@@ -98,16 +104,18 @@ class Installer:
Tuple of (success: bool, error_messages: List[str]) Tuple of (success: bool, error_messages: List[str])
""" """
errors = [] errors = []
# Check disk space (500MB minimum) # Check disk space (500MB minimum)
try: try:
stat = shutil.disk_usage(self.install_dir.parent) stat = shutil.disk_usage(self.install_dir.parent)
free_mb = stat.free / (1024 * 1024) free_mb = stat.free / (1024 * 1024)
if free_mb < 500: if free_mb < 500:
errors.append(f"Insufficient disk space: {free_mb:.1f}MB free (500MB required)") errors.append(
f"Insufficient disk space: {free_mb:.1f}MB free (500MB required)"
)
except Exception as e: except Exception as e:
errors.append(f"Could not check disk space: {e}") errors.append(f"Could not check disk space: {e}")
# Check write permissions # Check write permissions
test_file = self.install_dir / ".write_test" test_file = self.install_dir / ".write_test"
try: try:
@@ -116,9 +124,9 @@ class Installer:
test_file.unlink() test_file.unlink()
except Exception as e: except Exception as e:
errors.append(f"No write permission to {self.install_dir}: {e}") errors.append(f"No write permission to {self.install_dir}: {e}")
return len(errors) == 0, errors return len(errors) == 0, errors
def create_backup(self) -> Optional[Path]: def create_backup(self) -> Optional[Path]:
""" """
Create backup of existing installation Create backup of existing installation
@@ -128,26 +136,26 @@ class Installer:
""" """
if not self.install_dir.exists(): if not self.install_dir.exists():
return None return None
if self.dry_run: if self.dry_run:
return self.install_dir / "backup_dryrun.tar.gz" return self.install_dir / "backup_dryrun.tar.gz"
# Create backup directory # Create backup directory
backup_dir = self.install_dir / "backups" backup_dir = self.install_dir / "backups"
backup_dir.mkdir(exist_ok=True) backup_dir.mkdir(exist_ok=True)
# Create timestamped backup # Create timestamped backup
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_name = f"superclaude_backup_{timestamp}" backup_name = f"superclaude_backup_{timestamp}"
backup_path = backup_dir / f"{backup_name}.tar.gz" backup_path = backup_dir / f"{backup_name}.tar.gz"
# Create temporary directory for backup # Create temporary directory for backup
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_backup = Path(temp_dir) / backup_name temp_backup = Path(temp_dir) / backup_name
# Ensure temp backup directory exists # Ensure temp backup directory exists
temp_backup.mkdir(parents=True, exist_ok=True) temp_backup.mkdir(parents=True, exist_ok=True)
# Copy all files except backups directory # Copy all files except backups directory
for item in self.install_dir.iterdir(): for item in self.install_dir.iterdir():
if item.name != "backups": if item.name != "backups":
@@ -159,24 +167,23 @@ class Installer:
except Exception as e: except Exception as e:
# Log warning but continue backup process # Log warning but continue backup process
print(f"Warning: Could not backup {item.name}: {e}") print(f"Warning: Could not backup {item.name}: {e}")
# Create archive only if there are files to backup # Create archive only if there are files to backup
if any(temp_backup.iterdir()): if any(temp_backup.iterdir()):
shutil.make_archive( shutil.make_archive(backup_path.with_suffix(''), 'gztar',
backup_path.with_suffix(''), temp_dir, backup_name)
'gztar',
temp_dir,
backup_name
)
else: else:
# Create empty backup file to indicate backup was attempted # Create empty backup file to indicate backup was attempted
backup_path.touch() backup_path.touch()
print(f"Warning: No files to backup, created empty backup marker: {backup_path.name}") print(
f"Warning: No files to backup, created empty backup marker: {backup_path.name}"
)
self.backup_path = backup_path self.backup_path = backup_path
return backup_path return backup_path
def install_component(self, component_name: str, config: Dict[str, Any]) -> bool: def install_component(self, component_name: str,
config: Dict[str, Any]) -> bool:
""" """
Install a single component Install a single component
@@ -189,13 +196,13 @@ class Installer:
""" """
if component_name not in self.components: if component_name not in self.components:
raise ValueError(f"Unknown component: {component_name}") raise ValueError(f"Unknown component: {component_name}")
component = self.components[component_name] component = self.components[component_name]
# Skip if already installed # Skip if already installed
if component_name in self.installed_components: if component_name in self.installed_components:
return True return True
# Check prerequisites # Check prerequisites
success, errors = component.validate_prerequisites() success, errors = component.validate_prerequisites()
if not success: if not success:
@@ -204,7 +211,7 @@ class Installer:
print(f" - {error}") print(f" - {error}")
self.failed_components.add(component_name) self.failed_components.add(component_name)
return False return False
# Perform installation # Perform installation
try: try:
if self.dry_run: if self.dry_run:
@@ -212,20 +219,23 @@ class Installer:
success = True success = True
else: else:
success = component.install(config) success = component.install(config)
if success: if success:
self.installed_components.add(component_name) self.installed_components.add(component_name)
self.updated_components.add(component_name)
else: else:
self.failed_components.add(component_name) self.failed_components.add(component_name)
return success return success
except Exception as e: except Exception as e:
print(f"Error installing {component_name}: {e}") print(f"Error installing {component_name}: {e}")
self.failed_components.add(component_name) self.failed_components.add(component_name)
return False return False
def install_components(self, component_names: List[str], config: Optional[Dict[str, Any]] = None) -> bool: def install_components(self,
component_names: List[str],
config: Optional[Dict[str, Any]] = None) -> bool:
""" """
Install multiple components in dependency order Install multiple components in dependency order
@@ -237,14 +247,14 @@ class Installer:
True if all successful, False if any failed True if all successful, False if any failed
""" """
config = config or {} config = config or {}
# Resolve dependencies # Resolve dependencies
try: try:
ordered_names = self.resolve_dependencies(component_names) ordered_names = self.resolve_dependencies(component_names)
except ValueError as e: except ValueError as e:
print(f"Dependency resolution error: {e}") print(f"Dependency resolution error: {e}")
return False return False
# Validate system requirements # Validate system requirements
success, errors = self.validate_system_requirements() success, errors = self.validate_system_requirements()
if not success: if not success:
@@ -252,12 +262,12 @@ class Installer:
for error in errors: for error in errors:
print(f" - {error}") print(f" - {error}")
return False return False
# Create backup if updating # Create backup if updating
if self.install_dir.exists() and not self.dry_run: if self.install_dir.exists() and not self.dry_run:
print("Creating backup of existing installation...") print("Creating backup of existing installation...")
self.create_backup() self.create_backup()
# Install each component # Install each component
all_success = True all_success = True
for name in ordered_names: for name in ordered_names:
@@ -265,21 +275,21 @@ class Installer:
if not self.install_component(name, config): if not self.install_component(name, config):
all_success = False all_success = False
# Continue installing other components even if one fails # Continue installing other components even if one fails
if not self.dry_run: if not self.dry_run:
self._run_post_install_validation() self._run_post_install_validation()
return all_success return all_success
def _run_post_install_validation(self) -> None: def _run_post_install_validation(self) -> None:
"""Run post-installation validation for all installed components""" """Run post-installation validation for all installed components"""
print("\nRunning post-installation validation...") print("\nRunning post-installation validation...")
all_valid = True all_valid = True
for name in self.installed_components: for name in self.installed_components:
component = self.components[name] component = self.components[name]
success, errors = component.validate_installation() success, errors = component.validate_installation()
if success: if success:
print(f"{name}: Valid") print(f"{name}: Valid")
else: else:
@@ -287,16 +297,20 @@ class Installer:
for error in errors: for error in errors:
print(f" - {error}") print(f" - {error}")
all_valid = False all_valid = False
if all_valid: if all_valid:
print("\nAll components validated successfully!") print("\nAll components validated successfully!")
else: else:
print("\nSome components failed validation. Check errors above.") print("\nSome components failed validation. Check errors above.")
def update_components(self, component_names: List[str], config: Dict[str, Any]) -> bool:
"""Alias for update operation (uses install logic)"""
return self.install_components(component_names, config)
def get_installation_summary(self) -> Dict[str, Any]: def get_installation_summary(self) -> Dict[str, Any]:
""" """
Get summary of installation results Get summary of installation results
Returns: Returns:
Dict with installation statistics and results Dict with installation statistics and results
""" """
@@ -308,3 +322,10 @@ class Installer:
'install_dir': str(self.install_dir), 'install_dir': str(self.install_dir),
'dry_run': self.dry_run 'dry_run': self.dry_run
} }
def get_update_summary(self) -> Dict[str, Any]:
return {
'updated': list(self.updated_components),
'failed': list(self.failed_components),
'backup_path': str(self.backup_path) if self.backup_path else None
}