3636# Gradient colors for the banner
3737GRADIENT_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+
4748def print_colored (message : str , color : str ) -> None :
4849 """Print a message with a specific color."""
4950 print (f"{ color } { message } { COLOR_RESET } " )
5051
52+
5153def 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+
5558def print_error (message : str ) -> None :
5659 """Print an error message with a specific color."""
5760 print_colored (f"❌ Error: { message } " , COLOR_ERROR )
5861
62+
5963def print_success (message : str ) -> None :
6064 """Print a success message with a specific color."""
6165 print_colored (f"✅ { message } " , COLOR_SUCCESS )
6266
67+
6368def print_warning (message : str ) -> None :
6469 """Print a warning message with a specific color."""
6570 print_colored (f"⚠️ { message } " , COLOR_WARNING )
6671
72+
6773def 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+
8592def 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+
9098def 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+
97106def 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+
103113def 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+
107118def 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+
144163def print_logo () -> None :
145164 """Print the banner/logo for the release manager."""
146165 print (create_banner ())
147166
167+
148168def 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+
154175def 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+
161187def 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+
168199def 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+
178210def 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+
187225def run_checks () -> None :
188226 """Run cargo check and cargo test."""
189227 print_step ("Running cargo check" )
@@ -192,19 +230,57 @@ def run_checks() -> None:
192230 subprocess .run (["cargo" , "test" ], check = True )
193231 print_success ("All checks passed" )
194232
233+
234+ def generate_changelog (new_version : str ) -> None :
235+ """Generate changelog using git-iris."""
236+ print_step ("Generating changelog with git-iris" )
237+ current_version = get_current_version ()
238+ from_tag = f"v{ current_version } "
239+
240+ try :
241+ print_step (
242+ f"Updating changelog from { from_tag } to HEAD with version { new_version } "
243+ )
244+ subprocess .run (
245+ [
246+ "cargo" ,
247+ "run" ,
248+ "--" ,
249+ "changelog" ,
250+ "--from" ,
251+ from_tag ,
252+ "--to" ,
253+ "HEAD" ,
254+ "--update" ,
255+ "--version-name" ,
256+ new_version ,
257+ ],
258+ check = True ,
259+ )
260+ print_success ("Changelog updated successfully" )
261+ except subprocess .CalledProcessError as e :
262+ print_error (f"Failed to generate changelog: { str (e )} " )
263+ sys .exit (1 )
264+
265+
195266def show_changes () -> bool :
196267 """Show changes and ask for confirmation."""
197268 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 ()
269+ subprocess .run (["git" , "status" , "--porcelain" ], check = False )
270+ confirmation = input (
271+ f"{ COLOR_VERSION_PROMPT } Do you want to proceed with these changes? (y/N): { COLOR_RESET } "
272+ ).lower ()
200273 return confirmation == "y"
201274
275+
202276def commit_and_push (version : str ) -> None :
203277 """Commit and push changes to the repository."""
204278 print_step ("Committing and pushing changes" )
205279 try :
206- subprocess .run (["git" , "add" , "Cargo.*" ], check = True )
207- subprocess .run (["git" , "commit" , "-m" , f":rocket: Release version { version } " ], check = True )
280+ subprocess .run (["git" , "add" , "Cargo.*" , "CHANGELOG.md" ], check = True )
281+ subprocess .run (
282+ ["git" , "commit" , "-m" , f":rocket: Release version { version } " ], check = True
283+ )
208284 subprocess .run (["git" , "push" ], check = True )
209285 subprocess .run (["git" , "tag" , f"v{ version } " ], check = True )
210286 subprocess .run (["git" , "push" , "--tags" ], check = True )
@@ -213,10 +289,12 @@ def commit_and_push(version: str) -> None:
213289 print_error (f"Git operations failed: { str (e )} " )
214290 sys .exit (1 )
215291
292+
216293def is_valid_version (version : str ) -> bool :
217294 """Validate version format."""
218295 return re .match (r"^\d+\.\d+\.\d+$" , version ) is not None
219296
297+
220298def main () -> None :
221299 """Main function to handle the release process."""
222300 print_logo ()
@@ -229,22 +307,31 @@ def main() -> None:
229307 check_uncommitted_changes ()
230308
231309 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 } " )
310+ new_version = input (
311+ f"{ COLOR_VERSION_PROMPT } Current version is { current_version } . What should the new version be? { COLOR_RESET } "
312+ )
233313
234314 if not is_valid_version (new_version ):
235- print_error ("Invalid version format. Please use semantic versioning (e.g., 1.2.3)." )
315+ print_error (
316+ "Invalid version format. Please use semantic versioning (e.g., 1.2.3)."
317+ )
236318 sys .exit (1 )
237319
238320 update_version (new_version )
239321 run_checks ()
240322
323+ generate_changelog (new_version )
324+
241325 if not show_changes ():
242326 print_error ("Release cancelled." )
243327 sys .exit (1 )
244328
245329 commit_and_push (new_version )
246330
247- print_success (f"\n 🎉✨ { PROJECT_NAME } v{ new_version } has been successfully released! ✨🎉" )
331+ print_success (
332+ f"\n 🎉✨ { PROJECT_NAME } v{ new_version } has been successfully released! ✨🎉"
333+ )
334+
248335
249336if __name__ == "__main__" :
250- main ()
337+ main ()
0 commit comments