@@ -90,7 +90,38 @@ def self.commit_sha_for_tag(tag)
9090 `git rev-parse "#{ tag } "^{}` . chomp
9191 end
9292
93- def self . latest_release ( repo_name , prerelease , platform , github_token )
93+ def self . find_latest_marketing_version ( github_token , platform )
94+ latest_internal_release = Helper ::GitHelper . latest_release ( Helper ::GitHelper . repo_name , true , platform , github_token )
95+
96+ version = extract_version_from_tag_name ( latest_internal_release &.tag_name )
97+ if version . to_s . empty?
98+ UI . user_error! ( "Failed to find latest marketing version" )
99+ return
100+ end
101+ unless self . validate_semver ( version )
102+ UI . user_error! ( "Invalid marketing version: #{ version } , expected format: MAJOR.MINOR.PATCH" )
103+ return
104+ end
105+ version
106+ end
107+
108+ def self . extract_version_from_tag_name ( tag_name )
109+ # Remove build number (if present) and platform suffix from the tag name
110+ tag_name &.split ( /[-+]/ ) &.first
111+ end
112+
113+ def self . extract_version_from_branch_name ( branch_name )
114+ version = branch_name . split ( '/' ) &.last
115+ version if validate_semver ( version )
116+ end
117+
118+ def self . validate_semver ( version )
119+ # we only need basic "x.y.z" validation here
120+ version . match? ( /\A \d +\. \d +\. \d +\z / )
121+ end
122+
123+ # rubocop:disable Metrics/PerceivedComplexity
124+ def self . latest_release ( repo_name , prerelease , platform , github_token , allow_drafts : false )
94125 client = Octokit ::Client . new ( access_token : github_token )
95126
96127 current_page = 1
@@ -104,6 +135,9 @@ def self.latest_release(repo_name, prerelease, platform, github_token)
104135 # If `prerelease` is false, then ensure that the release is public.
105136 matching_release = releases . find do |release |
106137 matches_platform = platform . nil? || release . tag_name . end_with? ( "+#{ platform } " )
138+ if allow_drafts
139+ matches_platform ||= release . name . end_with? ( "+#{ platform } " )
140+ end
107141 matches_prerelease = prerelease == release . prerelease
108142 matches_platform && matches_prerelease
109143 end
@@ -116,6 +150,107 @@ def self.latest_release(repo_name, prerelease, platform, github_token)
116150
117151 return nil
118152 end
153+ # rubocop:enable Metrics/PerceivedComplexity
154+
155+ def self . delete_release ( release_url , github_token )
156+ client = Octokit ::Client . new ( access_token : github_token )
157+ client . delete_release ( release_url )
158+ end
159+
160+ def self . freeze_release_branch ( platform , github_token , other_action )
161+ UI . message ( "Checking latest marketing version" )
162+ latest_marketing_version = find_latest_marketing_version ( github_token , platform )
163+ UI . success ( "Latest marketing version: #{ latest_marketing_version } " )
164+
165+ draft_public_release_name = "#{ latest_marketing_version } +#{ platform } "
166+
167+ UI . message ( "Will freeze release branch for #{ latest_marketing_version } by creating a draft public release" )
168+ UI . message ( "First we'll check if #{ draft_public_release_name } release exists." )
169+
170+ UI . message ( "Checking for draft public release #{ draft_public_release_name } " )
171+ latest_public_release = latest_release ( Helper ::GitHelper . repo_name , false , platform , github_token , allow_drafts : true )
172+ UI . success ( "Latest public release (including drafts): #{ latest_public_release . name } " )
173+
174+ if latest_public_release . name == draft_public_release_name
175+ UI . success ( "Draft public release #{ draft_public_release_name } already exists. Nothing to do as the branch is already frozen." )
176+ return
177+ end
178+
179+ UI . message ( "Creating draft public release #{ draft_public_release_name } " )
180+
181+ description = <<~DESCRIPTION
182+ This draft release is here to indicate that the release branch is frozen.
183+ New internal releases on `release/#{ platform } /#{ latest_marketing_version } ` branch cannot be created.
184+ If you need to bump the internal release, please manually delete this draft release.
185+ DESCRIPTION
186+
187+ other_action . set_github_release (
188+ repository_name : repo_name ,
189+ api_bearer : github_token ,
190+ description : description ,
191+ name : draft_public_release_name ,
192+ tag_name : "" ,
193+ is_draft : true ,
194+ is_prerelease : false
195+ )
196+ UI . success ( "Draft public release #{ draft_public_release_name } created" )
197+ end
198+
199+ def self . assert_release_branch_is_not_frozen ( release_branch , platform , github_token )
200+ UI . message ( "Checking if release on #{ release_branch } branch can be bumped." )
201+
202+ marketing_version = extract_version_from_branch_name ( release_branch )
203+ if marketing_version . to_s . empty?
204+ UI . user_error! ( "Unable to extract version from '#{ release_branch } ' branch name." )
205+ return
206+ end
207+
208+ UI . message ( "Version extracted from '#{ release_branch } ' branch name: #{ marketing_version } " )
209+
210+ draft_public_release_name = "#{ marketing_version } +#{ platform } "
211+ UI . message ( "Checking if draft public release #{ draft_public_release_name } exists." )
212+
213+ latest_public_release = latest_release ( repo_name , false , platform , github_token , allow_drafts : true )
214+ UI . success ( "Latest public release (including drafts): #{ latest_public_release . name } " )
215+
216+ if latest_public_release . name == draft_public_release_name && latest_public_release . draft
217+ UI . important ( "Draft public release #{ draft_public_release_name } exists, which means the release branch is frozen." )
218+ UI . error ( "🚨 If you need to bump the release:" )
219+ UI . error ( " - Delete the draft public release to unfreeze the branch" )
220+ UI . error ( " - Release URL: ➡️ #{ latest_public_release . html_url } ⬅️" )
221+ UI . error ( " - Restart the workflow" )
222+ UI . user_error! ( "Release branch is frozen." )
223+ return
224+ end
225+
226+ UI . success ( "No draft public release #{ draft_public_release_name } found - the release isn't frozen." )
227+ end
228+
229+ def self . unfreeze_release_branch ( release_branch , platform , github_token )
230+ marketing_version = extract_version_from_branch_name ( release_branch )
231+ if marketing_version . to_s . empty?
232+ UI . user_error! ( "Unable to extract version from '#{ release_branch } ' branch name." )
233+ return
234+ end
235+
236+ UI . message ( "Unfreezing release branch #{ release_branch } if needed" )
237+
238+ draft_public_release_name = "#{ marketing_version } +#{ platform } "
239+ UI . message ( "Checking if draft public release #{ draft_public_release_name } exists." )
240+
241+ latest_public_release = latest_release ( repo_name , false , platform , github_token , allow_drafts : true )
242+ UI . success ( "Latest public release (including drafts): #{ latest_public_release . name } " )
243+
244+ unless latest_public_release . name == draft_public_release_name && latest_public_release . draft
245+ UI . important ( "Latest public release is not a draft. No need to delete it." )
246+ return
247+ end
248+
249+ UI . message ( "Release version matches and it's a draft release." )
250+ UI . important ( "Deleting draft public release #{ draft_public_release_name } " )
251+ delete_release ( latest_public_release . url , github_token )
252+ UI . success ( "Draft public release #{ draft_public_release_name } deleted" )
253+ end
119254
120255 def self . commit_author ( repo_name , commit_sha , github_token )
121256 client = Octokit ::Client . new ( access_token : github_token )
0 commit comments