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