88
99
1010def validate_download_url (url : str , owner : str , repo : str , tag : str , filename : str ) -> bool :
11- """Validate that download_url is well-formed and points to expected release asset."""
11+ """Validate that download_url is well-formed and points to a release asset.
12+
13+ Note: GitHub may initially serve assets via temporary "untagged" URLs.
14+ We validate the basic structure and accept it, knowing we'll construct
15+ the final URL ourselves to avoid issues with ephemeral URLs.
16+ """
1217 if not url or not url .strip ():
1318 return False
1419
15- # Must be HTTPS
20+ # Must be HTTPS and from GitHub releases
1621 if not url .startswith ("https://github.com/" ):
1722 return False
1823
19- # Must match pattern: https://github.com/{owner}/{repo}/releases/download/{tag}/{filename}
20- expected_prefix = f"https://github.com/{ owner } /{ repo } /releases/download/{ tag } /"
21- if not url .startswith (expected_prefix ):
22- return False
23-
24- # Extract asset name from URL and verify it matches expected filename
25- asset_name = url [len (expected_prefix ):]
26- if asset_name != filename :
27- return False
28-
29- # Reject untagged releases (ephemeral URLs)
30- if "/releases/download/untagged-" in url :
24+ # Must be from the correct owner/repo releases
25+ expected_base = f"https://github.com/{ owner } /{ repo } /releases/download/"
26+ if not url .startswith (expected_base ):
3127 return False
3228
29+ # URL structure is valid - we'll construct the final URL ourselves
30+ # to avoid issues with temporary "untagged-" URLs from GitHub
3331 return True
3432
3533
@@ -83,7 +81,7 @@ def build_manifest_entries(tag: str, assets: List[Dict[str, Any]], owner: str, r
8381
8482 download_url = asset .get ("browser_download_url" , "" )
8583
86- # Validate URL
84+ # Validate URL structure (basic sanity check)
8785 if not validate_download_url (download_url , owner , repo , tag , name ):
8886 errors .append (
8987 f"Invalid download_url for '{ name } ': expected "
@@ -92,14 +90,18 @@ def build_manifest_entries(tag: str, assets: List[Dict[str, Any]], owner: str, r
9290 )
9391 continue
9492
93+ # Construct the permanent, tagged download URL to avoid "untagged-*" ephemeral URLs
94+ # GitHub may initially serve assets via temporary URLs, so we construct the final one
95+ final_download_url = f"https://github.com/{ owner } /{ repo } /releases/download/{ tag } /{ name } "
96+
9597 entries .append (
9698 {
9799 "version" : tag ,
98100 "filename" : name ,
99101 "arch" : parsed ["arch" ],
100102 "platform" : parsed ["platform" ],
101103 "platform_version" : parsed ["platform_version" ],
102- "download_url" : download_url ,
104+ "download_url" : final_download_url ,
103105 }
104106 )
105107
0 commit comments