
























1+require "fileutils"
2+require "json"
3+require "open3"
4+require "shellwords"
5+require "tempfile"
6+7+default_platform(:android)
8+9+DEFAULT_PLAY_PACKAGE_NAME = "ai.openclaw.app"
10+DEFAULT_PLAY_TRACK = "internal"
11+DEFAULT_PLAY_RELEASE_STATUS = "completed"
12+13+def load_env_file(path)
14+return unless File.exist?(path)
15+16+File.foreach(path) do |line|
17+stripped = line.strip
18+next if stripped.empty? || stripped.start_with?("#")
19+20+key, value = stripped.split("=", 2)
21+next if key.nil? || key.empty? || value.nil?
22+23+ENV[key] = value if ENV[key].nil? || ENV[key].strip.empty?
24+end
25+end
26+27+def env_present?(value)
28+ !value.nil? && !value.strip.empty?
29+end
30+31+def android_root
32+File.expand_path("..", __dir__)
33+end
34+35+def repo_root
36+File.expand_path("../..", android_root)
37+end
38+39+def shell_join(args)
40+args.shelljoin
41+end
42+43+def play_package_name
44+raw = ENV["GOOGLE_PLAY_PACKAGE_NAME"].to_s.strip
45+raw.empty? ? DEFAULT_PLAY_PACKAGE_NAME : raw
46+end
47+48+def play_track
49+raw = ENV["GOOGLE_PLAY_TRACK"].to_s.strip
50+raw.empty? ? DEFAULT_PLAY_TRACK : raw
51+end
52+53+def play_release_status
54+raw = ENV["GOOGLE_PLAY_RELEASE_STATUS"].to_s.strip
55+raw.empty? ? DEFAULT_PLAY_RELEASE_STATUS : raw
56+end
57+58+def play_validate_only?
59+ENV["GOOGLE_PLAY_VALIDATE_ONLY"] == "1"
60+end
61+62+def play_metadata_upload_requested?
63+ENV["SUPPLY_UPLOAD_METADATA"] == "1"
64+end
65+66+def play_screenshot_upload_requested?
67+ENV["SUPPLY_UPLOAD_SCREENSHOTS"] == "1"
68+end
69+70+def play_image_upload_requested?
71+ENV["SUPPLY_UPLOAD_IMAGES"] == "1"
72+end
73+74+def play_auth_options
75+json_key = ENV["GOOGLE_PLAY_JSON_KEY"].to_s.strip
76+json_key = ENV["SUPPLY_JSON_KEY"].to_s.strip if json_key.empty?
77+json_key = ENV["GOOGLE_PLAY_JSON_KEY_PATH"].to_s.strip if json_key.empty?
78+return { json_key: json_key } unless json_key.empty?
79+80+json_key_data = ENV["GOOGLE_PLAY_JSON_KEY_DATA"].to_s.strip
81+json_key_data = ENV["SUPPLY_JSON_KEY_DATA"].to_s.strip if json_key_data.empty?
82+return { json_key_data: json_key_data } unless json_key_data.empty?
83+84+UI.user_error!("Missing Google Play API credentials. Set GOOGLE_PLAY_JSON_KEY or GOOGLE_PLAY_JSON_KEY_DATA.")
85+end
86+87+def with_play_json_key_file
88+auth = play_auth_options
89+if auth[:json_key]
90+yield auth[:json_key]
91+return
92+end
93+94+Tempfile.create(["openclaw-google-play", ".json"]) do |file|
95+file.write(auth.fetch(:json_key_data))
96+file.flush
97+yield file.path
98+end
99+end
100+101+def read_android_version_metadata
102+stdout, stderr, status = Open3.capture3(
103+"node",
104+"--import",
105+"tsx",
106+File.join(repo_root, "scripts", "android-version.ts"),
107+"--json"
108+)
109+unless status.success?
110+detail = stderr.to_s.strip
111+detail = stdout.to_s.strip if detail.empty?
112+UI.user_error!("Failed to read Android version metadata: #{detail}")
113+end
114+115+parsed = JSON.parse(stdout)
116+version = parsed.fetch("canonicalVersion").to_s
117+version_code = parsed.fetch("versionCode").to_i
118+UI.user_error!("Android version helper returned incomplete metadata.") if version.empty? || version_code <= 0
119+120+{ version: version, version_code: version_code }
121+rescue JSON::ParserError => e
122+UI.user_error!("Invalid JSON from Android version helper: #{e.message}")
123+end
124+125+def sync_android_versioning!
126+sh(shell_join(["node", "--import", "tsx", File.join(repo_root, "scripts", "android-sync-versioning.ts"), "--check"]))
127+end
128+129+def android_release_notes_path
130+File.join(__dir__, "metadata", "android", "en-US", "release_notes.txt")
131+end
132+133+def android_changelog_path(version_code)
134+File.join(__dir__, "metadata", "android", "en-US", "changelogs", "#{version_code}.txt")
135+end
136+137+def sync_android_changelog!(version_code)
138+release_notes_path = android_release_notes_path
139+UI.user_error!("Missing Android release notes at #{release_notes_path}.") unless File.exist?(release_notes_path)
140+141+changelog_path = android_changelog_path(version_code)
142+FileUtils.mkdir_p(File.dirname(changelog_path))
143+File.write(changelog_path, File.read(release_notes_path))
144+changelog_path
145+end
146+147+def play_metadata_path
148+File.join(__dir__, "metadata", "android")
149+end
150+151+def play_screenshot_paths
152+Dir[File.join(play_metadata_path, "**", "images", "**", "*.png")]
153+end
154+155+def validate_android_screenshots!
156+return unless play_screenshot_upload_requested?
157+158+if play_screenshot_paths.empty?
159+UI.user_error!("SUPPLY_UPLOAD_SCREENSHOTS=1 but no PNG screenshots were found under apps/android/fastlane/metadata/android/*/images.")
160+end
161+end
162+163+def release_artifact_path(version)
164+File.join(android_root, "build", "release-artifacts", "openclaw-#{version}-play-release.aab")
165+end
166+167+def build_release_artifacts!
168+sh(shell_join(["bun", File.join(android_root, "scripts", "build-release-artifacts.ts")]))
169+end
170+171+def upload_play_store_metadata!(version_metadata)
172+validate_android_screenshots!
173+sync_android_changelog!(version_metadata.fetch(:version_code))
174+175+upload_to_play_store(
176+ **play_auth_options,
177+package_name: play_package_name,
178+metadata_path: play_metadata_path,
179+skip_upload_apk: true,
180+skip_upload_aab: true,
181+skip_upload_metadata: !play_metadata_upload_requested?,
182+skip_upload_changelogs: false,
183+skip_upload_images: !play_image_upload_requested?,
184+skip_upload_screenshots: !play_screenshot_upload_requested?,
185+validate_only: play_validate_only?
186+)
187+end
188+189+def upload_play_store_build!(version_metadata)
190+artifact_path = release_artifact_path(version_metadata.fetch(:version))
191+UI.user_error!("Missing Play release artifact at #{artifact_path}. Run pnpm android:release:archive first.") unless File.exist?(artifact_path)
192+193+upload_to_play_store(
194+ **play_auth_options,
195+package_name: play_package_name,
196+aab: artifact_path,
197+track: play_track,
198+release_status: play_release_status,
199+metadata_path: play_metadata_path,
200+skip_upload_apk: true,
201+skip_upload_metadata: true,
202+skip_upload_changelogs: false,
203+skip_upload_images: true,
204+skip_upload_screenshots: true,
205+validate_only: play_validate_only?
206+)
207+end
208+209+load_env_file(File.join(__dir__, ".env"))
210+211+platform :android do
212+desc "Validate Google Play API credentials"
213+lane :auth_check do
214+with_play_json_key_file do |json_key_path|
215+validate_play_store_json_key(json_key: json_key_path)
216+end
217+UI.success("Google Play API credentials are valid.")
218+end
219+220+desc "Upload Google Play metadata, changelog, and optional screenshots"
221+lane :metadata do
222+sync_android_versioning!
223+version_metadata = read_android_version_metadata
224+ENV["SUPPLY_UPLOAD_METADATA"] = "1" unless ENV.key?("SUPPLY_UPLOAD_METADATA")
225+upload_play_store_metadata!(version_metadata)
226+UI.success("Uploaded Android Play metadata for #{version_metadata[:version]} (#{version_metadata[:version_code]}).")
227+end
228+229+desc "Build signed Android release artifacts locally without uploading"
230+lane :play_store_archive do
231+sync_android_versioning!
232+build_release_artifacts!
233+end
234+235+desc "Upload the signed Play AAB to Google Play"
236+lane :play_store do
237+sync_android_versioning!
238+version_metadata = read_android_version_metadata
239+upload_play_store_build!(version_metadata)
240+UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
241+end
242+243+desc "Upload Android metadata, archive release artifacts, then upload the Play AAB"
244+lane :release_upload do
245+auth_check
246+sync_android_versioning!
247+version_metadata = read_android_version_metadata
248+ENV["SUPPLY_UPLOAD_METADATA"] = "1"
249+upload_play_store_metadata!(version_metadata)
250+build_release_artifacts!
251+upload_play_store_build!(version_metadata)
252+UI.success("Uploaded Android Play build to #{play_track}: version=#{version_metadata[:version]} code=#{version_metadata[:version_code]}")
253+UI.important("Production promotion remains manual in Google Play Console.")
254+end
255+end
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。