From 2777af675be04fa638ea3d5ee145b24148d66b62 Mon Sep 17 00:00:00 2001 From: Gabriel Freitas Date: Wed, 18 Jun 2025 13:02:41 -0300 Subject: [PATCH] feat: add support for multiple immutable release tag URLs in a repo Adds detection and version tracking for URLs like: - releases/download/{tag}/{asset} - releases/tag/{tag} This enables monorepo workflows where multiple mods share a repository but use immutable tags (e.g., mod-name__latest) instead of the automatic releases/latest endpoint. Uses asset creation timestamps to generate sequential versions in YYYYMMDD_HHMMSS format, ensuring each mod gets independent version tracking rather than sharing the repository's latest release version. Maintains full backward compatibility with existing URL patterns. --- .github/scripts/update_mod_versions.py | 53 ++++++++++++++++++++++++-- README.md | 7 +++- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/.github/scripts/update_mod_versions.py b/.github/scripts/update_mod_versions.py index 6b577d70..dbbc0ecd 100755 --- a/.github/scripts/update_mod_versions.py +++ b/.github/scripts/update_mod_versions.py @@ -29,12 +29,20 @@ def extract_repo_info(repo_url): VersionSource = Enum("VersionSource", [ ("RELEASE_TAG", "release"), ("HEAD", "commit"), + ("SPECIFIC_TAG", "specific_tag"), ]) -def get_version_string(source: Enum, owner, repo, start_timestamp, n = 1): +def get_version_string(source: Enum, owner, repo, start_timestamp, n = 1, tag_data=None): """Get the version string from a given GitHub repo.""" if source is VersionSource.RELEASE_TAG: url = f'https://api.github.com/repos/{owner}/{repo}/releases/latest' + elif source is VersionSource.SPECIFIC_TAG: + if not tag_data: + print(f"ERROR: SPECIFIC_TAG source requires tag_name") + return None + + tag_name = tag_data['name'] + url = f'https://api.github.com/repos/{owner}/{repo}/releases/tags/{tag_name}' else: if not source is VersionSource.HEAD: print(f"UNIMPLEMENTED(VersionSource): `{source}`,\nfalling back to `HEAD`") @@ -62,6 +70,29 @@ def get_version_string(source: Enum, owner, repo, start_timestamp, n = 1): # Return name of latest tag return data.get('tag_name') + elif source is VersionSource.SPECIFIC_TAG: + assets = data.get('assets', []) + if not assets: + print(f"⚠️ No assets found in release {tag_name}") + return None + + latest_created_at = "" + latest_asset = None + + for asset in assets: + created_at = asset.get('created_at', '') + if created_at > latest_created_at: + latest_created_at = created_at + latest_asset = asset.name + + # Convert 2099-12-31T01:02:03Z to 20991231_010203 + parts = latest_created_at.replace('Z', '').split('T') + date_part = parts[0].replace('-', '') # 20991231 + time_part = parts[1].replace(':', '') # 010203 + version = f"{date_part}_{time_part}" # 20991231_010203 + tag_data['file'] = latest_asset + return version + if data and len(data) > 0: # Return shortened commit hash (first 7 characters) return data[0]['sha'][:7] @@ -98,7 +129,7 @@ def get_version_string(source: Enum, owner, repo, start_timestamp, n = 1): print(f"Waiting {wait_time} seconds until next attempt...") time.sleep(wait_time) n += 1 - return get_version_string(source, owner, repo, start_timestamp, n) # Retry + return get_version_string(source, owner, repo, start_timestamp, n, tag_data=tag_data) # Retry else: print(f"Next attempt in {wait_time} seconds, but Action run time would exceed 1800 seconds - Stopping...") sys.exit(1) @@ -165,7 +196,22 @@ def process_mod(start_timestamp, name, meta_file): print("Download URL links to HEAD, checking latest commit...") source = VersionSource.HEAD new_version = get_version_string(VersionSource.HEAD, owner, repo, start_timestamp) + elif (("/releases/download/" in download_url and "/releases/latest/" not in download_url) or + ("/releases/tag/" in download_url and "/releases/tag/latest" not in download_url)): + print("Download URL links to specific tag, checking that tag...") + source = VersionSource.SPECIFIC_TAG + tag_data = {} + + if "/releases/download/" in download_url: + # Pattern: /releases/download/{tag}/{file} - tag is second-to-last + tag_data['name'] = download_url.split('/')[-2] + else: + # Pattern: /releases/tag/{tag} - tag is last + tag_data['name'] = download_url.split('/')[-1] + new_version = get_version_string( + source, owner, repo, start_timestamp, tag_data=tag_data + ) else: print("Checking releases for latest version tag...") source = VersionSource.RELEASE_TAG @@ -192,6 +238,8 @@ def process_mod(start_timestamp, name, meta_file): meta['version'] = new_version if "/archive/refs/tags/" in download_url: meta['downloadURL'] = f"{repo_url}/archive/refs/tags/{meta['version']}.zip" + elif source == VersionSource.SPECIFIC_TAG: + meta['download_url'] = f"{repo_url}/releases/download/{tag_data['name']}/{tag_data['file']}" with open(meta_file, 'w', encoding='utf-8') as f: # Preserve formatting with indentation @@ -241,4 +289,3 @@ if __name__ == "__main__": # Exit with status code 0 even if no updates were made sys.exit(0) - diff --git a/README.md b/README.md index 6e3cb4ac..46260db6 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,11 @@ This file stores essential metadata in JSON format. **Make sure you adhere to th - **version**: The version number of the mod files available at `downloadURL`. - *folderName*: (*Optional*) The name for the mod's install folder. This must be **unique**, and cannot contain characters `<` `>` `:` `"` `/` `\` `|` `?` `*` - *automatic-version-check*: (*Optional* but **recommended**) Set to `true` to let the Index automatically update the `version` field. - - Updates happen once every hour, by checking either your mod's latest Release **or** latest commit, depending on the `downloadURL`. - - Enable this option **only** if your `downloadURL` points to an automatically updating source, using a link to [releases/latest](https://docs.github.com/en/repositories/releasing-projects-on-github/linking-to-releases) (recommended), or a link to the [latest commit (HEAD)](https://docs.github.com/en/repositories/working-with-files/using-files/downloading-source-code-archives#source-code-archive-urls). + - Updates happen once every hour, by checking either your mod's latest Release, latest commit, or specific release tag, depending on the `downloadURL`. + - Enable this option **only** if your `downloadURL` points to an automatically updating source: + - **Latest release** (recommended): Using a link to [releases/latest](https://docs.github.com/en/repositories/releasing-projects-on-github/linking-to-releases) + - **Latest commit**: Using a link to the [latest commit (HEAD)](https://docs.github.com/en/repositories/working-with-files/using-files/downloading-source-code-archives#source-code-archive-urls) + - **Permanent release tag**: Using a link to a specific release tag where you upload new files: `https://github.com/author/repo/releases/tag/my-release-tag`. **(This will always use the latest uploaded file)**. ### 3. thumbnail.jpg (Optional) If included, this image will appear alongside your mod in the index. Maximum and recommended size is 1920x1080 pixels.