1313import sys
1414import re
1515
16- SEMVER_REGEX = re .compile (fr'^v(?P<major>\d+?).(?P<minor>\d+?).(?P<patch>\d+?)$' )
16+ SEMVER_REGEX = re .compile (fr'^v(?P<major>\d+?).(?P<minor>\d+?).(?P<patch>\d+?)(rc(?P<rc>\d+?))? $' )
1717react_api_env_var_name = 'REACT_APP_API_VERSION'
1818REACT_API_ENV_REGEX = re .compile (fr'{ react_api_env_var_name } =(?P<version>.*)' )
1919semver_options = {
2020 'major' : 'Makes incompatible API changes' ,
2121 'minor' : 'Adds (backwards-compatible) functionality' ,
2222 'patch' : 'Makes (backwards-compatible) bug fixes' ,
23+ 'rc' : 'Increments on an existing RC tag' ,
2324 'none' : 'Don\' t update this service'
2425}
2526_default_subprocess_options = {'shell' : True , 'check' : True , 'capture_output' : True , 'text' : True }
2930 'api' : dict ({'cwd' : api_working_directory }, ** _default_subprocess_options ),
3031}
3132
32- def assert_using_a_tty ():
33- if not sys .stdout .isatty ():
34- print ("Error: Must run this method with a tty. If you're using windows try:\n " + f"winpty { ' ' .join (sys .argv )} " )
35- sys .exit (1 )
36-
3733def parse_command_line_arguments ():
3834 parser = argparse .ArgumentParser (description = 'Tag the code ready for a release.\n ' +
39- '; ' .join (option .upper () + ': ' + description for option , description in semver_options .items ()) + "." )
40- parser .add_argument ('--app' , choices = ['major' , 'minor' , 'patch' ], help = 'Set the semver update type for the App' )
35+ '; ' .join (option .upper () + ': ' + description for option , description in semver_options .items ()) + "." )
36+ parser .add_argument ('--app' , choices = ['major' , 'minor' , 'patch' , 'rc' ], help = 'Set the semver update type for the App' )
4137 parser .add_argument ('--api' , choices = semver_options .keys (), help = 'Set the semver update type for the API' )
4238 return parser .parse_args ()
4339
4440def ask_for_update_type_for (service_name ):
4541 while True :
46- user_response = input (f'Are the changes to the { service_name } : [0] NONE, [1] PATCH, [2] MINOR, or [3] MAJOR?\n ' )
47- if len (user_response ) != 1 and user_response not in ('0' , '1' , '2' , '3' ):
48- print ("Please respond either '0', '1', '2' or '3'" )
42+ user_response = input (f'Are the changes to the { service_name } : [0] NONE, [R] RELEASE CANDIDATE (RC), [ 1] PATCH, [2] MINOR, or [3] MAJOR?\n ' )
43+ if len (user_response ) != 1 and user_response not in ('0' , 'R' , ' 1' , '2' , '3' ):
44+ print ("Please respond either '0', 'R', ' 1', '2' or '3'" )
4945 elif service_name == 'app' and user_response == '0' :
5046 print ("Our release procedure does not allow a new API to be released without an App update." )
5147 else :
52- return {'0' : 'none' , '1' : 'patch' , '2' : 'minor' , '3' : 'major' }[user_response ]
48+ return {'0' : 'none' , 'R' : 'rc' , ' 1' : 'patch' , '2' : 'minor' , '3' : 'major' }[user_response ]
5349
5450def get_update_description_from_user (cli_input ):
5551 # try to retrieve from command line args
@@ -61,9 +57,22 @@ def get_update_description_from_user(cli_input):
6157 return update_description
6258
6359def get_versions_from_github ():
60+ app_mainline = [x ['name' ] for x in requests .get ('https://api.github.com/repos/isaacphysics/isaac-react-app/tags' ).json () if 'rc' not in x ['name' ]][0 ]
61+ api_mainline = [x ['name' ] for x in requests .get ('https://api.github.com/repos/isaacphysics/isaac-api/tags' ).json () if 'rc' not in x ['name' ]][0 ]
62+ try :
63+ app_rc = [x ['name' ] for x in requests .get ('https://api.github.com/repos/isaacphysics/isaac-react-app/tags' ).json () if 'rc' in x ['name' ]][0 ]
64+ except IndexError :
65+ app_rc = None
66+ try :
67+ api_rc = [x ['name' ] for x in requests .get ('https://api.github.com/repos/isaacphysics/isaac-api/tags' ).json () if 'rc' in x ['name' ]][0 ]
68+ except IndexError :
69+ api_rc = None
70+
6471 return {
65- 'app' : requests .get ('https://api.github.com/repos/isaacphysics/isaac-react-app/tags' ).json ()[0 ]['name' ],
66- 'api' : requests .get ('https://api.github.com/repos/isaacphysics/isaac-api/tags' ).json ()[0 ]['name' ],
72+ 'app-mainline' : app_mainline ,
73+ 'api-mainline' : api_mainline ,
74+ 'app-rc' : app_rc ,
75+ 'api-rc' : api_rc
6776 }
6877
6978def get_build_results_from_github (repo , branch ):
@@ -85,18 +94,36 @@ def get_build_results_from_github(repo, branch):
8594def increment_version (update_type ):
8695 def repl_matcher (match ):
8796 if update_type != 'none' :
88- version = {'major' : int (match .group ('major' )), 'minor' : int (match .group ('minor' )), 'patch' : int (match .group ('patch' ))}
89- version_order = ['major' , 'minor' , 'patch' ]
97+ version = {'major' : int (match .group ('major' )), 'minor' : int (match .group ('minor' )), 'patch' : int (match .group ('patch' )), 'rc' : None if not match . group ( 'rc' ) else int ( match . group ( 'rc' )) }
98+ version_order = ['major' , 'minor' , 'patch' , 'rc' ]
9099 for ver in version_order :
91100 if version_order .index (update_type ) < version_order .index (ver ):
92101 version [ver ] = 0
93102 elif ver == update_type :
94103 version [ver ] += 1
95- return f"v{ version ['major' ]} .{ version ['minor' ]} .{ version ['patch' ]} "
104+ if update_type == 'rc' :
105+ return f"v{ version ['major' ]} .{ version ['minor' ]} .{ version ['patch' ]} rc{ version ['rc' ]} "
106+ else :
107+ return f"v{ version ['major' ]} .{ version ['minor' ]} .{ version ['patch' ]} "
96108 else :
97109 return match .group (0 )
98110 return repl_matcher
99111
112+ def get_previous_versions_for_update_type (previous_versions , update_description ):
113+ prev = {}
114+ for service_name in ['app' , 'api' ]:
115+ service_update_description = update_description [service_name ]
116+ if service_update_description == 'rc' :
117+ if previous_versions [f'{ service_name } -rc' ]:
118+ prev [service_name ] = previous_versions [f"{ service_name } -rc" ]
119+ else :
120+ print ("Error: RC update type requires an existing release candidate tag to increment on, and none was "
121+ "found. You may need to create an initial tag (e.g. vX.Y.Zrc0) by hand." )
122+ sys .exit (1 )
123+ else :
124+ prev [service_name ] = previous_versions [f'{ service_name } -mainline' ]
125+ return prev
126+
100127def update_versions (previous_versions , update_description , snapshot = False ):
101128 update_versions = {}
102129 for service_name in ['app' , 'api' ]:
@@ -149,7 +176,7 @@ def set_versions(versions, update_description):
149176 # Record the App version
150177 # package.json
151178 subprocess .run (f"npm --no-git-tag-version version { versions ['app' ]} " , ** subprocess_options ['app' ])
152-
179+
153180 # Record the API version
154181 if update_description ['api' ] != 'none' :
155182 # .env
@@ -182,21 +209,20 @@ def commit_and_push_changes(versions, update_description):
182209
183210
184211if __name__ == '__main__' :
185- assert_using_a_tty ()
186-
187212 cli_args = parse_command_line_arguments ()
188213 update_description = get_update_description_from_user (cli_args )
189214
190215 check_app_and_api_are_clean (update_description )
191216
192217 most_recent_versions = get_versions_from_github ()
193- target_versions = update_versions (most_recent_versions , update_description )
218+ relevant_recent_versions = get_previous_versions_for_update_type (most_recent_versions , update_description )
219+ target_versions = update_versions (relevant_recent_versions , update_description )
194220 check_user_is_ready_to_release (target_versions , update_description )
195221
196222 set_versions (target_versions , update_description )
197223 commit_and_tag_changes (target_versions , update_description )
198224
199- bump_update_description = {service : 'patch' if update != ' none' else 'none ' for service , update in update_description .items ()}
225+ bump_update_description = {service : update if update in [ 'rc' , ' none'] else 'patch ' for service , update in update_description .items ()}
200226 bumped_versions = update_versions (target_versions , bump_update_description , snapshot = True )
201227 set_versions (bumped_versions , update_description )
202228 commit_and_push_changes (target_versions , update_description )
0 commit comments