Skip to content

Commit ff35f3b

Browse files
committed
Track mobile release SHAs with refs
1 parent ff18374 commit ff35f3b

10 files changed

Lines changed: 876 additions & 1 deletion

apps/android/VERSIONING.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,38 @@ Recommended workflow:
5656

5757
The third-party flavor is archived as a signed APK for non-Play distribution. It is not uploaded by the Play release lane.
5858

59+
## Release SHA tracking
60+
61+
Successful Play build uploads create a non-tag Git ref that records the source
62+
commit for the uploaded store build:
63+
64+
```text
65+
refs/openclaw/mobile-releases/android/<versionName>-<versionCode>
66+
```
67+
68+
Example:
69+
70+
```text
71+
refs/openclaw/mobile-releases/android/2026.6.10-2026061008
72+
```
73+
74+
These refs are intentionally outside `refs/tags/*` and `refs/heads/*`. They do
75+
not appear on GitHub release or tag pages, and they do not participate in the
76+
core OpenClaw release machinery.
77+
78+
`pnpm android:release:upload` checks the ref before uploading the Play build and
79+
records it only after `upload_to_play_store` succeeds. Existing refs are
80+
immutable: the same ref at the same SHA is accepted, while the same ref at a
81+
different SHA fails. `GOOGLE_PLAY_VALIDATE_ONLY=1` still checks the ref but does
82+
not record it because no Play build is published.
83+
84+
Useful direct commands:
85+
86+
```bash
87+
pnpm mobile:release:preflight -- --platform android --version 2026.6.10 --version-code 2026061008
88+
pnpm mobile:release:resolve -- --platform android --version 2026.6.10 --version-code 2026061008
89+
```
90+
5991
## Signing model
6092

6193
`apps/android/Config/ReleaseSigning.json` pins the Android signing assets in the shared private `apps-signing` repo. The Android pipeline uses the same `MATCH_PASSWORD` release-owner secret as iOS, but the Android files are managed by `scripts/android-release-signing.mjs` instead of Fastlane `match`.

apps/android/fastlane/Fastfile

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,58 @@ def capture_android_screenshots!
198198
sh(shell_join(["bash", File.join(repo_root, "scripts", "android-screenshots.sh")]))
199199
end
200200

201+
def mobile_release_ref_script
202+
File.join(repo_root, "scripts", "mobile-release-ref.ts")
203+
end
204+
205+
def release_git_sha
206+
stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
207+
UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
208+
stdout.strip
209+
end
210+
211+
def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
212+
args = [
213+
"node",
214+
"--import",
215+
"tsx",
216+
mobile_release_ref_script,
217+
command,
218+
"--platform",
219+
platform,
220+
"--version",
221+
version,
222+
"--root",
223+
repo_root,
224+
]
225+
args.push("--build", build.to_s) if build
226+
args.push("--version-code", version_code.to_s) if version_code
227+
args.push("--sha", sha.to_s) if sha
228+
sh(shell_join(args))
229+
end
230+
231+
def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
232+
mobile_release_ref_command(
233+
"preflight",
234+
platform: platform,
235+
version: version,
236+
build: build,
237+
version_code: version_code,
238+
sha: sha
239+
)
240+
end
241+
242+
def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
243+
mobile_release_ref_command(
244+
"record",
245+
platform: platform,
246+
version: version,
247+
build: build,
248+
version_code: version_code,
249+
sha: sha
250+
)
251+
end
252+
201253
def read_android_release_signing_properties!(path)
202254
UI.user_error!("Missing materialized Android release signing properties at #{path}.") unless File.exist?(path)
203255

@@ -282,6 +334,13 @@ def upload_play_store_metadata!(version_metadata)
282334
end
283335

284336
def upload_play_store_build!(version_metadata, upload_metadata: false, upload_images: false, upload_screenshots: false)
337+
release_sha = release_git_sha
338+
ensure_mobile_release_ref_available!(
339+
platform: "android",
340+
version: version_metadata.fetch(:version),
341+
version_code: version_metadata.fetch(:version_code),
342+
sha: release_sha
343+
)
285344
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1" if upload_screenshots
286345
validate_android_screenshots!
287346
sync_android_changelog!(version_metadata.fetch(:version_code))
@@ -302,6 +361,15 @@ def upload_play_store_build!(version_metadata, upload_metadata: false, upload_im
302361
skip_upload_screenshots: !upload_screenshots,
303362
validate_only: play_validate_only?
304363
)
364+
365+
unless play_validate_only?
366+
record_mobile_release_ref!(
367+
platform: "android",
368+
version: version_metadata.fetch(:version),
369+
version_code: version_metadata.fetch(:version_code),
370+
sha: release_sha
371+
)
372+
end
305373
end
306374

307375
load_env_file(File.join(ANDROID_FASTLANE_ROOT, ".env"))

apps/ios/VERSIONING.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,37 @@ pnpm ios:version:pin -- --version 2026.4.10
129129

130130
This keeps the TestFlight version stable while review is in flight.
131131

