From 59cb346928311a55e1ed6abe11728c58b26c36b6 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Fri, 6 Mar 2026 19:10:24 +0100 Subject: [PATCH 1/3] Upload CDN builds as Internal first, flip to External on publish During finalize_release, all CDN builds are now uploaded with visibility: internal. The CDN post IDs are embedded in the draft GitHub release body. In publish_release, those IDs are read back and update_apps_cdn_build_metadata flips visibility to external before the GitHub release is published. Co-Authored-By: Claude Opus 4.6 --- fastlane/Fastfile | 85 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 97cf6ac381..6876a1db59 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -5,6 +5,7 @@ fastlane_require 'zip' fastlane_require 'aws-sdk-cloudfront' fastlane_require 'json' fastlane_require 'net/http' +fastlane_require 'octokit' fastlane_require 'uri' UI.user_error!('Please run fastlane via `bundle exec`') unless FastlaneCore::Helper.bundler? @@ -290,7 +291,7 @@ lane :finalize_release do |version:, skip_confirm: false| - Download latest translations from GlotPress - Bump version to #{version} (remove beta suffix) - Trigger a release build for all platforms (macOS, Windows), which will then: - - Upload build artifacts to the Apps CDN + - Upload build artifacts to the Apps CDN (as Internal, visible only to Automatticians) - Create a draft GitHub release with release notes and download links - Notify #dotcom-studio on Slack PROMPT @@ -325,12 +326,16 @@ lane :publish_release do |version:, skip_confirm: false, github_username: nil| UI.important <<~PROMPT Publishing release #{version}. This will: + - Make CDN builds public (change visibility from Internal to External) - Publish the draft GitHub release for v#{version} - Create a backmerge PR from `#{release_branch}` into `#{MAIN_BRANCH}` - Delete the `#{release_branch}` branch after creating the backmerge PR PROMPT next unless skip_confirm || UI.confirm('Continue?') + # Update CDN build visibility from Internal to External + make_cdn_builds_public(version: version) + # Publish the draft GitHub release publish_github_release( repository: GITHUB_REPO, @@ -542,15 +547,16 @@ def distribute_builds( arch: build[:arch], build_type: build_type, install_type: build[:install_type], - visibility: 'external', + visibility: :internal, version: version, build_number: build_number, release_notes: release_notes, sha: build[:sha], error_on_duplicate: false ) - # Store the download URL for later use + # Store the download URL and post ID for later use build[:cdn_url] = result[:media_url] + build[:post_id] = result[:post_id] end unless DRY_RUN @@ -615,7 +621,8 @@ def upload_file_to_apps_cdn(site_id:, product:, file_path:, platform:, arch:, bu UI.message(" error on duplicate: #{error_on_duplicate}") return { - media_url: media_url + media_url: media_url, + post_id: 0 } end @@ -762,6 +769,12 @@ def create_draft_github_release(version:, release_tag:, builds:) 'The latest version is always available on the [WordPress Studio](https://developer.wordpress.com/studio/) site.' end + # Embed CDN post IDs so publish_release can update their visibility later + cdn_post_ids = builds.each_with_object({}) do |(key, build), hash| + hash[key] = build[:post_id] if build[:post_id]&.positive? + end + body += "\n" unless cdn_post_ids.empty? + release_notes_path = File.join(PROJECT_ROOT_FOLDER, 'fastlane', 'github_release_notes.txt') File.write(release_notes_path, body) @@ -779,6 +792,70 @@ def create_draft_github_release(version:, release_tag:, builds:) UI.success("Created draft GitHub release #{release_tag} with download links") end +# Make CDN builds public by updating their visibility from Internal to External. +# +# Reads CDN post IDs embedded in the draft GitHub release body by {create_draft_github_release}, +# then calls {update_apps_cdn_build_metadata} for each to set visibility to External. +# +# @param version [String] The version to publish (e.g., '1.7.5') +# +def make_cdn_builds_public(version:) + release_name = "v#{version}" + cdn_post_ids = extract_cdn_post_ids_from_draft_release(release_name: release_name) + + UI.user_error!("No CDN post IDs found in draft release #{release_name}. Cannot publish without updating CDN visibility.") if cdn_post_ids.empty? + + post_ids = cdn_post_ids.values.map(&:to_i) + UI.message("Updating CDN build visibility to External for #{post_ids.size} builds...") + + update_apps_cdn_build_metadata( + site_id: WPCOM_STUDIO_SITE_ID, + post_ids: post_ids, + visibility: :external + ) + + UI.success('All CDN builds are now public (External visibility)') +end + +# Extract CDN post IDs from the draft GitHub release body. +# +# The post IDs are embedded as an HTML comment by {create_draft_github_release}: +# +# +# @param release_name [String] The release name (e.g., 'v1.7.5') +# @return [Hash] A hash of build key => post ID, or empty hash if not found +# +def extract_cdn_post_ids_from_draft_release(release_name:) + client = Octokit::Client.new(access_token: get_required_env('GITHUB_TOKEN'), auto_paginate: true) + releases = client.releases(GITHUB_REPO) + release = releases.find { |r| r.draft && r.name == release_name } + + unless release + UI.important("No draft release found with name #{release_name}.") + return {} + end + + body = release.body || '' + match = body.match(//) + unless match + UI.important("Draft release #{release_name} found but no CDN post IDs embedded in the body.") + return {} + end + + begin + parsed = JSON.parse(match[1]) + rescue JSON::ParserError => e + UI.error("Failed to parse CDN post IDs from draft release #{release_name}: #{e.message}") + return {} + end + + parsed.each do |key, value| + UI.user_error!("Invalid CDN post ID for '#{key}' in draft release #{release_name}: #{value.inspect} (expected positive integer)") unless value.is_a?(Integer) && value.positive? + end + + parsed +end + # Trigger a release build in Buildkite for the given version. # # Uses `buildkite_add_trigger_step` on CI (to create a separate build with proper Git mirroring) From 3739247884501a502aa3447c7d61ea09c01840c6 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 9 Mar 2026 19:10:11 +0100 Subject: [PATCH 2/3] Simplify post id structure in releases --- fastlane/Fastfile | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 6876a1db59..8c1cc807e5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -770,10 +770,8 @@ def create_draft_github_release(version:, release_tag:, builds:) end # Embed CDN post IDs so publish_release can update their visibility later - cdn_post_ids = builds.each_with_object({}) do |(key, build), hash| - hash[key] = build[:post_id] if build[:post_id]&.positive? - end - body += "\n" unless cdn_post_ids.empty? + cdn_post_ids = builds.values.filter_map { |b| b[:post_id] }.select(&:positive?) + body += "\n" unless cdn_post_ids.empty? release_notes_path = File.join(PROJECT_ROOT_FOLDER, 'fastlane', 'github_release_notes.txt') File.write(release_notes_path, body) @@ -801,11 +799,10 @@ end # def make_cdn_builds_public(version:) release_name = "v#{version}" - cdn_post_ids = extract_cdn_post_ids_from_draft_release(release_name: release_name) + post_ids = extract_cdn_post_ids_from_draft_release(release_name: release_name) UI.user_error!("No CDN post IDs found in draft release #{release_name}. Cannot publish without updating CDN visibility.") if cdn_post_ids.empty? - post_ids = cdn_post_ids.values.map(&:to_i) UI.message("Updating CDN build visibility to External for #{post_ids.size} builds...") update_apps_cdn_build_metadata( @@ -820,10 +817,10 @@ end # Extract CDN post IDs from the draft GitHub release body. # # The post IDs are embedded as an HTML comment by {create_draft_github_release}: -# +# # # @param release_name [String] The release name (e.g., 'v1.7.5') -# @return [Hash] A hash of build key => post ID, or empty hash if not found +# @return [Array] An array of post IDs, or empty array if not found # def extract_cdn_post_ids_from_draft_release(release_name:) client = Octokit::Client.new(access_token: get_required_env('GITHUB_TOKEN'), auto_paginate: true) @@ -832,28 +829,17 @@ def extract_cdn_post_ids_from_draft_release(release_name:) unless release UI.important("No draft release found with name #{release_name}.") - return {} + return [] end body = release.body || '' - match = body.match(//) + match = body.match(//) unless match UI.important("Draft release #{release_name} found but no CDN post IDs embedded in the body.") - return {} - end - - begin - parsed = JSON.parse(match[1]) - rescue JSON::ParserError => e - UI.error("Failed to parse CDN post IDs from draft release #{release_name}: #{e.message}") - return {} - end - - parsed.each do |key, value| - UI.user_error!("Invalid CDN post ID for '#{key}' in draft release #{release_name}: #{value.inspect} (expected positive integer)") unless value.is_a?(Integer) && value.positive? + return [] end - parsed + match[1].split(',').map(&:to_i) end # Trigger a release build in Buildkite for the given version. From 38e6abe5b3333974e790c1bc8a4fe29c07575672 Mon Sep 17 00:00:00 2001 From: Ian Maia Date: Mon, 9 Mar 2026 19:11:36 +0100 Subject: [PATCH 3/3] Improve error message with workaround suggestion --- fastlane/Fastfile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 8c1cc807e5..b652ec6bbd 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -801,7 +801,19 @@ def make_cdn_builds_public(version:) release_name = "v#{version}" post_ids = extract_cdn_post_ids_from_draft_release(release_name: release_name) - UI.user_error!("No CDN post IDs found in draft release #{release_name}. Cannot publish without updating CDN visibility.") if cdn_post_ids.empty? + if post_ids.empty? + UI.user_error! <<~ERROR + No CDN post IDs found in draft release #{release_name}. Cannot publish without updating CDN visibility. + To recover manually, an Automattician can find the post IDs by running this from a proxied machine: + curl -H "Authorization: Bearer $WPCOM_API_TOKEN" \\ + "https://public-api.wordpress.com/wp/v2/sites/#{WPCOM_STUDIO_SITE_ID}/a8c_cdn_build?search=#{version}&visibility=1316&per_page=20" \\ + | jq '[.[] | {id, slug}]' + (visibility=1316 filters for Internal builds only) + Then edit the draft GitHub release body to add a comment like: + + and retry the publish_release lane or release tool task. + ERROR + end UI.message("Updating CDN build visibility to External for #{post_ids.size} builds...")