1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00
Files
browser/apps/desktop/fastlane/fastfile
aj-bw 3202b56614 [bre-1089] mac desktop publish automation using fastlane (#16091)
* Use Fastlane to publish to Apple App Store

* Publish MacOS build number as artifact

* Download and source build number from artifact

* Refactor Fastlane file to use already existing builds in TestFlight

* fastfile changes, release workflow changes, gitignore addition

* reorder steps to after dist dir is created

* resolve pathing issue

* upload step path fix

* make comments more clear

* enable phased rollout, add auto-submit checkbox

* move logic from release to publish workflow

* configure dry run properly for MAS

* edit file for testing

* workflow testing

* verbose logging for debugging

* update to look at releases

* remove verbose flag for next test

* add verbose logging back

* disable precheck

* hardcode app v for test

* hardcode app v for testing

* additional test

* log build numbers

* remove testing values, prep for draft PR

* flip metadata bool for testing

* comment out branch check

* hardcode locales

* add metadata and locales change

* lane change

* more logging for finding build

* address logs feedback

* edit_live false

* testing

* extra logging from apple api

* testing

* workaround for attaching build attempt

* workaround patch update

* simplify and retest skip metadata true

* turn precheck true

* remove autosubmit checkbox, add live edit true for testing release notes formatting

* re-org dispatch, rename dir to release_notes, flip live edit to false

* another formatting attempt

* additional formatting changes

* account for double space, add dash to beginning

* different formatting approach

* format test

* simplified notes formatting test, double line after each period

* proper formatting

* rename file for rust linter

* remove testing comments

* remove default string from notes, logic to check for empty release notes in mas_publish, formatting

* add validation logic after publishing

---------

Co-authored-by: Micaiah Martin <github@sourcecodemt.com>
2025-08-28 10:53:17 -04:00

175 lines
6.8 KiB
Plaintext

default_platform(:mac)
# Static configuration for the Mac desktop app
require 'json'
require 'base64'
APP_CONFIG = {
app_identifier: "com.bitwarden.desktop",
release_notes_path: "fastlane/release_notes",
locales: ["ca", "zh-Hans", "zh-Hant", "da", "nl-NL", "en-US", "fi", "fr-FR", "de-DE", "id", "it", "ja", "ko", "no", "pt-PT", "pt-BR", "ru", "es-ES", "es-MX", "sv", "tr", "vi", "en-GB", "th"]
}
platform :mac do
desc "Prepare release notes from changelog"
lane :prepare_release_notes do |options|
changelog = options[:changelog] || "Bug fixes and improvements"
# Split on periods and format with bullet points
# Try different formatting approaches for App Store Connect
formatted_changelog = changelog
.split('.')
.map(&:strip)
.reject(&:empty?)
.map { |item| "• #{item}" }
.join("\n")
UI.message("Original changelog: #{changelog[0,100]}#{changelog.length > 100 ? '...' : ''}")
UI.message("Formatted changelog: #{formatted_changelog[0,100]}#{formatted_changelog.length > 100 ? '...' : ''}")
# Create release notes directories and files for all locales
APP_CONFIG[:locales].each do |locale|
dir = "release_notes/#{locale}"
FileUtils.mkdir_p(dir)
File.write("#{dir}/release_notes.txt", formatted_changelog)
UI.message("Creating release notes for #{locale}")
end
# Create release notes hash for deliver
notes = APP_CONFIG[:locales].each_with_object({}) do |locale, hash|
file_path = "release_notes/#{locale}/release_notes.txt"
if File.exist?(file_path)
hash[locale] = File.read(file_path)
else
UI.important("No release notes found for #{locale} at #{file_path}, skipping.")
end
end
UI.success("✅ Prepared release notes for #{APP_CONFIG[:locales].count} locales")
notes
end
desc "Display configuration information"
lane :show_config do |options|
build_number = (options[:build_number] || ENV["BUILD_NUMBER"]).to_s.strip
app_version = (options[:app_version] || ENV["APP_VERSION"]).to_s.strip
UI.message("📦 App ID: #{APP_CONFIG[:app_identifier]}")
UI.message("🏷️ Version: #{app_version.empty? ? '(not set)' : app_version}")
UI.message("🔢 Build Number: #{build_number}")
UI.message("🌍 Locales: #{APP_CONFIG[:locales].count}")
end
desc "Publish desktop to the Mac App Store"
lane :publish do |options|
build_number = (options[:build_number] || ENV["BUILD_NUMBER"]).to_s.strip
app_version = (options[:app_version] || ENV["APP_VERSION"]).to_s.strip
changelog = options[:changelog] || "Bug fixes and improvements"
is_dry_run = options[:dry_run] == "true" || options[:dry_run] == true
if is_dry_run
UI.header("🧪 DRY RUN: Testing Bitwarden Desktop App Store submission")
else
UI.header("🚀 Publishing Bitwarden Desktop to Mac App Store")
end
# Show configuration info
show_config(build_number: build_number, app_version: app_version)
# Validate app_version
UI.user_error!("❌ APP_VERSION is required") if app_version.nil? || app_version.empty?
# Validate build_number
UI.user_error!("❌ BUILD_NUMBER is required") if build_number.nil? || build_number.empty?
# Prepare release notes for all locales
notes = prepare_release_notes(changelog: changelog)
if is_dry_run
UI.important("🧪 DRY RUN MODE - Skipping actual App Store Connect submission")
UI.message("✅ Validation passed")
UI.message("✅ Release notes prepared for #{APP_CONFIG[:locales].count} locales")
UI.message("✅ Release notes: #{changelog[0,100]}#{changelog.length > 100 ? '...' : ''}")
UI.success("🎯 DRY RUN COMPLETE - Everything looks ready for production!")
next # Use 'next' instead of 'return' in fastlane lanes
end
# Set up App Store Connect API
app_store_connect_api_key(
key_id: "6TV9MKN3GP",
issuer_id: ENV["APP_STORE_CONNECT_TEAM_ISSUER"],
key_content: Base64.encode64(ENV["APP_STORE_CONNECT_AUTH_KEY"]),
is_key_content_base64: true
)
UI.message("📝 Using release notes for #{notes.keys.count} locales")
UI.message("🎯 Publishing version #{app_version} with build #{build_number}")
# Upload to App Store Connect
deliver(
platform: "osx",
app_identifier: APP_CONFIG[:app_identifier],
app_version: app_version,
build_number: build_number,
metadata_path: "metadata",
skip_binary_upload: true,
skip_screenshots: true,
skip_metadata: false, # Enable metadata upload to include release notes
release_notes: notes,
edit_live: false,
submit_for_review: true, # if this is false, the build number does not attach to the draft
phased_release: true, # Enable 7-day phased rollout
precheck_include_in_app_purchases: false,
run_precheck_before_submit: false,
automatic_release: true,
force: true
)
# Verify submission in App Store Connect (skip in dry run mode)
unless is_dry_run
UI.message("⏳ Waiting 60 seconds for App Store Connect to process submission...")
sleep(60)
UI.message("🔍 Verifying submission in App Store Connect...")
# Find the app
app = Spaceship::ConnectAPI::App.find(APP_CONFIG[:app_identifier])
UI.user_error!("❌ App not found in App Store Connect") if app.nil?
# Find the version we just submitted
versions = app.get_app_store_versions
target_version = nil
versions.each do |v|
if v.version_string == app_version
target_version = v
break
end
end
UI.user_error!("❌ Version #{app_version} not found in App Store Connect after submission") if target_version.nil?
UI.success("✅ Version #{app_version} found in App Store Connect")
UI.message("📊 Current status: #{target_version.app_store_state}")
# Validate build attachment
if target_version.build.nil?
UI.user_error!("❌ No build attached to version #{app_version}")
elsif target_version.build.version != build_number
UI.user_error!("❌ Wrong build attached: found #{target_version.build.version}, expected #{build_number}")
else
UI.success("✅ Build #{build_number} correctly attached to version #{app_version}")
end
# Check submission status
valid_states = ["WAITING_FOR_REVIEW", "IN_REVIEW"]
unless valid_states.include?(target_version.app_store_state)
UI.user_error!("❌ Unexpected submission state: #{target_version.app_store_state}. Expected one of: #{valid_states.join(', ')}")
end
UI.success("🎉 Verification complete: Version #{app_version} with build #{build_number} successfully submitted!")
else
UI.success("🧪 DRY RUN: Skipping App Store Connect verification")
end
end
end