version_manager.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. """
  2. Version management for Dune Weaver
  3. Handles current version reading and GitHub API integration for latest version checking
  4. """
  5. import asyncio
  6. import aiohttp
  7. import json
  8. import os
  9. from pathlib import Path
  10. from typing import Dict, Optional
  11. import logging
  12. logger = logging.getLogger(__name__)
  13. class VersionManager:
  14. def __init__(self):
  15. self.repo_owner = "tuanchris"
  16. self.repo_name = "dune-weaver"
  17. self.github_api_url = f"https://api.github.com/repos/{self.repo_owner}/{self.repo_name}"
  18. self._current_version = None
  19. def get_current_version(self) -> str:
  20. """Read current version from VERSION file"""
  21. if self._current_version is None:
  22. try:
  23. version_file = Path(__file__).parent.parent.parent / "VERSION"
  24. if version_file.exists():
  25. self._current_version = version_file.read_text().strip()
  26. else:
  27. logger.warning("VERSION file not found, using default version")
  28. self._current_version = "1.0.0"
  29. except Exception as e:
  30. logger.error(f"Error reading VERSION file: {e}")
  31. self._current_version = "1.0.0"
  32. return self._current_version
  33. async def get_latest_release(self) -> Dict[str, any]:
  34. """Get latest release info from GitHub API"""
  35. try:
  36. async with aiohttp.ClientSession() as session:
  37. async with session.get(
  38. f"{self.github_api_url}/releases/latest",
  39. timeout=aiohttp.ClientTimeout(total=10)
  40. ) as response:
  41. if response.status == 200:
  42. data = await response.json()
  43. return {
  44. "version": data.get("tag_name", "").lstrip("v"),
  45. "name": data.get("name", ""),
  46. "published_at": data.get("published_at", ""),
  47. "html_url": data.get("html_url", ""),
  48. "body": data.get("body", ""),
  49. "prerelease": data.get("prerelease", False)
  50. }
  51. elif response.status == 404:
  52. # No releases found
  53. logger.info("No releases found on GitHub")
  54. return None
  55. else:
  56. logger.warning(f"GitHub API returned status {response.status}")
  57. return None
  58. except asyncio.TimeoutError:
  59. logger.warning("Timeout while fetching latest release from GitHub")
  60. return None
  61. except Exception as e:
  62. logger.error(f"Error fetching latest release: {e}")
  63. return None
  64. def compare_versions(self, version1: str, version2: str) -> int:
  65. """Compare two semantic versions. Returns -1, 0, or 1"""
  66. try:
  67. # Parse semantic versions (e.g., "1.2.3")
  68. v1_parts = [int(x) for x in version1.split('.')]
  69. v2_parts = [int(x) for x in version2.split('.')]
  70. # Pad shorter version with zeros
  71. max_len = max(len(v1_parts), len(v2_parts))
  72. v1_parts.extend([0] * (max_len - len(v1_parts)))
  73. v2_parts.extend([0] * (max_len - len(v2_parts)))
  74. if v1_parts < v2_parts:
  75. return -1
  76. elif v1_parts > v2_parts:
  77. return 1
  78. else:
  79. return 0
  80. except (ValueError, AttributeError):
  81. logger.warning(f"Invalid version format: {version1} vs {version2}")
  82. return 0
  83. async def get_version_info(self) -> Dict[str, any]:
  84. """Get complete version information"""
  85. current = self.get_current_version()
  86. latest_release = await self.get_latest_release()
  87. if latest_release:
  88. latest = latest_release["version"]
  89. comparison = self.compare_versions(current, latest)
  90. update_available = comparison < 0
  91. else:
  92. latest = current # Fallback if no releases found
  93. update_available = False
  94. return {
  95. "current": current,
  96. "latest": latest,
  97. "update_available": update_available,
  98. "latest_release": latest_release
  99. }
  100. # Global instance
  101. version_manager = VersionManager()