55import os
66import re
77import subprocess # nosec
8+ import traceback
89from dataclasses import dataclass
910
1011import create_tarballs
@@ -123,29 +124,58 @@ def __init__(self, config: Config, git_prov: git.Git, github_prov: github.GitHub
123124 self .config = config
124125 self .git = git_prov
125126 self .github = github_prov
127+ self .version = ""
126128
127129 def require (self , condition : bool , message : str | None = None ) -> None :
128130 if not condition :
129131 raise stage .InvalidState (message or "Requirement not met" )
130132
131133 def assign_to_user (
132134 self ,
133- s : stage .Stage ,
135+ s : stage .Stage | None ,
134136 version : str ,
135137 task : str | None = None ,
136138 action : str = "" ,
137139 instruction : str | None = None ,
138140 ) -> stage .UserAbort :
139141 """Assign the issue to the acting user for them to take some action."""
140- self .github .issue_unassign (self .config .issue , ["toktok-releaser" ])
141- self .github .issue_assign (self .config .issue , [self .github .actor ()])
142+ if self .config .issue :
143+ self .github .issue_unassign (self .config .issue , ["toktok-releaser" ])
144+ self .github .issue_assign (self .config .issue , [self .github .actor ()])
142145 self .update_dashboard (version , current_task = task , instruction = instruction )
143- s .ok (f"Assigned to { self .github .actor ()} " )
146+ if s :
147+ s .ok (f"Assigned to { self .github .actor ()} " )
144148 return stage .UserAbort (f"Returning to the user to { action } " )
145149
150+ def report_failure (self , version : str , exception : Exception ) -> None :
151+ """Report a failure to the release tracking issue."""
152+ if not self .config .issue :
153+ return
154+
155+ instruction = f"❌ **Failure:** { exception } "
156+ self .assign_to_user (
157+ None ,
158+ version ,
159+ action = "fix the failure" ,
160+ instruction = instruction ,
161+ )
162+
163+ def run (self ) -> None :
164+ """Run the release process."""
165+ try :
166+ self .run_stages ()
167+ except stage .UserAbort as e :
168+ print (e .message )
169+ except Exception as e :
170+ traceback .print_exc ()
171+ self .report_failure (self .version , e )
172+ raise e
173+
146174 def compute_done_milestones (self , version : str ) -> set [str ]:
147175 """Heuristics to determine which milestones are completed."""
148- done = set ()
176+ done : set [str ] = set ()
177+ if not version :
178+ return done
149179
150180 # 1. Preparation
151181 if self .github .find_pr_for_branch (
@@ -200,15 +230,20 @@ def render_progress_list(
200230 ]
201231
202232 lines = []
233+ instruction_rendered = False
203234 for name , desc in milestones :
204235 status = "[x]" if name in done else "[ ]"
205236 if current_task == name :
206237 lines .append (f"- { status } **Current Step: { desc } **" )
207238 if instruction :
208239 lines .append (f" > ℹ️ **Action Required:** { instruction } " )
240+ instruction_rendered = True
209241 else :
210242 lines .append (f"- { status } { desc } " )
211243
244+ if instruction and not instruction_rendered :
245+ lines .append (f"\n ℹ️ **Action Required:** { instruction } " )
246+
212247 return "\n " .join (lines )
213248
214249 def update_dashboard (
@@ -272,6 +307,7 @@ def stage_version(self) -> str:
272307 if self .config .version == "latest" :
273308 version = self .github .latest_release ()
274309 s .ok (f"Using latest release { version } " )
310+ self .version = version
275311 return version
276312
277313 self .require (
@@ -280,6 +316,7 @@ def stage_version(self) -> str:
280316 f"(expected: { git .VERSION_REGEX .pattern } )" ,
281317 )
282318 s .ok (f"Accepting override version { self .config .version } " )
319+ self .version = self .config .version
283320 return self .config .version
284321 version = self .github .next_milestone ().title
285322 if not self .config .production :
@@ -288,6 +325,7 @@ def stage_version(self) -> str:
288325 version = f"{ version } -rc.{ rc + 1 } "
289326 self .require (re .match (git .VERSION_REGEX , version ) is not None )
290327 s .ok (version )
328+ self .version = version
291329 return version
292330
293331 def stage_rename_issue (self , version : str ) -> None :
@@ -931,13 +969,15 @@ def stage_close_issue(self) -> None:
931969 self .github .close_issue (self .config .issue )
932970 s .ok (f"Issue { self .config .issue } closed" )
933971
934- def run_stages (self ) -> None :
935- self .require (self .git .current_branch () == self .config .branch )
936- self .require (self .git .is_clean ())
972+ def run_stages (self , version : str | None = None ) -> None :
973+ if version is None :
974+ self .require (self .git .current_branch () == self .config .branch )
975+ self .require (self .git .is_clean ())
976+
977+ self .stage_init ()
937978
938- self .stage_init ()
979+ version = self .stage_version ()
939980
940- version = self .stage_version ()
941981 self .stage_rename_issue (version )
942982 self .stage_assign_milestone (version )
943983 self .stage_production_ready (version )
@@ -995,19 +1035,15 @@ def main(config: Config) -> None:
9951035 git_prov = git .DEFAULT_GIT
9961036 github_prov = github .DEFAULT_GITHUB
9971037
998- try :
999- # Stash any local changes for the user to later resume working on.
1000- with git .Stash (prov = git_prov ):
1001- # We need to be on the main branch to create a release, but we
1002- # want to return to the original branch afterwards.
1003- with git .Checkout (config .branch , prov = git_prov ):
1004- # Undo any partial changes if the script is aborted.
1005- with git .ResetOnExit (prov = git_prov ):
1006- releaser = Releaser (config , git_prov , github_prov )
1007- releaser .run_stages ()
1008- except stage .UserAbort as e :
1009- print (e .message )
1010- return
1038+ # Stash any local changes for the user to later resume working on.
1039+ with git .Stash (prov = git_prov ):
1040+ # We need to be on the main branch to create a release, but we
1041+ # want to return to the original branch afterwards.
1042+ with git .Checkout (config .branch , prov = git_prov ):
1043+ # Undo any partial changes if the script is aborted.
1044+ with git .ResetOnExit (prov = git_prov ):
1045+ releaser = Releaser (config , git_prov , github_prov )
1046+ releaser .run ()
10111047
10121048
10131049if __name__ == "__main__" :
0 commit comments