File
Blob: tools/update_node_version.py
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | Script to update Node.js version in node-version.h based on GitHub releases. |
| 4 | |
| 5 | This script: |
| 6 | 1. Fetches Node.js version tags from GitHub |
| 7 | 2. Finds the maximum even major version |
| 8 | 3. Looks for the latest version in the (max-2) series |
| 9 | 4. Updates the node-version.h file if needed |
| 10 | """ |
| 11 | |
| 12 | import json |
| 13 | import re |
| 14 | import sys |
| 15 | import urllib.request |
| 16 | from pathlib import Path |
| 17 | |
| 18 | |
| 19 | def fetch_nodejs_tags(): |
| 20 | """Fetch Node.js version tags from GitHub API.""" |
| 21 | url = "https://api.github.com/repos/nodejs/node/tags" |
| 22 | headers = { |
| 23 | "User-Agent": "workerd-node-version-updater", |
| 24 | "Accept": "application/vnd.github.v3+json", |
| 25 | } |
| 26 | |
| 27 | all_tags = [] |
| 28 | page = 1 |
| 29 | |
| 30 | while True: |
| 31 | req = urllib.request.Request(f"{url}?page={page}&per_page=100", headers=headers) |
| 32 | try: |
| 33 | with urllib.request.urlopen(req) as response: |
| 34 | data = json.loads(response.read().decode()) |
| 35 | if not data: |
| 36 | break |
| 37 | all_tags.extend(data) |
| 38 | page += 1 |
| 39 | except Exception as e: |
| 40 | print(f"Error fetching tags: {e}", file=sys.stderr) |
| 41 | sys.exit(1) |
| 42 | |
| 43 | return all_tags |
| 44 | |
| 45 | |
| 46 | def parse_version(version_str): |
| 47 | """Parse a version string like 'v22.17.0' into (major, minor, patch).""" |
| 48 | match = re.match(r"^v(\d+)\.(\d+)\.(\d+)$", version_str) |
| 49 | if match: |
| 50 | return tuple(map(int, match.groups())) |
| 51 | return None |
| 52 | |
| 53 | |
| 54 | def find_target_version(tags): |
| 55 | """Find the target Node.js version based on the algorithm.""" |
| 56 | # Extract valid versions |
| 57 | versions = [] |
| 58 | for tag in tags: |
| 59 | version = parse_version(tag["name"]) |
| 60 | if version: |
| 61 | versions.append((version, tag["name"])) |
| 62 | |
| 63 | if not versions: |
| 64 | print("No valid versions found", file=sys.stderr) |
| 65 | sys.exit(1) |
| 66 | |
| 67 | # Sort versions |
| 68 | versions.sort(reverse=True) |
| 69 | |
| 70 | # Find maximum even major version |
| 71 | max_even_major = None |
| 72 | for (major, _minor, _patch), _ in versions: |
| 73 | if major % 2 == 0: |
| 74 | max_even_major = major |
| 75 | break |
| 76 | |
| 77 | if max_even_major is None: |
| 78 | print("No even major version found", file=sys.stderr) |
| 79 | sys.exit(1) |
| 80 | |
| 81 | # Calculate target major version (max_even - 2) |
| 82 | target_major = max_even_major - 2 |
| 83 | |
| 84 | # Find latest version in target major series |
| 85 | for (major, _minor, _patch), tag_name in versions: |
| 86 | if major == target_major: |
| 87 | return tag_name[1:] # Remove 'v' prefix |
| 88 | |
| 89 | print(f"No version found for major version {target_major}", file=sys.stderr) |
| 90 | sys.exit(1) |
| 91 | |
| 92 | |
| 93 | def update_header_file(file_path, new_version): |
| 94 | """Update the node-version.h file with the new version.""" |
| 95 | path = Path(file_path) |
| 96 | content = path.read_text() |
| 97 | |
| 98 | # Replace the version string |
| 99 | # Match: static constexpr kj::StringPtr nodeVersion = "X.Y.Z"_kj; |
| 100 | pattern = r'static constexpr kj::StringPtr nodeVersion = "[^"]+"_kj;' |
| 101 | replacement = f'static constexpr kj::StringPtr nodeVersion = "{new_version}"_kj;' |
| 102 | new_content = re.sub(pattern, replacement, content) |
| 103 | |
| 104 | if new_content != content: |
| 105 | path.write_text(new_content) |
| 106 | return True |
| 107 | return False |
| 108 | |
| 109 | |
| 110 | def main(): |
| 111 | if len(sys.argv) != 2: |
| 112 | print("Usage: update_node_version.py <output_file>", file=sys.stderr) |
| 113 | sys.exit(1) |
| 114 | |
| 115 | output_file = sys.argv[1] |
| 116 | |
| 117 | print("Fetching Node.js versions from GitHub...") |
| 118 | tags = fetch_nodejs_tags() |
| 119 | |
| 120 | print("Finding target version...") |
| 121 | target_version = find_target_version(tags) |
| 122 | print(f"Target version: {target_version}") |
| 123 | |
| 124 | # Update the header file |
| 125 | if update_header_file(output_file, target_version): |
| 126 | print(f"Updated {output_file} with version {target_version}") |
| 127 | else: |
| 128 | print(f"No changes needed - already at version {target_version}") |
| 129 | |
| 130 | |
| 131 | if __name__ == "__main__": |
| 132 | main() |