Skip to content

Commit 5a6316b

Browse files
committed
Automate changelog + version updates
1 parent 63d3408 commit 5a6316b

File tree

5 files changed

+216
-7
lines changed

5 files changed

+216
-7
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ dev-base = [
7676
"types-tqdm",
7777
"types-python-dateutil",
7878
"python-dotenv==1.1.0",
79+
"types-colorama",
7980
]
8081
test = [
8182
"pytest == 8.3.3",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Changes in <VERSION>
2+
3+
## Breaking changes
4+
5+
6+
## New features
7+
8+
9+
## Bug fixes
10+
11+
12+
## Improvements
13+
14+
15+
## Other changes
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#!/usr/bin/env python
2+
from __future__ import annotations
3+
4+
import re
5+
from pathlib import Path
6+
7+
from graphdatascience.semantic_version.semantic_version import SemanticVersion
8+
9+
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
10+
VERSION_FILE = REPO_ROOT / "src" / "graphdatascience" / "version.py"
11+
12+
VERSION_REGEX = r'__version__ = "([^"]*)"'
13+
14+
15+
class PythonLibraryVersion:
16+
def __init__(self, major: int, minor: int, patch: int, suffix: str = ""):
17+
self.major = major
18+
self.minor = minor
19+
self.patch = patch
20+
self.suffix = suffix
21+
22+
@classmethod
23+
def from_string(cls, version: str) -> PythonLibraryVersion:
24+
parts = version.split("a")
25+
if len(parts) == 2:
26+
version, alpha_version = parts
27+
suffix = f"a{alpha_version}"
28+
else:
29+
version = parts[0]
30+
suffix = ""
31+
32+
semantic_version = SemanticVersion.from_string(version)
33+
return cls(semantic_version.major, semantic_version.minor, semantic_version.patch, suffix)
34+
35+
def is_alpha(self) -> bool:
36+
return self.suffix.startswith("a")
37+
38+
def alpha(self) -> int | None:
39+
if self.is_alpha():
40+
return int(self.suffix[1:])
41+
return None
42+
43+
def major_minor(self) -> str:
44+
return f"{self.major}.{self.minor}"
45+
46+
def copy(self) -> PythonLibraryVersion:
47+
return PythonLibraryVersion(
48+
self.major,
49+
self.minor,
50+
self.patch,
51+
self.suffix,
52+
)
53+
54+
def __str__(self) -> str:
55+
if self.patch > 0:
56+
return f"{self.major}.{self.minor}.{self.patch}{self.suffix}"
57+
else:
58+
return f"{self.major}.{self.minor}{self.suffix}"
59+
60+
61+
def read_library_version() -> PythonLibraryVersion:
62+
version_file = VERSION_FILE.read_text()
63+
version_regex = r'^__version__\s*=\s*"([^"]*)"'
64+
65+
match = re.search(version_regex, version_file)
66+
if not match:
67+
raise ValueError("Could not find version string in version.py")
68+
69+
return PythonLibraryVersion.from_string(match.group(1))
70+
71+
72+
def bump_version(current_version: PythonLibraryVersion) -> PythonLibraryVersion:
73+
new_version = current_version.copy()
74+
if alpha_version := current_version.alpha():
75+
# Alpha release - bump alpha path
76+
next_alpha = alpha_version + 1
77+
new_version.suffix = f"a{next_alpha}"
78+
else:
79+
new_version.minor += 1
80+
return new_version
81+
82+
83+
def update_version_py(new_version: PythonLibraryVersion) -> None:
84+
content = VERSION_FILE.read_text()
85+
86+
updated = re.sub(VERSION_REGEX, f'__version__ = "{new_version.major_minor()}"', content)
87+
88+
VERSION_FILE.write_text(updated)
89+
print(f"✅ Updated {VERSION_FILE} to version {new_version}")
90+
91+
92+
def update_changelog(new_version: PythonLibraryVersion) -> None:
93+
changelog_file = REPO_ROOT / "changelog.md"
94+
95+
template = Path(__file__).parent / "changelog.template"
96+
new_changelog_body = template.read_text().replace("<VERSION>", str(new_version))
97+
98+
changelog_file.write_text(new_changelog_body)
99+
100+
print(f"✅ Updated {changelog_file} for version {new_version}")
101+
102+
103+
def update_publish_yml(repo_root: Path, released_version: PythonLibraryVersion) -> None:
104+
"""Add new release branch to publish.yml."""
105+
publish_file = repo_root / "doc" / "publish.yml"
106+
content = publish_file.read_text()
107+
108+
# Extract major.minor from released version
109+
new_branch = f"{released_version.major_minor()}"
110+
111+
# Update branches list
112+
updated = re.sub(r"(branches:\s*\[)([^\]]*)", lambda m: f"{m.group(1)}{m.group(2)}, '{new_branch}'", content)
113+
114+
publish_file.write_text(updated)
115+
print(f"✓ Updated {publish_file.relative_to(repo_root)} - added branch '{new_branch}'")
116+
117+
118+
def update_antora_yml(repo_root: Path, new_version: str) -> None:
119+
"""Update version in antora.yml."""
120+
antora_file = repo_root / "doc" / "antora.yml"
121+
content = antora_file.read_text()
122+
123+
updated = re.sub(r"version: '[^']*'", f"version: '{new_version}'", content)
124+
updated = re.sub(r"docs-version: '[^']*'", f"docs-version: '{new_version}'", updated)
125+
126+
antora_file.write_text(updated)
127+
print(f"✓ Updated {antora_file.relative_to(repo_root)} to version {new_version}")
128+
129+
130+
def update_package_json(repo_root: Path, new_version: str) -> None:
131+
"""Update version in package.json."""
132+
package_file = repo_root / "doc" / "package.json"
133+
content = package_file.read_text()
134+
135+
# Set to preview version
136+
preview_version = f"{new_version}-preview"
137+
updated = re.sub(r'"version":\s*"[^"]*"', f'"version": "{preview_version}"', content)
138+
139+
package_file.write_text(updated)
140+
print(f"✓ Updated {package_file.relative_to(repo_root)} to version {preview_version}")
141+
142+
143+
def main() -> None:
144+
# Get current version
145+
current_version = read_library_version()
146+
147+
print(f"Current version: {current_version}")
148+
149+
# Calculate next version
150+
next_version = bump_version(current_version)
151+
print(f"Next version: {next_version}")
152+
153+
print("\nStarting post-release tasks...")
154+
155+
update_version_py(next_version)
156+
157+
if not current_version.is_alpha():
158+
update_changelog(next_version)
159+
# update_publish_yml(current_version)
160+
# update_antora_yml(next_version)
161+
# TODO update preview.yml
162+
# update_package_json(next_version)
163+
164+
# update_installation_adoc(current_version, next_version)
165+
166+
print("\n✅ Post-release tasks completed!")
167+
print("\nNext steps:")
168+
print("* Review the changes")
169+
if not current_version.is_alpha():
170+
print("* Update installation.adoc")
171+
print(f"* Commit with message: 'Prepare for {next_version} development'")
172+
print("* Push to main branch")
173+
174+
175+
if __name__ == "__main__":
176+
main()
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env python
22

3-
from pathlib import Path
43
import re
5-
import requests
4+
from pathlib import Path
5+
66
import colorama
7+
import requests
78

89
from graphdatascience.server_version.server_version import ServerVersion
910

@@ -41,6 +42,7 @@ def latest_released_plugin_version() -> ServerVersion:
4142

4243
def verify_installation_docs(repo_dir: Path, client_version: str, min_server_version: str) -> None:
4344
latest_server_version = latest_released_plugin_version()
45+
print("Verifying installation.adoc...")
4446
print(f"Latest released GDS Plugin version: `{latest_server_version}`")
4547

4648
installation_doc_path = repo_dir / "doc" / "modules" / "ROOT" / "pages" / "installation.adoc"
@@ -86,11 +88,15 @@ def verify_installation_docs(repo_dir: Path, client_version: str, min_server_ver
8688
f"Found entry:\n{latest_documented_server_compat}"
8789
)
8890

91+
print(
92+
f"✅ installation.adoc is up to date with client version {stable_library_version} and server version {min_server_version}"
93+
)
94+
8995

9096
def main() -> None:
91-
print("Client Pre-Release Checker")
97+
print(f"{colorama.Style.BRIGHT}Client Pre-Release Checker{colorama.Style.RESET_ALL}")
9298

93-
repo_dir = Path(__file__).parent.parent
99+
repo_dir = Path(__file__).parent.parent.parent
94100
version = read_library_version(repo_dir)
95101
print(f"Version to be released: `{colorama.Fore.GREEN}{version}{colorama.Style.RESET_ALL}`")
96102

src/graphdatascience/semantic_version/semantic_version.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,29 @@ class SemanticVersion:
1212
A representation of a semantic version, such as for python packages.
1313
"""
1414

15-
def __init__(self, major: int, minor: int, patch: int):
15+
def __init__(self, major: int, minor: int, patch: int | None = None):
1616
self.major = major
1717
self.minor = minor
18+
19+
if patch is None:
20+
patch = 0
1821
self.patch = patch
1922

2023
@classmethod
2124
def from_string(cls, version: str) -> SemanticVersion:
22-
server_version_match = re.search(r"^(\d+)\.(\d+)\.(\d+)", version)
25+
server_version_match = re.search(r"^(\d+)\.(\d+)\.?(\d+)?", version)
2326
if not server_version_match:
2427
raise InvalidServerVersionError(f"{version} is not a valid semantic version")
2528

26-
return cls(*map(int, server_version_match.groups()))
29+
major = int(server_version_match.group(1))
30+
minor = int(server_version_match.group(2))
31+
patch = int(server_version_match.group(3)) if server_version_match.group(3) is not None else 0
32+
33+
return cls(
34+
major=major,
35+
minor=minor,
36+
patch=patch,
37+
)
2738

2839
def __lt__(self, other: SemanticVersion) -> bool:
2940
if self.major != other.major:

0 commit comments

Comments
 (0)