Link the Android APK through a stable URL and bump it on release (#28143)

* Link the Android APK through a stable URL and bump it on release

READMEs, the docs site, and the desktop app now link
https://www.onorca.dev/download/android, which the marketing site
redirects to the current APK, so none of them need a per-release edit.
A published Android release opens a PR on the marketing site that bumps
its one ANDROID_RELEASE_VERSION constant; a failed bump is reported in
the job summary and does not fail the release.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* Skip the APK download bump for OTA-shell test builds

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* Bump the APK download for every published Android release

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
Jinwoo Hong
2026-10-12 23:00:27 -04:00
committed by GitHub
parent 4d1001fba6
commit 5a77f068b6
13 changed files with 172 additions and 19 deletions
@@ -38,6 +38,10 @@ jobs:
permissions:
contents: write
outputs:
publish_release: ${{ steps.release.outputs.publish_release }}
version: ${{ steps.release.outputs.version }}
defaults:
run:
working-directory: mobile
@@ -231,3 +235,76 @@ jobs:
android/app/build/outputs/apk/release/*.apk \
"${{ steps.apk.outputs.checksum }}"
fi
# Why a separate job: the release is already published here, so a failed bump only leaves
# the site's APK download on the previous release and must not fail the release run.
bump-apk-download:
needs: android-build
if: needs.android-build.outputs.publish_release == 'true'
runs-on: ubuntu-latest
continue-on-error: true
permissions:
contents: read
env:
VERSION: ${{ needs.android-build.outputs.version }}
SITE_REPO: stablyai/orca-marketing-website
BRANCH: bump/android-apk-${{ needs.android-build.outputs.version }}
steps:
- name: Checkout orca
uses: actions/checkout@v6
with:
sparse-checkout: config/scripts
# Why the buf0-bot app, as in homebrew-bump.yml: GITHUB_TOKEN cannot push to another
# repo, and a PR it opened would not run the site's checks.
- name: Generate buf0-bot token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: 2590194
private-key: ${{ secrets.BUFO_BOT_PRIVATE_KEY }}
owner: stablyai
repositories: orca-marketing-website
permission-contents: write
permission-pull-requests: write
- name: Checkout marketing site
uses: actions/checkout@v6
with:
repository: stablyai/orca-marketing-website
path: site
token: ${{ steps.app-token.outputs.token }}
- name: Open the version bump PR
working-directory: site
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
node ../config/scripts/bump-android-apk-download-version.mjs src/lib/downloadTargets.ts "$VERSION"
if git diff --quiet; then
echo "The site's main branch already has Android APK $VERSION or newer." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
title="Point the Android APK download at $VERSION"
git config user.name "buf0-bot[bot]"
git config user.email "252831055+buf0-bot[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git commit -am "$title"
# Why --force: a rerun rebuilds the branch on the site's current main.
git push --force origin "$BRANCH"
# Why: a rerun of the same release reuses its open PR instead of opening another.
pr="$(gh pr list --repo "$SITE_REPO" --head "$BRANCH" --state open --json url --jq '.[].url')"
if [ -z "$pr" ]; then
pr="$(gh pr create --repo "$SITE_REPO" --base main --head "$BRANCH" --title "$title" \
--body "Opened by the [Android release run]($GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID) for mobile-android-v$VERSION. Merging points /download/android and every APK link on the site at the new APK.")"
fi
echo "Android APK download bump: $pr" >> "$GITHUB_STEP_SUMMARY"
- name: Report a failed bump
if: failure()
run: |
echo "::warning::The release is published, but the site's Android APK download was not bumped to $VERSION."
printf '### Android APK download not bumped\n\nThe release is published, but /download/android still serves the previous APK. Rerun this job, or set `ANDROID_RELEASE_VERSION` in `src/lib/downloadTargets.ts` of %s to `%s` by hand.\n' "$SITE_REPO" "$VERSION" >> "$GITHUB_STEP_SUMMARY"
+2 -2
View File
@@ -36,7 +36,7 @@
Monitor and steer your agents from your phone — get notified when an agent finishes and send follow-ups from anywhere.
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK 0.0.55](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://www.onorca.dev/download/android) · [Docs →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -234,7 +234,7 @@ yay -S stably-orca-bin
Pair with your desktop app to monitor and steer your agents from your phone.
- **iOS:** [Download on the App Store](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android:** [Download APK 0.0.55](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [Install guide](https://www.onorca.dev/docs/android-apk)
- **Android:** [Download APK](https://www.onorca.dev/download/android) · [Install guide](https://www.onorca.dev/docs/android-apk)
---
@@ -0,0 +1,41 @@
#!/usr/bin/env node
// Rewrites the marketing site's ANDROID_RELEASE_VERSION, which /download/android and every
// APK link derive from. Usage: bump-android-apk-download-version.mjs <downloadTargets.ts> <x.y.z>
import { readFileSync, writeFileSync } from 'node:fs'
import { pathToFileURL } from 'node:url'
import { compareReleaseTags } from './stable-release-tags.mjs'
const VERSION_LINE = /^(export const ANDROID_RELEASE_VERSION = ")(\d+\.\d+\.\d+)(";)$/m
/** The source pointing at `version`, unchanged when it already names that or a newer one. */
export function bumpAndroidApkDownloadVersion(source, version) {
if (!/^\d+\.\d+\.\d+$/.test(version)) {
throw new Error(`Android release version must use x.y.z format: ${version}`)
}
const match = VERSION_LINE.exec(source)
if (!match) {
throw new Error('ANDROID_RELEASE_VERSION declaration not found')
}
// Why: rerunning an older release must not walk the download back.
if (compareReleaseTags(match[2], version) >= 0) {
return { previousVersion: match[2], source }
}
return {
previousVersion: match[2],
source: source.replace(VERSION_LINE, `$1${version}$3`)
}
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const [file, version] = process.argv.slice(2)
const before = readFileSync(file, 'utf8')
const { previousVersion, source } = bumpAndroidApkDownloadVersion(before, version)
if (source === before) {
console.log(`Android APK download already at ${previousVersion}; nothing to bump`)
} else {
writeFileSync(file, source)
console.log(`Android APK download bumped ${previousVersion} -> ${version}`)
}
}
@@ -0,0 +1,36 @@
import { describe, expect, it } from 'vitest'
import { bumpAndroidApkDownloadVersion } from './bump-android-apk-download-version.mjs'
const source = (version) =>
[
'import { getDownloadPlatforms } from "./downloadPlatforms";',
'',
`export const ANDROID_RELEASE_VERSION = "${version}";`,
'export const ANDROID_DOWNLOAD_URL = `https://github.com/stablyai/orca/releases/download/mobile-android-v${ANDROID_RELEASE_VERSION}/app-release.apk`;',
''
].join('\n')
describe('bumpAndroidApkDownloadVersion', () => {
it('rewrites only the version declaration', () => {
expect(bumpAndroidApkDownloadVersion(source('0.0.52'), '0.0.55')).toEqual({
previousVersion: '0.0.52',
source: source('0.0.55')
})
})
it('compares numerically, not as text', () => {
expect(bumpAndroidApkDownloadVersion(source('0.0.9'), '0.0.10').source).toBe(source('0.0.10'))
})
it('leaves the same or a newer version in place', () => {
expect(bumpAndroidApkDownloadVersion(source('0.0.55'), '0.0.55').source).toBe(source('0.0.55'))
expect(bumpAndroidApkDownloadVersion(source('0.0.56'), '0.0.55').source).toBe(source('0.0.56'))
})
it('fails loudly when the declaration moved or the version is malformed', () => {
expect(() => bumpAndroidApkDownloadVersion('export const OTHER = "0.0.1";', '0.0.55')).toThrow(
'ANDROID_RELEASE_VERSION declaration not found'
)
expect(() => bumpAndroidApkDownloadVersion(source('0.0.52'), 'v0.0.55')).toThrow('x.y.z')
})
})
+2 -2
View File
@@ -36,7 +36,7 @@
Supervisa y dirige a tus agentes desde el teléfono — recibe una notificación cuando un agente termine y envía instrucciones de seguimiento desde cualquier lugar.
[App Store de iOS](https://apps.apple.com/us/app/orca-ide/id6766130217) · [APK para Android](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
[App Store de iOS](https://apps.apple.com/us/app/orca-ide/id6766130217) · [APK para Android](https://www.onorca.dev/download/android) · [Docs →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -231,7 +231,7 @@ yay -S stably-orca-bin
Vincúlala con tu app de escritorio para supervisar y dirigir a tus agentes desde el teléfono.
- **iOS:** [Descargar desde App Store](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android:** [Descargar el APK](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk)
- **Android:** [Descargar el APK](https://www.onorca.dev/download/android)
---
+2 -2
View File
@@ -40,7 +40,7 @@
Surveillez et pilotez vos agents depuis votre téléphone — soyez notifié quand un agent termine, et envoyez des instructions de suivi où que vous soyez.
[App Store iOS](https://apps.apple.com/us/app/orca-ide/id6766130217) · [APK Android 0.0.55](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
[App Store iOS](https://apps.apple.com/us/app/orca-ide/id6766130217) · [APK Android](https://www.onorca.dev/download/android) · [Docs →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -238,7 +238,7 @@ yay -S stably-orca-bin
Associez-la à l'app de bureau pour surveiller et piloter vos agents depuis votre téléphone.
- **iOS :** [Télécharger sur l'App Store](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android :** [Télécharger l'APK 0.0.55](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk)
- **Android :** [Télécharger l'APK](https://www.onorca.dev/download/android)
---
+2 -2
View File
@@ -36,7 +36,7 @@
スマートフォンからエージェントを監視・操作 — エージェントの完了を通知で受け取り、どこからでもフォローアップを送信できます。
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [ドキュメント →](https://www.onorca.dev/docs/mobile)
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://www.onorca.dev/download/android) · [ドキュメント →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -231,7 +231,7 @@ yay -S stably-orca-bin
デスクトップアプリとペアリングして、スマートフォンからエージェントを監視・操作できます。
- **iOS:** [App Store からダウンロード](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android:** [APK をダウンロード](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk)
- **Android:** [APK をダウンロード](https://www.onorca.dev/download/android)
---
+2 -2
View File
@@ -36,7 +36,7 @@
휴대폰에서 에이전트를 모니터링하고 조종하세요 — 에이전트가 완료되면 알림을 받고 어디서든 후속 지시를 보낼 수 있습니다.
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK 0.0.55](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [문서 →](https://www.onorca.dev/docs/mobile)
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://www.onorca.dev/download/android) · [문서 →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -233,7 +233,7 @@ yay -S stably-orca-bin
데스크톱 앱과 페어링해 휴대폰에서 에이전트를 모니터링하고 조종하세요.
- **iOS:** [App Store에서 다운로드](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android:** [APK 0.0.55 다운로드](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [설치 가이드](https://www.onorca.dev/docs/android-apk)
- **Android:** [APK 다운로드](https://www.onorca.dev/download/android) · [설치 가이드](https://www.onorca.dev/docs/android-apk)
---
+2 -2
View File
@@ -36,7 +36,7 @@
Monitore e conduza seus agentes pelo celular — receba uma notificação quando um agente terminar e envie instruções de acompanhamento de qualquer lugar.
[App Store para iOS](https://apps.apple.com/us/app/orca-ide/id6766130217) · [APK Android 0.0.55](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
[App Store para iOS](https://apps.apple.com/us/app/orca-ide/id6766130217) · [APK Android](https://www.onorca.dev/download/android) · [Docs →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -233,7 +233,7 @@ yay -S stably-orca-bin
Conecte ao app desktop para monitorar e conduzir seus agentes pelo celular.
- **iOS:** [Baixar na App Store](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android:** [Baixar APK 0.0.55](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk)
- **Android:** [Baixar APK](https://www.onorca.dev/download/android)
---
+2 -2
View File
@@ -36,7 +36,7 @@
用手机监控并指挥你的智能体 — 智能体完成时收到通知,随时随地发送后续指令。
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk) · [文档 →](https://www.onorca.dev/docs/mobile)
[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://www.onorca.dev/download/android) · [文档 →](https://www.onorca.dev/docs/mobile)
</td>
<td width="50%">
@@ -231,7 +231,7 @@ yay -S stably-orca-bin
与桌面应用配对,用手机监控并指挥你的智能体。
- **iOS:** [从 App Store 下载](https://apps.apple.com/us/app/orca-ide/id6766130217)
- **Android:** [下载 APK](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.55/app-release.apk)
- **Android:** [下载 APK](https://www.onorca.dev/download/android)
---
+2 -2
View File
@@ -10,8 +10,8 @@ The Orca mobile companion is an iOS/Android app that pairs with your desktop Orc
<Callout title="Beta">
The mobile companion is in beta. Install iOS from the [App
Store](https://apps.apple.com/us/app/orca-ide/id6766130217), join the [TestFlight preview
channel](https://testflight.apple.com/join/YjeGMQBA), or install Android from the [current APK
0.0.48](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.48/app-release.apk).
channel](https://testflight.apple.com/join/YjeGMQBA), or install Android from the [current
APK](https://www.onorca.dev/download/android).
</Callout>
## What you can do from mobile
@@ -22,7 +22,7 @@ const IOS_CHANNEL_COPY: Record<IosChannel, InstallCopy> = {
const ANDROID_COPY: InstallCopy = {
ctaLabel: 'Download APK',
url: 'https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.52/app-release.apk'
url: 'https://www.onorca.dev/download/android'
}
export function getInstallCopy(platform: Platform, iosChannel: IosChannel): InstallCopy {
@@ -12,8 +12,7 @@ import { MobileRelayBetaNotice } from './MobileRelayBetaNotice'
export { getMobileSettingsPaneSearchEntries }
const ORCA_IOS_APP_STORE_URL = 'https://apps.apple.com/app/orca-ide/id6766130217'
const ORCA_ANDROID_APK_URL =
'https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.52/app-release.apk'
const ORCA_ANDROID_APK_URL = 'https://www.onorca.dev/download/android'
export function MobileSettingsPane(): React.JSX.Element {
const showMobileButton = useAppStore((s) => s.settings?.showMobileButton !== false)