Skip to content

Commit 2141b7a

Browse files
committed
✨ Integrate automatic changelog generation in release script
Implement automatic changelog generation during release process This change enhances the release workflow by leveraging the recently added version name feature: - Add get_previous_tag() to determine the previous release tag - Implement generate_changelog() to create changelog entries - Update git commit process to include CHANGELOG.md - Improve code formatting with consistent function spacing - Add error handling for subprocess calls with check=False - Fix file handling with proper encoding parameters This creates a seamless release experience where version bumping and changelog generation happen in a single coordinated workflow.
1 parent 67d9ead commit 2141b7a

File tree

1 file changed

+110
-24
lines changed

1 file changed

+110
-24
lines changed

Diff for: scripts/release.py

+110-24
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,40 @@
3636
# Gradient colors for the banner
3737
GRADIENT_COLORS = [
3838
(138, 43, 226), # BlueViolet
39-
(75, 0, 130), # Indigo
40-
(0, 191, 255), # DeepSkyBlue
39+
(75, 0, 130), # Indigo
40+
(0, 191, 255), # DeepSkyBlue
4141
(30, 144, 255), # DodgerBlue
4242
(138, 43, 226), # BlueViolet
43-
(75, 0, 130), # Indigo
44-
(0, 191, 255), # DeepSkyBlue
43+
(75, 0, 130), # Indigo
44+
(0, 191, 255), # DeepSkyBlue
4545
]
4646

47+
4748
def print_colored(message: str, color: str) -> None:
4849
"""Print a message with a specific color."""
4950
print(f"{color}{message}{COLOR_RESET}")
5051

52+
5153
def print_step(step: str) -> None:
5254
"""Print a step in the process with a specific color."""
5355
print_colored(f"\n{step}", COLOR_STEP)
5456

57+
5558
def print_error(message: str) -> None:
5659
"""Print an error message with a specific color."""
5760
print_colored(f"❌ Error: {message}", COLOR_ERROR)
5861

62+
5963
def print_success(message: str) -> None:
6064
"""Print a success message with a specific color."""
6165
print_colored(f"✅ {message}", COLOR_SUCCESS)
6266

67+
6368
def print_warning(message: str) -> None:
6469
"""Print a warning message with a specific color."""
6570
print_colored(f"⚠️ {message}", COLOR_WARNING)
6671

72+
6773
def generate_gradient(colors: List[Tuple[int, int, int]], steps: int) -> List[str]:
6874
"""Generate a list of color codes for a smooth multi-color gradient."""
6975
gradient = []
@@ -82,28 +88,33 @@ def generate_gradient(colors: List[Tuple[int, int, int]], steps: int) -> List[st
8288

8389
return gradient
8490

91+
8592
def strip_ansi(text: str) -> str:
8693
"""Remove ANSI color codes from a string."""
8794
ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
8895
return ansi_escape.sub("", text)
8996

97+
9098
def apply_gradient(text: str, gradient: List[str], line_number: int) -> str:
9199
"""Apply gradient colors diagonally to text."""
92100
return "".join(
93101
f"{gradient[(i + line_number) % len(gradient)]}{char}"
94102
for i, char in enumerate(text)
95103
)
96104

105+
97106
def center_text(text: str, width: int) -> str:
98107
"""Center text, accounting for ANSI color codes and Unicode widths."""
99108
visible_length = wcswidth(strip_ansi(text))
100109
padding = (width - visible_length) // 2
101110
return f"{' ' * padding}{text}{' ' * (width - padding - visible_length)}"
102111

112+
103113
def center_block(block: List[str], width: int) -> List[str]:
104114
"""Center a block of text within a given width."""
105115
return [center_text(line, width) for line in block]
106116

117+
107118
def create_banner() -> str:
108119
"""Create a beautiful cosmic-themed banner with diagonal gradient."""
109120
banner_width = 80
@@ -133,57 +144,84 @@ def create_banner() -> str:
133144

134145
release_manager_text = COLOR_STEP + "Release Manager"
135146

136-
banner.extend([
137-
f"{COLOR_BORDER}{'─' * (banner_width - 2)}╯",
138-
center_text(f"{COLOR_STAR}∴。  ・゚*。☆ {release_manager_text}{COLOR_STAR} ☆。*゚・  。∴", banner_width),
139-
center_text(f"{COLOR_STAR}・ 。 ☆ ∴。  ・゚*。★・ ∴。  ・゚*。☆ ・ 。 ☆ ∴。", banner_width),
140-
])
147+
banner.extend(
148+
[
149+
f"{COLOR_BORDER}{'─' * (banner_width - 2)}╯",
150+
center_text(
151+
f"{COLOR_STAR}∴。  ・゚*。☆ {release_manager_text}{COLOR_STAR} ☆。*゚・  。∴",
152+
banner_width,
153+
),
154+
center_text(
155+
f"{COLOR_STAR}・ 。 ☆ ∴。  ・゚*。★・ ∴。  ・゚*。☆ ・ 。 ☆ ∴。", banner_width
156+
),
157+
]
158+
)
141159

142160
return "\n".join(banner)
143161

162+
144163
def print_logo() -> None:
145164
"""Print the banner/logo for the release manager."""
146165
print(create_banner())
147166

167+
148168
def check_tool_installed(tool_name: str) -> None:
149169
"""Check if a tool is installed."""
150170
if shutil.which(tool_name) is None:
151171
print_error(f"{tool_name} is not installed. Please install it and try again.")
152172
sys.exit(1)
153173

174+
154175
def check_branch() -> None:
155176
"""Ensure we're on the main branch."""
156-
current_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).decode().strip()
177+
current_branch = (
178+
subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
179+
.decode()
180+
.strip()
181+
)
157182
if current_branch != "main":
158183
print_error("You must be on the main branch to release.")
159184
sys.exit(1)
160185

