at master 5.0 kB view raw
1import json 2import re 3import sys 4import subprocess 5import urllib.request 6 7from typing import Iterable, Optional, Tuple 8from urllib.request import urlopen 9from datetime import datetime 10 11# Number of spaces used for each indentation level 12JSON_INDENT = 4 13 14releases_json = None 15 16# Releases that have reached end-of-life no longer receive any updates 17# and it is rather pointless trying to update those. 18# 19# https://endoflife.date/electron 20def supported_version_range() -> range: 21 """Returns a range of electron releases that have not reached end-of-life yet""" 22 global releases_json 23 if releases_json is None: 24 releases_json = json.loads( 25 urlopen("https://endoflife.date/api/electron.json").read() 26 ) 27 supported_releases = [ 28 int(x["cycle"]) 29 for x in releases_json 30 if x["eol"] == False 31 or datetime.strptime(x["eol"], "%Y-%m-%d") > datetime.today() 32 ] 33 34 return range( 35 min(supported_releases), # incl. 36 # We have also packaged the beta release in nixpkgs, 37 # but it is not tracked by endoflife.date 38 max(supported_releases) + 2, # excl. 39 1, 40 ) 41 42def get_latest_version(major_version: str) -> Tuple[str, str]: 43 """Returns the latest version for a given major version""" 44 electron_releases: dict = json.loads( 45 urlopen("https://releases.electronjs.org/releases.json").read() 46 ) 47 major_version_releases = filter( 48 lambda item: item["version"].startswith(f"{major_version}."), electron_releases 49 ) 50 m = max(major_version_releases, key=lambda item: item["date"]) 51 52 rev = f"v{m['version']}" 53 return (m, rev) 54 55 56def load_info_json(path: str) -> dict: 57 """Load the contents of a JSON file 58 59 Args: 60 path: The path to the JSON file 61 62 Returns: An empty dict if the path does not exist, otherwise the contents of the JSON file. 63 """ 64 try: 65 with open(path, "r") as f: 66 return json.loads(f.read()) 67 except: 68 return {} 69 70 71def save_info_json(path: str, content: dict) -> None: 72 """Saves the given info to a JSON file 73 74 Args: 75 path: The path where the info should be saved 76 content: The content to be saved as JSON. 77 """ 78 with open(path, "w") as f: 79 f.write(json.dumps(content, indent=JSON_INDENT, default=vars, sort_keys=True)) 80 f.write("\n") 81 82 83def parse_cve_numbers(tag_name: str) -> Iterable[str]: 84 """Returns mentioned CVE numbers from a given release tag""" 85 cve_pattern = r"CVE-\d{4}-\d+" 86 url = f"https://api.github.com/repos/electron/electron/releases/tags/{tag_name}" 87 headers = { 88 "Accept": "application/vnd.github+json", 89 "X-GitHub-Api-Version": "2022-11-28", 90 } 91 request = urllib.request.Request(url=url, headers=headers) 92 release_note = "" 93 try: 94 with urlopen(request) as response: 95 release_note = json.loads(response.read().decode("utf-8"))["body"] 96 except: 97 print( 98 f"WARN: Fetching release note for {tag_name} from GitHub failed!", 99 file=sys.stderr, 100 ) 101 102 return sorted(re.findall(cve_pattern, release_note)) 103 104 105def commit_result( 106 package_name: str, old_version: Optional[str], new_version: str, path: str 107) -> None: 108 """Creates a git commit with a short description of the change 109 110 Args: 111 package_name: The package name, e.g. `electron-source.electron-{major_version}` 112 or `electron_{major_version}-bin` 113 114 old_version: Version number before the update. 115 Can be left empty when initializing a new release. 116 117 new_version: Version number after the update. 118 119 path: Path to the lockfile to be committed 120 """ 121 assert ( 122 isinstance(package_name, str) and len(package_name) > 0 123 ), "Argument `package_name` cannot be empty" 124 assert ( 125 isinstance(new_version, str) and len(new_version) > 0 126 ), "Argument `new_version` cannot be empty" 127 128 if old_version != new_version: 129 major_version = new_version.split(".")[0] 130 cve_fixes_text = "\n".join( 131 list( 132 map(lambda cve: f"- Fixes {cve}", parse_cve_numbers(f"v{new_version}")) 133 ) 134 ) 135 init_msg = f"init at {new_version}" 136 update_msg = f"{old_version} -> {new_version}" 137 diff = ( 138 f"- Diff: https://github.com/electron/electron/compare/refs/tags/v{old_version}...v{new_version}\n" 139 if old_version != None 140 else "" 141 ) 142 commit_message = f"""{package_name}: {update_msg if old_version != None else init_msg} 143 144- Changelog: https://github.com/electron/electron/releases/tag/v{new_version} 145{diff}{cve_fixes_text} 146""" 147 subprocess.run( 148 [ 149 "git", 150 "add", 151 path, 152 ] 153 ) 154 subprocess.run( 155 [ 156 "git", 157 "commit", 158 "-m", 159 commit_message, 160 ] 161 )