Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ GEM
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.4.1)
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
fastlane-plugin-wpmreleasetoolkit (14.6.0)
fastlane-plugin-wpmreleasetoolkit (14.7.0)
buildkit (~> 1.5)
chroma (= 0.2.0)
diffy (~> 3.3)
dotenv (~> 2.8)
fastlane (~> 2.231)
fastlane (~> 2.235)
gettext (~> 3.5)
git (~> 1.3)
google-cloud-storage (~> 1.31)
Expand Down Expand Up @@ -269,7 +269,7 @@ GEM
rake (13.4.2)
rake-compiler (1.3.1)
rake
rchardet (1.10.0)
rchardet (1.10.2)
regexp_parser (2.12.0)
representable (3.2.0)
declarative (< 0.1.0)
Expand Down
100 changes: 96 additions & 4 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -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'

require_relative 'lib/studio_release_git'
Expand Down Expand Up @@ -427,7 +428,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 #studio on Slack
PROMPT
Expand Down Expand Up @@ -458,16 +459,23 @@ end
# @param skip_confirm [Boolean] Skip interactive confirmation prompts (default: false)
#
lane :publish_release do |version:, skip_confirm: false, github_username: nil|
# Required by update_apps_cdn_build_metadata to flip CDN visibility; check early for a clear failure
get_required_env('WPCOM_API_TOKEN')

release_branch = "release/#{version}"

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)

Comment thread
iangmaia marked this conversation as resolved.
# Publish the draft GitHub release — this creates the release tag (e.g., v1.7.4)
publish_github_release(
repository: GITHUB_REPO,
Expand Down Expand Up @@ -611,6 +619,11 @@ def distribute_builds(
appx_version = "#{version[/\d+\.\d+\.\d+/]}.0"
release_notes = release_tag.nil? ? "Development build #{version}-#{build_number}" : "Release #{release_tag}"

# Only Production builds go through the finalize → publish flow where {make_cdn_builds_public}
# flips them to External. Nightlies and Betas have no publish step, so uploading them as
# Internal would leave them invisible to users (the updates endpoint only serves External builds).
visibility = build_type == 'Production' ? :internal : :external

Comment thread
iangmaia marked this conversation as resolved.
update_builds = {
x64: {
binary_path: File.join(BUILDS_FOLDER, 'Studio-darwin-x64', 'Studio.app.zip'),
Expand Down Expand Up @@ -726,15 +739,16 @@ def distribute_builds(
arch: build[:arch],
build_type: build_type,
install_type: build[:install_type],
visibility: 'external',
visibility: visibility,
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]
Comment thread
iangmaia marked this conversation as resolved.
end

unless DRY_RUN
Expand Down Expand Up @@ -799,7 +813,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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it doesn't take long to look at the rest of the code on top of this, seeing this 0 in the code threw me off.

What do you think of adding a little comment?

Suggested change
post_id: 0
# downstream checks for a positive post_id; 0 will result in no upload, as appropriate for a dry run
post_id: 0

}
end

Expand Down Expand Up @@ -1203,6 +1218,18 @@ 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.
# Coerce to Integer defensively: the CDN API returns integer post IDs, but the
# update action requires Integers, so don't rely on the API's serialization.
cdn_post_ids = builds.values.filter_map { |b| b[:post_id]&.to_i }.select(&:positive?)

# Every build must have a post ID: builds missing from the embedded list would
# silently stay Internal after publish_release and never reach users.
missing = builds.values.reject { |b| b[:post_id]&.to_i&.positive? }.map { |b| b[:name] }
UI.user_error!("Missing CDN post IDs for: #{missing.join(', ')}. publish_release needs every build's post ID to flip its visibility to External.") unless missing.empty?

body += "\n<!-- CDN_POST_IDS:#{cdn_post_ids.join(',')} -->"

release_name = github_release_name(version:)
release_notes_path = File.join(PROJECT_ROOT_FOLDER, 'fastlane', 'github_release_notes.txt')
File.write(release_notes_path, body)
Expand All @@ -1222,6 +1249,71 @@ def create_draft_github_release(version:, release_tag:, builds:)
UI.success("Created draft GitHub release '#{release_name}' 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 = github_release_name(version:)
post_ids = extract_cdn_post_ids_from_draft_release(release_name: release_name)

Comment thread
iangmaia marked this conversation as resolved.
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}&per_page=50" \\
| jq '[.[] | select(.class_list | index("visibility-internal")) | {id, slug}]'
(the class_list filter selects builds that are still Internal)
Then edit the draft GitHub release body to add a comment like:
<!-- CDN_POST_IDS:123,456,789 -->
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...")

update_apps_cdn_build_metadata(
site_id: WPCOM_STUDIO_SITE_ID,
post_ids: post_ids,
visibility: :external
)
Comment thread
iangmaia marked this conversation as resolved.

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}:
# <!-- CDN_POST_IDS:123,456,789 -->
#
# @param release_name [String] The release name as created by {create_draft_github_release},
# i.e. {github_release_name} (e.g., 'Version 1.7.5')
# @return [Array<Integer>] 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)
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(/<!-- CDN_POST_IDS:([0-9,]+) -->/)
unless match
UI.important("Draft release #{release_name} found but no CDN post IDs embedded in the body.")
return []
end

match[1].split(',').map(&:to_i)
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)
Expand Down