132+
## Release SHA tracking
133+
134+
Successful App Store Connect uploads create a non-tag Git ref that records the
135+
source commit for the uploaded store build:
136+
137+
```text
138+
refs/openclaw/mobile-releases/ios/<CFBundleShortVersionString>-<CFBundleVersion>
139+
```
140+
141+
Example:
142+
143+
```text
144+
refs/openclaw/mobile-releases/ios/2026.6.10-8
145+
```
146+
147+
These refs are intentionally outside `refs/tags/*` and `refs/heads/*`. They do
148+
not appear on GitHub release or tag pages, and they do not participate in the
149+
core OpenClaw release machinery.
150+
151+
`pnpm ios:release:upload` checks the ref before archive/upload work and records
152+
it only after `upload_to_testflight` succeeds. Existing refs are immutable: the
153+
same ref at the same SHA is accepted, while the same ref at a different SHA
154+
fails.
155+
156+
Useful direct commands:
157+
158+
```bash
159+
pnpm mobile:release:preflight -- --platform ios --version 2026.6.10 --build 8
160+
pnpm mobile:release:resolve -- --platform ios --version 2026.6.10 --build 8
161+
```
162+
132163
## New release promotion workflow
133164

134165
When you want the next production iOS release to align with the current gateway release:

apps/ios/fastlane/Fastfile

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,58 @@ def prepare_app_store_release!(version:, build_number:)
11281128
release_xcconfig
11291129
end
11301130

1131+
def mobile_release_ref_script
1132+
File.join(repo_root, "scripts", "mobile-release-ref.ts")
1133+
end
1134+
1135+
def release_git_sha
1136+
stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
1137+
UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
1138+
stdout.strip
1139+
end
1140+
1141+
def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
1142+
args = [
1143+
"node",
1144+
"--import",
1145+
"tsx",
1146+
mobile_release_ref_script,
1147+
command,
1148+
"--platform",
1149+
platform,
1150+
"--version",
1151+
version,
1152+
"--root",
1153+
repo_root,
1154+
]
1155+
args.push("--build", build.to_s) if build
1156+
args.push("--version-code", version_code.to_s) if version_code
1157+
args.push("--sha", sha.to_s) if sha
1158+
sh(shell_join(args))
1159+
end
1160+
1161+
def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
1162+
mobile_release_ref_command(
1163+
"preflight",
1164+
platform: platform,
1165+
version: version,
1166+
build: build,
1167+
version_code: version_code,
1168+
sha: sha
1169+
)
1170+
end
1171+
1172+
def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
1173+
mobile_release_ref_command(
1174+
"record",
1175+
platform: platform,
1176+
version: version,
1177+
build: build,
1178+
version_code: version_code,
1179+
sha: sha
1180+
)
1181+
end
1182+
11311183
def validate_app_store_ipa!(ipa_path)
11321184
script_path = File.join(repo_root, "scripts", "ios-validate-app-store-ipa.sh")
11331185
sh(shell_join(["bash", script_path, "--ipa", ipa_path]))
@@ -1309,15 +1361,22 @@ platform :ios do
13091361
UI.user_error!("Use `pnpm ios:release:upload`; direct Fastlane TestFlight upload is disabled.")
13101362
end
13111363

1364+
release_sha = release_git_sha
13121365
release_signing_check!
13131366
preserve_local_signing do
13141367
screenshots
13151368
end
1369+
context = prepare_app_store_context(require_api_key: true)
1370+
ensure_mobile_release_ref_available!(
1371+
platform: "ios",
1372+
version: context[:short_version],
1373+
build: context[:build_number],
1374+
sha: release_sha
1375+
)
13161376
ENV["DELIVER_SCREENSHOTS"] = "1"
13171377
ENV["DELIVER_RELEASE_NOTES"] = "1"
13181378
metadata
13191379

1320-
context = prepare_app_store_context(require_api_key: true)
13211380
build = build_app_store_release(context)
13221381

13231382
upload_to_testflight(
@@ -1326,6 +1385,12 @@ platform :ios do
13261385
skip_waiting_for_build_processing: true,
13271386
uses_non_exempt_encryption: false
13281387
)
1388+
record_mobile_release_ref!(
1389+
platform: "ios",
1390+
version: build[:short_version],
1391+
build: build[:build_number],
1392+
sha: release_sha
1393+
)
13291394

13301395
UI.success("Uploaded iOS App Store build: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}")
13311396
UI.important("App Review submission remains manual in App Store Connect.")

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1680,6 +1680,9 @@
16801680
"mac:open": "open dist/OpenClaw.app",
16811681
"mac:package": "bash scripts/package-mac-app.sh",
16821682
"mac:restart": "bash scripts/restart-mac.sh",
1683+
"mobile:release:preflight": "node --import tsx scripts/mobile-release-ref.ts preflight",
1684+
"mobile:release:record": "node --import tsx scripts/mobile-release-ref.ts record",
1685+
"mobile:release:resolve": "node --import tsx scripts/mobile-release-ref.ts resolve",
16831686
"openclaw": "node scripts/run-node.mjs",
16841687
"openclaw:rpc": "node scripts/run-node.mjs agent --mode rpc --json",
16851688
"perf:issue-78851": "node --import tsx scripts/perf/issue-78851-model-resolution.ts",

0 commit comments

Comments
 (0)