186+
161187
def check_uncommitted_changes() -> None:
162188
"""Check for uncommitted changes."""
163-
result = subprocess.run(["git", "diff-index", "--quiet", "HEAD", "--"], capture_output=True)
189+
result = subprocess.run(
190+
["git", "diff-index", "--quiet", "HEAD", "--"], capture_output=True, check=False
191+
)
164192
if result.returncode != 0:
165-
print_error("You have uncommitted changes. Please commit or stash them before releasing.")
193+
print_error(
194+
"You have uncommitted changes. Please commit or stash them before releasing."
195+
)
166196
sys.exit(1)
167197

198+
168199
def get_current_version() -> str:
169200
"""Get the current version from Cargo.toml."""
170-
with open("Cargo.toml", "r") as f:
201+
with open("Cargo.toml", "r", encoding="utf-8") as f:
171202
content = f.read()
172203
match = re.search(r'version\s*=\s*"(\d+\.\d+\.\d+)"', content)
173204
if match:
174205
return match.group(1)
175206
print_error("Could not find version in Cargo.toml")
176207
sys.exit(1)
177208

209+
178210
def update_version(new_version: str) -> None:
179211
"""Update the version in Cargo.toml."""
180-
with open("Cargo.toml", "r") as f:
212+
with open("Cargo.toml", "r", encoding="utf-8") as f:
181213
content = f.read()
182-
updated_content = re.sub(r'^(version\s*=\s*)"(\d+\.\d+\.\d+)"', f'\\1"{new_version}"', content, flags=re.MULTILINE)
183-
with open("Cargo.toml", "w") as f:
214+
updated_content = re.sub(
215+
r'^(version\s*=\s*)"(\d+\.\d+\.\d+)"',
216+
f'\\1"{new_version}"',
217+
content,
218+
flags=re.MULTILINE,
219+
)
220+
with open("Cargo.toml", "w", encoding="utf-8") as f:
184221
f.write(updated_content)
185222
print_success(f"Updated version in Cargo.toml to {new_version}")
186223

224+
187225
def run_checks() -> None:
188226
"""Run cargo check and cargo test."""
189227
print_step("Running cargo check")
@@ -192,19 +230,56 @@ def run_checks() -> None:
192230
subprocess.run(["cargo", "test"], check=True)
193231
print_success("All checks passed")
194232

