version_manager.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  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. async def get_current_version(self) -> str:
  20. """Read current version from VERSION file (async)"""
  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 = await asyncio.to_thread(version_file.read_text)
  26. self._current_version = self._current_version.strip()
  27. else:
  28. logger.warning("VERSION file not found, using default version")
  29. self._current_version = "1.0.0"
  30. except Exception as e:
  31. logger.error(f"Error reading VERSION file: {e}")
  32. self._current_version = "1.0.0"
  33. return self._current_version
  34. async def get_latest_release(self) -> Dict[str, any]:
  35. """Get latest release info from GitHub API"""
  36. try:
  37. async with aiohttp.ClientSession() as session:
  38. async with session.get(
  39. f"{self.github_api_url}/releases/latest",
  40. timeout=aiohttp.ClientTimeout(total=10)
  41. ) as response:
  42. if response.status == 200:
  43. data = await response.json()
  44. return {
  45. "version": data.get("tag_name", "").lstrip("v"),
  46. "name": data.get("name", ""),
  47. "published_at": data.get("published_at", ""),
  48. "html_url": data.get("html_url", ""),
  49. "body": data.get("body", ""),
  50. "prerelease": data.get("prerelease", False)
  51. }
  52. elif response.status == 404:
  53. # No releases found
  54. logger.info("No releases found on GitHub")
  55. return None
  56. else:
  57. logger.warning(f"GitHub API returned status {response.status}")
  58. return None
  59. except asyncio.TimeoutError:
  60. logger.warning("Timeout while fetching latest release from GitHub")
  61. return None
  62. except Exception as e:
  63. logger.error(f"Error fetching latest release: {e}")
  64. return None
  65. def compare_versions(self, version1: str, version2: str) -> int:
  66. """Compare two semantic versions. Returns -1, 0, or 1"""
  67. try:
  68. # Parse semantic versions (e.g., "1.2.3")
  69. v1_parts = [int(x) for x in version1.split('.')]
  70. v2_parts = [int(x) for x in version2.split('.')]
  71. # Pad shorter version with zeros
  72. max_len = max(len(v1_parts), len(v2_parts))
  73. v1_parts.extend([0] * (max_len - len(v1_parts)))
  74. v2_parts.extend([0] * (max_len - len(v2_parts)))
  75. if v1_parts < v2_parts:
  76. return -1
  77. elif v1_parts > v2_parts:
  78. return 1
  79. else:
  80. return 0
  81. except (ValueError, AttributeError):
  82. logger.warning(f"Invalid version format: {version1} vs {version2}")
  83. return 0
  84. async def get_version_info(self) -> Dict[str, any]:
  85. """Get complete version information"""
  86. current = await self.get_current_version()
  87. latest_release = await self.get_latest_release()
  88. if latest_release:
  89. latest = latest_release["version"]
  90. comparison = self.compare_versions(current, latest)
  91. update_available = comparison < 0
  92. else:
  93. latest = current # Fallback if no releases found
  94. update_available = False
  95. return {
  96. "current": current,
  97. "latest": latest,
  98. "update_available": update_available,
  99. "latest_release": latest_release
  100. }
  101. # Global instance
  102. version_manager = VersionManager()