|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""update_versions.py |
| 3 | +
|
| 4 | +Fetch the latest released versions of: |
| 5 | +* PyPI package `kernel` |
| 6 | +* npm package `@onkernel/sdk` |
| 7 | +
|
| 8 | +and update every version constraint inside: |
| 9 | +* templates/python/*/pyproject.toml -> "kernel>=<latest>" |
| 10 | +* templates/typescript/*/package.json -> "@onkernel/sdk": ">=<latest>" |
| 11 | +
|
| 12 | +If a file is modified, it is overwritten in-place. The script exits with code 0 |
| 13 | +whether or not modifications were required. However, it prints a summary that is |
| 14 | +useful inside CI to decide if a commit is necessary. |
| 15 | +""" |
| 16 | +from __future__ import annotations |
| 17 | + |
| 18 | +import json |
| 19 | +import os |
| 20 | +import re |
| 21 | +import subprocess |
| 22 | +import sys |
| 23 | +from pathlib import Path |
| 24 | +from typing import List |
| 25 | + |
| 26 | +try: |
| 27 | + import requests # type: ignore |
| 28 | +except ImportError: |
| 29 | + subprocess.check_call([sys.executable, "-m", "pip", "install", "requests"]) |
| 30 | + import requests # type: ignore |
| 31 | + |
| 32 | +REPO_ROOT = Path(__file__).resolve().parent.parent |
| 33 | +PY_TEMPLATES_GLOB = REPO_ROOT / "templates" / "python" / "*" / "pyproject.toml" |
| 34 | +TS_TEMPLATES_GLOB = REPO_ROOT / "templates" / "typescript" / "*" / "package.json" |
| 35 | + |
| 36 | + |
| 37 | +# --------------------------------------------------------------------------- |
| 38 | +# Helpers to fetch latest versions |
| 39 | +# --------------------------------------------------------------------------- |
| 40 | + |
| 41 | +def _get_latest_pypi_version(package: str) -> str: |
| 42 | + url = f"https://pypi.org/pypi/{package}/json" |
| 43 | + resp = requests.get(url, timeout=10) |
| 44 | + resp.raise_for_status() |
| 45 | + data = resp.json() |
| 46 | + return data["info"]["version"] |
| 47 | + |
| 48 | + |
| 49 | +def _get_latest_npm_version(package: str) -> str: |
| 50 | + # NPM package names are url-encoded |
| 51 | + import urllib.parse as _up |
| 52 | + |
| 53 | + encoded = _up.quote(package, safe="") |
| 54 | + url = f"https://registry.npmjs.org/{encoded}" |
| 55 | + resp = requests.get(url, timeout=10) |
| 56 | + resp.raise_for_status() |
| 57 | + data = resp.json() |
| 58 | + return data["dist-tags"]["latest"] |
| 59 | + |
| 60 | + |
| 61 | +# --------------------------------------------------------------------------- |
| 62 | +# Updaters |
| 63 | +# --------------------------------------------------------------------------- |
| 64 | + |
| 65 | +_KERN_DEP_REGEX = re.compile(r"(\"kernel)([^\"]*)(\")") |
| 66 | + |
| 67 | + |
| 68 | +def _update_pyproject(file_path: Path, new_version: str) -> bool: |
| 69 | + """Return True if file changed.""" |
| 70 | + text = file_path.read_text() |
| 71 | + |
| 72 | + # Replace any appearance like "kernel>=0.8.0", "kernel==0.5.0", "kernel~=0.7" |
| 73 | + new_constraint = f'"kernel>={new_version}"' |
| 74 | + new_text = re.sub(r'"kernel[<>=~!0-9.]*"', new_constraint, text) |
| 75 | + |
| 76 | + if new_text != text: |
| 77 | + file_path.write_text(new_text) |
| 78 | + return True |
| 79 | + return False |
| 80 | + |
| 81 | + |
| 82 | +def _update_package_json(file_path: Path, new_version: str) -> bool: |
| 83 | + data = json.loads(file_path.read_text()) |
| 84 | + changed = False |
| 85 | + |
| 86 | + for section in ("dependencies", "peerDependencies" , "devDependencies"): |
| 87 | + deps = data.get(section) |
| 88 | + if deps and "@onkernel/sdk" in deps: |
| 89 | + if deps["@onkernel/sdk"] != f">={new_version}": |
| 90 | + deps["@onkernel/sdk"] = f">={new_version}" |
| 91 | + changed = True |
| 92 | + |
| 93 | + if changed: |
| 94 | + file_path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n") |
| 95 | + return changed |
| 96 | + |
| 97 | + |
| 98 | +# --------------------------------------------------------------------------- |
| 99 | +# Main execution |
| 100 | +# --------------------------------------------------------------------------- |
| 101 | + |
| 102 | +def main() -> None: |
| 103 | + latest_kernel = _get_latest_pypi_version("kernel") |
| 104 | + latest_sdk = _get_latest_npm_version("@onkernel/sdk") |
| 105 | + |
| 106 | + print(f"Latest kernel version on PyPI: {latest_kernel}") |
| 107 | + print(f"Latest @onkernel/sdk version on npm: {latest_sdk}") |
| 108 | + |
| 109 | + modified_files: List[Path] = [] |
| 110 | + |
| 111 | + # Python templates |
| 112 | + for file_path in REPO_ROOT.glob("templates/python/*/pyproject.toml"): |
| 113 | + if _update_pyproject(file_path, latest_kernel): |
| 114 | + modified_files.append(file_path.relative_to(REPO_ROOT)) |
| 115 | + |
| 116 | + # Typescript templates |
| 117 | + for file_path in REPO_ROOT.glob("templates/typescript/*/package.json"): |
| 118 | + if _update_package_json(file_path, latest_sdk): |
| 119 | + modified_files.append(file_path.relative_to(REPO_ROOT)) |
| 120 | + |
| 121 | + if modified_files: |
| 122 | + print("Updated the following files:") |
| 123 | + for p in modified_files: |
| 124 | + print(f" - {p}") |
| 125 | + else: |
| 126 | + print("All template files already up-to-date. No changes made.") |
| 127 | + |
| 128 | + |
| 129 | +if __name__ == "__main__": |
| 130 | + main() |
0 commit comments