Browse Source

feat(dependencies): add support for semver tags

Carlo Sala 11 months ago
parent
commit
423b9a8ded

+ 1 - 0
.github/workflows/dependencies/requirements.txt

@@ -3,4 +3,5 @@ charset-normalizer==3.3.2
 idna==3.7
 idna==3.7
 PyYAML==6.0.1
 PyYAML==6.0.1
 requests==2.31.0
 requests==2.31.0
+semver==3.0.2
 urllib3==2.2.1
 urllib3==2.2.1

+ 56 - 6
.github/workflows/dependencies/updater.py

@@ -1,13 +1,15 @@
 import os
 import os
+import re
 import shutil
 import shutil
 import subprocess
 import subprocess
 import sys
 import sys
 import timeit
 import timeit
 from copy import deepcopy
 from copy import deepcopy
-from typing import Literal, NotRequired, TypedDict
+from typing import Literal, NotRequired, Optional, TypedDict
 
 
 import requests
 import requests
 import yaml
 import yaml
+from semver import Version
 
 
 # Get TMP_DIR variable from environment
 # Get TMP_DIR variable from environment
 TMP_DIR = os.path.join(os.environ.get("TMP_DIR", "/tmp"), "ohmyzsh")
 TMP_DIR = os.path.join(os.environ.get("TMP_DIR", "/tmp"), "ohmyzsh")
@@ -16,6 +18,35 @@ DEPS_YAML_FILE = ".github/dependencies.yml"
 # Dry run flag
 # Dry run flag
 DRY_RUN = os.environ.get("DRY_RUN", "0") == "1"
 DRY_RUN = os.environ.get("DRY_RUN", "0") == "1"
 
 
+# utils for tag comparison
+BASEVERSION = re.compile(
+    r"""[vV]?
+    (?P<major>(0|[1-9])\d*)
+    (\.
+    (?P<minor>(0|[1-9])\d*)
+    (\.
+    (?P<patch>(0|[1-9])\d*)
+    )?
+    )?
+    """,
+    re.VERBOSE,
+)
+
+
+def coerce(version: str) -> Optional[Version]:
+    match = BASEVERSION.search(version)
+    if not match:
+        return None
+
+    # BASEVERSION looks for `MAJOR.minor.patch` in the string given
+    # it fills with None if any of them is missing (for example `2.1`)
+    ver = {
+        key: 0 if value is None else value for key, value in match.groupdict().items()
+    }
+    # Version takes `major`, `minor`, `patch` arguments
+    ver = Version(**ver)  # pyright: ignore[reportArgumentType]
+    return ver
+
 
 
 class CodeTimer:
 class CodeTimer:
     def __init__(self, name=None):
     def __init__(self, name=None):
@@ -390,6 +421,11 @@ class GitHub:
 
 
         # Send a GET request to the GitHub API
         # Send a GET request to the GitHub API
         response = requests.get(url)
         response = requests.get(url)
+        current_version = coerce(current_tag)
+        if current_version is None:
+            raise ValueError(
+                f"Stored {current_version} from {repo} does not follow semver"
+            )
 
 
         # If the request was successful
         # If the request was successful
         if response.status_code == 200:
         if response.status_code == 200:
@@ -401,10 +437,27 @@ class GitHub:
                     "has_updates": False,
                     "has_updates": False,
                 }
                 }
 
 
-            latest_ref = data[-1]
+            latest_ref = None
+            latest_version: Optional[Version] = None
+            for ref in data:
+                # we find the tag since GitHub returns it as plain git ref
+                tag_version = coerce(ref["ref"].replace("refs/tags/", ""))
+                if tag_version is None:
+                    # we skip every tag that is not semver-complaint
+                    continue
+                if latest_version is None or tag_version.compare(latest_version) > 0:
+                    # if we have a "greater" semver version, set it as latest
+                    latest_version = tag_version
+                    latest_ref = ref
+
+            # raise if no valid semver tag is found
+            if latest_ref is None or latest_version is None:
+                raise ValueError(f"No tags following semver found in {repo}")
+
+            # we get the tag since GitHub returns it as plain git ref
             latest_tag = latest_ref["ref"].replace("refs/tags/", "")
             latest_tag = latest_ref["ref"].replace("refs/tags/", "")
 
 
-            if latest_tag == current_tag:
+            if latest_version.compare(current_version) <= 0:
                 return {
                 return {
                     "has_updates": False,
                     "has_updates": False,
                 }
                 }
@@ -424,9 +477,6 @@ class GitHub:
 
 
     @staticmethod
     @staticmethod
     def check_updates(repo, branch, version) -> UpdateStatusFalse | UpdateStatusTrue:
     def check_updates(repo, branch, version) -> UpdateStatusFalse | UpdateStatusTrue:
-        # TODO: add support for semver updating (based on tags)
-        # Check if upstream github repo has a new version
-        # GitHub API URL for comparing two commits
         url = f"https://api.github.com/repos/{repo}/compare/{version}...{branch}"
         url = f"https://api.github.com/repos/{repo}/compare/{version}...{branch}"
 
 
         # Send a GET request to the GitHub API
         # Send a GET request to the GitHub API