mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-23 12:28:10 +00:00
275 lines
8.5 KiB
Ruby
275 lines
8.5 KiB
Ruby
require "fileutils"
|
|
require "json"
|
|
require "open3"
|
|
require "shellwords"
|
|
require "supply/client"
|
|
|
|
default_platform(:android)
|
|
|
|
DEFAULT_PLAY_PACKAGE_NAME = "ai.openclaw.app"
|
|
DEFAULT_PLAY_TRACK = "internal"
|
|
DEFAULT_PLAY_RELEASE_STATUS = "completed"
|
|
|
|
def load_env_file(path)
|
|
return unless File.exist?(path)
|
|
|
|
File.foreach(path) do |line|
|
|
stripped = line.strip
|
|
next if stripped.empty? || stripped.start_with?("#")
|
|
|
|
key, value = stripped.split("=", 2)
|
|
next if key.nil? || key.empty? || value.nil?
|
|
|
|
ENV[key] = value if ENV[key].nil? || ENV[key].strip.empty?
|
|
end
|
|
end
|
|
|
|
def env_present?(value)
|
|
!value.nil? && !value.strip.empty?
|
|
end
|
|
|
|
def android_root
|
|
File.expand_path("..", __dir__)
|
|
end
|
|
|
|
def repo_root
|
|
File.expand_path("../..", android_root)
|
|
end
|
|
|
|
def shell_join(args)
|
|
args.shelljoin
|
|
end
|
|
|
|
def play_package_name
|
|
raw = ENV["GOOGLE_PLAY_PACKAGE_NAME"].to_s.strip
|
|
raw.empty? ? DEFAULT_PLAY_PACKAGE_NAME : raw
|
|
end
|
|
|
|
def play_track
|
|
raw = ENV["GOOGLE_PLAY_TRACK"].to_s.strip
|
|
raw.empty? ? DEFAULT_PLAY_TRACK : raw
|
|
end
|
|
|
|
def play_release_status
|
|
raw = ENV["GOOGLE_PLAY_RELEASE_STATUS"].to_s.strip
|
|
raw.empty? ? DEFAULT_PLAY_RELEASE_STATUS : raw
|
|
end
|
|
|
|
def play_validate_only?
|
|
ENV["GOOGLE_PLAY_VALIDATE_ONLY"] == "1"
|
|
end
|
|
|
|
def play_metadata_upload_requested?
|
|
ENV["SUPPLY_UPLOAD_METADATA"] == "1"
|
|
end
|
|
|
|
def play_screenshot_upload_requested?
|
|
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] == "1"
|
|
end
|
|
|
|
def play_image_upload_requested?
|
|
ENV["SUPPLY_UPLOAD_IMAGES"] == "1"
|
|
end
|
|
|
|
def play_auth_options
|
|
json_key = ENV["GOOGLE_PLAY_JSON_KEY"].to_s.strip
|
|
json_key = ENV["SUPPLY_JSON_KEY"].to_s.strip if json_key.empty?
|
|
json_key = ENV["GOOGLE_PLAY_JSON_KEY_PATH"].to_s.strip if json_key.empty?
|
|
return { json_key: json_key } unless json_key.empty?
|
|
|
|
json_key_data = ENV["GOOGLE_PLAY_JSON_KEY_DATA"].to_s.strip
|
|
json_key_data = ENV["SUPPLY_JSON_KEY_DATA"].to_s.strip if json_key_data.empty?
|
|
return { json_key_data: json_key_data } unless json_key_data.empty?
|
|
|
|
UI.user_error!("Missing Google Play API credentials. Set GOOGLE_PLAY_JSON_KEY or GOOGLE_PLAY_JSON_KEY_DATA.")
|
|
end
|
|
|
|
def validate_play_auth!
|
|
client = nil
|
|
begin
|
|
client = Supply::Client.make_from_config(params: play_auth_options)
|
|
client.begin_edit(package_name: play_package_name)
|
|
rescue => e
|
|
UI.user_error!("Google Play API credentials are invalid for #{play_package_name}: #{e.message}")
|
|
ensure
|
|
if client&.current_edit
|
|
begin
|
|
client.abort_current_edit
|
|
rescue => e
|
|
UI.user_error!("Google Play API credentials opened a validation edit but could not close it: #{e.message}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def read_android_version_metadata
|
|
stdout, stderr, status = Open3.capture3(
|
|
"node",
|
|
"--import",
|
|
"tsx",
|
|
File.join(repo_root, "scripts", "android-version.ts"),
|
|
"--json",
|
|
"--root",
|
|
repo_root
|
|
)
|
|
unless status.success?
|
|
detail = stderr.to_s.strip
|
|
detail = stdout.to_s.strip if detail.empty?
|
|
UI.user_error!("Failed to read Android version metadata: #{detail}")
|
|
end
|
|
|
|
parsed = JSON.parse(stdout)
|
|
version = parsed.fetch("canonicalVersion").to_s
|
|
version_code = parsed.fetch("versionCode").to_i
|
|
UI.user_error!("Android version helper returned incomplete metadata.") if version.empty? || version_code <= 0
|
|
|
|
{ version: version, version_code: version_code }
|
|
rescue JSON::ParserError => e
|
|
UI.user_error!("Invalid JSON from Android version helper: #{e.message}")
|
|
end
|
|
|
|
def sync_android_versioning!
|
|
sh(shell_join(["node", "--import", "tsx", File.join(repo_root, "scripts", "android-sync-versioning.ts"), "--check", "--root", repo_root]))
|
|
end
|
|
|
|
def android_release_notes_path
|
|
File.join(__dir__, "metadata", "android", "en-US", "release_notes.txt")
|
|
end
|
|
|
|
def android_changelog_path(version_code)
|
|
File.join(__dir__, "metadata", "android", "en-US", "changelogs", "#{version_code}.txt")
|
|
end
|
|
|
|
def sync_android_changelog!(version_code)
|
|
release_notes_path = android_release_notes_path
|
|
UI.user_error!("Missing Android release notes at #{release_notes_path}.") unless File.exist?(release_notes_path)
|
|
|
|
changelog_path = android_changelog_path(version_code)
|
|
FileUtils.mkdir_p(File.dirname(changelog_path))
|
|
File.write(changelog_path, File.read(release_notes_path))
|
|
changelog_path
|
|
end
|
|
|
|
def play_metadata_path
|
|
File.join(__dir__, "metadata", "android")
|
|
end
|
|
|
|
def play_screenshot_paths
|
|
Dir[File.join(play_metadata_path, "**", "images", "**", "*.png")]
|
|
end
|
|
|
|
def validate_android_screenshots!
|
|
return unless play_screenshot_upload_requested?
|
|
|
|
if play_screenshot_paths.empty?
|
|
UI.user_error!("SUPPLY_UPLOAD_SCREENSHOTS=1 but no PNG screenshots were found under apps/android/fastlane/metadata/android/*/images.")
|
|
end
|
|
end
|
|
|
|
def release_artifact_path(version)
|
|
File.join(android_root, "build", "release-artifacts", "openclaw-#{version}-play-release.aab")
|
|
end
|
|
|
|
def build_release_artifacts!
|
|
sh(shell_join(["bun", File.join(android_root, "scripts", "build-release-artifacts.ts")]))
|
|
end
|
|
|
|
def capture_android_screenshots!
|
|
sh(shell_join(["bash", File.join(repo_root, "scripts", "android-screenshots.sh")]))
|
|
end
|
|
|
|
def upload_play_store_metadata!(version_metadata)
|
|
validate_android_screenshots!
|
|
sync_android_changelog!(version_metadata.fetch(:version_code))
|
|
|
|
upload_to_play_store(
|
|
**play_auth_options,
|
|
package_name: play_package_name,
|
|
track: play_track,
|
|
version_code: version_metadata.fetch(:version_code),
|
|
metadata_path: play_metadata_path,
|
|
skip_upload_apk: true,
|
|
skip_upload_aab: true,
|
|
skip_upload_metadata: !play_metadata_upload_requested?,
|
|
skip_upload_changelogs: false,
|
|
skip_upload_images: !play_image_upload_requested?,
|
|
skip_upload_screenshots: !play_screenshot_upload_requested?,
|
|
validate_only: play_validate_only?
|
|
)
|
|
end
|
|
|
|
def upload_play_store_build!(version_metadata, upload_metadata: false, upload_images: false, upload_screenshots: false)
|
|
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1" if upload_screenshots
|
|
validate_android_screenshots!
|
|
sync_android_changelog!(version_metadata.fetch(:version_code))
|
|
artifact_path = release_artifact_path(version_metadata.fetch(:version))
|
|
UI.user_error!("Missing Play release artifact at #{artifact_path}. Run pnpm android:release:archive first.") unless File.exist?(artifact_path)
|
|
|
|
upload_to_play_store(
|
|
**play_auth_options,
|
|
package_name: play_package_name,
|
|
aab: artifact_path,
|
|
track: play_track,
|
|
release_status: play_release_status,
|
|
metadata_path: play_metadata_path,
|
|
skip_upload_apk: true,
|
|
skip_upload_metadata: !upload_metadata,
|
|
skip_upload_changelogs: false,
|
|
skip_upload_images: !upload_images,
|
|
skip_upload_screenshots: !upload_screenshots,
|
|
validate_only: play_validate_only?
|
|
)
|
|
end
|
|
|
|
load_env_file(File.join(__dir__, ".env"))
|
|
|
|
platform :android do
|
|
desc "Validate Google Play API credentials"
|
|
lane :auth_check do
|
|
validate_play_auth!
|
|
UI.success("Google Play API credentials are valid.")
|
|
end
|
|
|
|
desc "Upload Google Play metadata, changelog, and optional screenshots"
|
|
lane :metadata do
|
|
sync_android_versioning!
|
|
version_metadata = read_android_version_metadata
|
|
ENV["SUPPLY_UPLOAD_METADATA"] = "1" unless ENV.key?("SUPPLY_UPLOAD_METADATA")
|
|
upload_play_store_metadata!(version_metadata)
|
|
UI.success("Uploaded Android Play metadata for #{version_metadata[:version]} (#{version_metadata[:version_code]}).")
|
|
end
|
|
|
|
desc "Build signed Android release artifacts locally without uploading"
|
|
lane :play_store_archive do
|
|
sync_android_versioning!
|
|
build_release_artifacts!
|
|
end
|
|
|
|
desc "Generate deterministic Android screenshots for Google Play metadata"
|
|
lane :screenshots do
|
|
capture_android_screenshots!
|
|
end
|
|
|
|
desc "Upload the signed Play AAB to Google Play"
|
|
lane :play_store do
|
|
sync_android_versioning!
|
|
version_metadata = read_android_version_metadata
|
|
upload_play_store_build!(version_metadata)
|
|
UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
|
|
end
|
|
|
|
desc "Upload Android metadata, archive release artifacts, then upload the Play AAB"
|
|
lane :release_upload do
|
|
auth_check
|
|
sync_android_versioning!
|
|
version_metadata = read_android_version_metadata
|
|
screenshots
|
|
ENV["SUPPLY_UPLOAD_METADATA"] = "1"
|
|
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1"
|
|
build_release_artifacts!
|
|
upload_play_store_build!(version_metadata, upload_metadata: true, upload_screenshots: true)
|
|
UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
|
|
UI.important("Production promotion remains manual in Google Play Console.")
|
|
end
|
|
end
|