22import argparse
33import os
44import subprocess
5+ import sys
56from inspect import getsourcefile
67from os .path import abspath
78
89import semver
10+ import tomlkit
911
1012from utils .validate_version import assert_version_is_not_published
1113
1517SCRIPTS_DIR = os .path .join (REPO_ROOT_DIR , "scripts" )
1618SCRIPT_FILENAME = os .path .basename (getsourcefile (lambda : 0 ))
1719
18- def overwrite_version (version ):
20+ def get_current_version ():
21+ """Read the current version from Cargo.toml using tomlkit"""
1922 toml_path = os .path .join (REPO_ROOT_DIR , "Cargo.toml" )
20- with open (toml_path , "r" ) as file :
21- lines = file .readlines ()
22-
23- # This will preserve line order.
24- current_version = None
25- for i , line in enumerate (lines ):
26- if line .startswith ("version =" ):
27- current_version = line .split ("=" )[1 ].strip ().strip ('"' )
28- if current_version == version :
29- print (f"Already at version { version } ." )
30- exit (1 )
31- lines [i ] = f"version = \" { version } \" \n "
32- break
33-
34- if current_version is None :
35- print ("The `version` field is not present in Cargo.toml." )
36- exit (1 )
23+
24+ try :
25+ with open (toml_path , "r" ) as file :
26+ doc = tomlkit .parse (file .read ())
27+
28+ if "package" in doc and "version" in doc ["package" ]:
29+ return doc ["package" ]["version" ]
30+ else :
31+ print ("Error: The `version` field is not present in Cargo.toml [package] section." )
32+ sys .exit (1 )
33+ except FileNotFoundError :
34+ print (f"Error: Cargo.toml not found at { toml_path } " )
35+ sys .exit (1 )
36+ except Exception as e :
37+ print (f"Error: Failed to parse Cargo.toml: { e } " )
38+ sys .exit (1 )
39+
40+ def overwrite_version (version , dry_run = False ):
41+ """
42+ Update version in Cargo.toml using tomlkit.
43+ Preserves formatting, comments, and structure.
44+ """
45+ toml_path = os .path .join (REPO_ROOT_DIR , "Cargo.toml" )
46+
47+ try :
48+ with open (toml_path , "r" ) as file :
49+ content = file .read ()
50+ doc = tomlkit .parse (content )
51+ except FileNotFoundError :
52+ print (f"Error: Cargo.toml not found at { toml_path } " )
53+ sys .exit (1 )
54+ except Exception as e :
55+ print (f"Error: Failed to parse Cargo.toml: { e } " )
56+ sys .exit (1 )
57+
58+ # Check if [package] section exists
59+ if "package" not in doc :
60+ print ("Error: [package] section not found in Cargo.toml" )
61+ sys .exit (1 )
62+
63+ # Check if version field exists
64+ if "version" not in doc ["package" ]:
65+ print ("Error: version field not found in [package] section" )
66+ sys .exit (1 )
67+
68+ current_version = doc ["package" ]["version" ]
69+
70+ if current_version == version :
71+ print (f"Already at version { version } ." )
72+ sys .exit (1 )
3773
3874 commit_message = f"chore(deps): change version from { current_version } with { version } "
39- print (commit_message )
75+ print (f"Updating version in Cargo.toml: { current_version } -> { version } " )
4076
77+ if dry_run :
78+ print ("Dry-run mode: Skipping version file write and commit." )
79+ return
80+
81+ # Update version in the document
82+ doc ["package" ]["version" ] = version
83+
84+ # Write back to file (preserves formatting and comments)
4185 with open (toml_path , "w" ) as file :
42- file .writelines ( lines )
86+ file .write ( tomlkit . dumps ( doc ) )
4387
4488 # Update VRL version in Cargo.lock
4589 subprocess .run (["cargo" , "update" , "-p" , "vrl" ], check = True , cwd = REPO_ROOT_DIR )
4690
4791 subprocess .run (["git" , "commit" , "-a" , "-m" , commit_message ], check = True , cwd = REPO_ROOT_DIR )
4892
4993
94+ def resolve_version (version_arg ):
95+ """
96+ Resolve version argument to actual version string.
97+ Supports:
98+ - Exact version (e.g., "1.2.3")
99+ - "major" - bump major version
100+ - "minor" - bump minor version
101+ - "patch" - bump patch version
102+ """
103+ bump_types = ["major" , "minor" , "patch" ]
104+
105+ if version_arg .lower () in bump_types :
106+ current_version_str = get_current_version ()
107+ current_version = semver .VersionInfo .parse (current_version_str )
108+
109+ if version_arg .lower () == "major" :
110+ new_version = current_version .bump_major ()
111+ elif version_arg .lower () == "minor" :
112+ new_version = current_version .bump_minor ()
113+ elif version_arg .lower () == "patch" :
114+ new_version = current_version .bump_patch ()
115+
116+ new_version_str = str (new_version )
117+ print (f"Bumping { version_arg } version: { current_version_str } -> { new_version_str } " )
118+ return new_version_str
119+ else :
120+ # Assume it's an exact version
121+ return version_arg
122+
50123def validate_version (version ):
51124 try :
52125 semver .VersionInfo .parse (version )
@@ -56,24 +129,33 @@ def validate_version(version):
56129
57130 assert_version_is_not_published (version )
58131
59- def generate_changelog ():
132+ def generate_changelog (dry_run = False ):
60133 print ("Generating changelog..." )
134+ if dry_run :
135+ print ("Dry-run mode: Skipping changelog generation and commit." )
136+ return
61137 subprocess .run (["./generate_release_changelog.sh" , "--no-prompt" ], check = True , cwd = SCRIPTS_DIR )
62138 subprocess .run (["git" , "commit" , "-a" , "-m" , "chore(releasing): generate changelog" ],
63139 check = True ,
64140 cwd = REPO_ROOT_DIR )
65141
66142def create_branch (branch_name , dry_run = False ):
67143 print (f"Creating branch: { branch_name } " )
144+ if dry_run :
145+ print ("Dry-run mode: Skipping branch creation." )
146+ return
68147 subprocess .run (["git" , "checkout" , "-b" , branch_name ], check = True , cwd = REPO_ROOT_DIR )
69- if not dry_run :
70- subprocess .run (["git" , "push" , "-u" , "origin" , branch_name ],
71- check = True ,
72- cwd = REPO_ROOT_DIR )
148+ subprocess .run (["git" , "push" , "-u" , "origin" , branch_name ],
149+ check = True ,
150+ cwd = REPO_ROOT_DIR )
73151
74- def create_pull_request (branch_name , new_version , dry_run = False ):
152+ def create_pull_request (branch_name , new_version , issue_link = None , dry_run = False ):
75153 title = f"chore(releasing): Prepare { new_version } release"
76154 body = f"Generated with { SCRIPT_FILENAME } "
155+
156+ if issue_link :
157+ body += f"\n \n Related issue: { issue_link } "
158+
77159 print (f"Creating pull request with title: { title } " )
78160 if dry_run :
79161 print ("Dry-run mode: Skipping PR creation." )
@@ -87,24 +169,33 @@ def create_pull_request(branch_name, new_version, dry_run=False):
87169
88170def main ():
89171 parser = argparse .ArgumentParser (description = "Prepare a new release" )
90- parser .add_argument ("version" , help = "The new version to release" )
172+ parser .add_argument ("version" , help = "The new version to release (e.g., '1.2.3', 'major', 'minor', or 'patch')" )
173+ parser .add_argument ("--issue" , "-i" , dest = "issue_link" ,
174+ help = "GitHub issue link to include in the PR body (e.g., 'https://github.com/owner/repo/issues/123')" )
91175 parser .add_argument ("--dry-run" , action = "store_true" ,
92- help = "Run the script without making remote changes" )
176+ help = "Run the script without making any changes (read-only) " )
93177 args = parser .parse_args ()
94178
95- new_version = args .version
179+ # Resolve version (could be exact version or bump type)
180+ new_version = resolve_version (args .version )
96181 dry_run = args .dry_run
182+ issue_link = args .issue_link
183+
184+ if not dry_run :
185+ validate_version (new_version )
97186
98- validate_version (new_version )
99187 branch_name = f"prepare-{ new_version } -release"
100188 create_branch (branch_name , dry_run )
101- overwrite_version (new_version )
102- generate_changelog ()
103- subprocess .run (["git" , "push" ], check = True , cwd = REPO_ROOT_DIR )
104- create_pull_request (branch_name , new_version , dry_run )
189+ overwrite_version (new_version , dry_run )
190+ generate_changelog (dry_run )
191+
192+ if not dry_run :
193+ subprocess .run (["git" , "push" ], check = True , cwd = REPO_ROOT_DIR )
194+
195+ create_pull_request (branch_name , new_version , issue_link , dry_run )
105196
106197 if dry_run :
107- print ("Dry -run completed. No actual remote changes were made." )
198+ print ("\n Dry -run completed. No changes were made." )
108199
109200if __name__ == "__main__" :
110201 main ()
0 commit comments