233+
234+
def generate_changelog(original_version: str, new_version: str) -> None:
235+
"""Generate changelog using git-iris."""
236+
print_step("Generating changelog with git-iris")
237+
from_tag = f"v{original_version}"
238+
239+
try:
240+
print_step(
241+
f"Updating changelog from {from_tag} to HEAD with version {new_version}"
242+
)
243+
subprocess.run(
244+
[
245+
"cargo",
246+
"run",
247+
"--",
248+
"changelog",
249+
"--from",
250+
from_tag,
251+
"--to",
252+
"HEAD",
253+
"--update",
254+
"--version-name",
255+
new_version,
256+
],
257+
check=True,
258+
)
259+
print_success("Changelog updated successfully")
260+
except subprocess.CalledProcessError as e:
261+
print_error(f"Failed to generate changelog: {str(e)}")
262+
sys.exit(1)
263+
264+
195265
def show_changes() -> bool:
196266
"""Show changes and ask for confirmation."""
197267
print_warning("The following files will be modified:")
198-
subprocess.run(["git", "status", "--porcelain"])
199-
confirmation = input(f"{COLOR_VERSION_PROMPT}Do you want to proceed with these changes? (y/N): {COLOR_RESET}").lower()
268+
subprocess.run(["git", "status", "--porcelain"], check=False)
269+
confirmation = input(
270+
f"{COLOR_VERSION_PROMPT}Do you want to proceed with these changes? (y/N): {COLOR_RESET}"
271+
).lower()
200272
return confirmation == "y"
201273

274+
202275
def commit_and_push(version: str) -> None:
203276
"""Commit and push changes to the repository."""
204277
print_step("Committing and pushing changes")
205278
try:
206-
subprocess.run(["git", "add", "Cargo.*"], check=True)
207-
subprocess.run(["git", "commit", "-m", f":rocket: Release version {version}"], check=True)
279+
subprocess.run(["git", "add", "Cargo.*", "CHANGELOG.md"], check=True)
280+
subprocess.run(
281+
["git", "commit", "-m", f":rocket: Release version {version}"], check=True
282+
)
208283
subprocess.run(["git", "push"], check=True)
209284
subprocess.run(["git", "tag", f"v{version}"], check=True)
210285
subprocess.run(["git", "push", "--tags"], check=True)
@@ -213,10 +288,12 @@ def commit_and_push(version: str) -> None:
213288
print_error(f"Git operations failed: {str(e)}")
214289
sys.exit(1)
215290

291+
216292
def is_valid_version(version: str) -> bool:
217293
"""Validate version format."""
218294
return re.match(r"^\d+\.\d+\.\d+$", version) is not None
219295

296+
220297
def main() -> None:
221298
"""Main function to handle the release process."""
222299
print_logo()
@@ -229,22 +306,31 @@ def main() -> None:
229306
check_uncommitted_changes()
230307

231308
current_version = get_current_version()
232-
new_version = input(f"{COLOR_VERSION_PROMPT}Current version is {current_version}. What should the new version be? {COLOR_RESET}")
309+
new_version = input(
310+
f"{COLOR_VERSION_PROMPT}Current version is {current_version}. What should the new version be? {COLOR_RESET}"
311+
)
233312

234313
if not is_valid_version(new_version):
235-
print_error("Invalid version format. Please use semantic versioning (e.g., 1.2.3).")
314+
print_error(
315+
"Invalid version format. Please use semantic versioning (e.g., 1.2.3)."
316+
)
236317
sys.exit(1)
237318

238319
update_version(new_version)
239320
run_checks()
240321

322+
generate_changelog(current_version, new_version)
323+
241324
if not show_changes():
242325
print_error("Release cancelled.")
243326
sys.exit(1)
244327

245328
commit_and_push(new_version)
246329

247-
print_success(f"\n🎉✨ {PROJECT_NAME} v{new_version} has been successfully released! ✨🎉")
330+
print_success(
331+
f"\n🎉✨ {PROJECT_NAME} v{new_version} has been successfully released! ✨🎉"
332+
)
333+
248334

249335
if __name__ == "__main__":
250-
main()
336+
main()

0 commit comments

Comments
 (0)