Automated Android CI/CD pipeline to Google Play — supports TWA, React Native, Flutter, and native Android. Run npx android-cicd to set up keystore generation, G
复制下面这句话,粘贴给 Claude Code、Codex、Cursor 等 AI 编程工具,它会读取安装说明并在你确认后完成安装。
请阅读 https://ai.atlankj.com/install/asset/gh-android-cicd-975447ba2dd9 ,按照其中的说明把「android-cicd」安装到你(当前 AI 工具)中。执行前先告诉我将运行的命令和写入的位置,等我确认。
查看 AI 将读取的安装说明正在读取 GitHub 原文…
内容来自 GitHub 原始文件,由原作者维护。在 GitHub 查看
Set up a complete, multi-stage Android CI/CD pipeline that automatically builds and publishes to Google Play via GitHub Actions. Supports TWA (Trusted Web Activity / Bubblewrap), React Native, Flutter, and native Android (Gradle) projects.
main and on version tagsversionCode bumpingRun the interactive setup wizard from the root of the target project:
npx android-cicd
The wizard handles: framework detection → keystore generation → GitHub Secrets → workflow scaffold.
Before running the wizard, ensure:
keytool accessible (JAVA_HOME set, or installed via Eclipse Adoptium / Android Studio)gh CLI installed and authenticated (gh auth login)The wizard auto-detects the framework from the project directory structure:
| Condition | Detected framework |
|---|---|
pubspec.yaml contains flutter: | flutter |
android/app/build.gradle exists + package.json has react-native dep | react-native |
android-root-app/build.gradle or twa-manifest.json or .bubblewrap/config.json exists | twa |
app/build.gradle exists | native |
android/app/build.gradle exists (fallback) | native |
The user can override the detected framework during the wizard.
The scaffolded workflow publishes to different tracks based on the git ref:
| Git event | Google Play track |
|---|---|
Push to main | internal |
Tag matching v*-alpha (e.g. v1.2-alpha) | alpha |
Tag matching v*-beta (e.g. v1.2-beta) | beta |
Tag matching v* (e.g. v1.2.0) | production |
Manual workflow_dispatch | User-selectable (internal / alpha / beta / production) |
To release to production:
git tag v1.2.0
git push origin v1.2.0
On every push to main, CI automatically:
versionCode from the version file for the detected framework[skip ci] (prevents re-triggering the workflow)mainVersion file by framework:
| Framework | Version file | Field |
|---|---|---|
| TWA | android-root-app/build.gradle | versionCode |
| React Native | android/app/build.gradle | versionCode |
| Flutter | pubspec.yaml | version: x.y.z+N (the +N build number) |
| Native | app/build.gradle | versionCode |
For tag-based builds (alpha / beta / production), auto-bump does not run — the tag represents a pinned commit. Increment the version manually before tagging.
The wizard sets these automatically via gh secret set:
| Secret | Description |
|---|---|
KEYSTORE_FILE | Base64-encoded upload keystore (.jks) |
KEYSTORE_PASSWORD | Keystore password |
KEY_ALIAS | Key alias (e.g. upload) |
KEY_PASSWORD | Key password (usually same as KEYSTORE_PASSWORD) |
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON | Full JSON content of the service account key |
Add to your build.gradle (see templates/gradle/signing.gradle):
android {
signingConfigs {
release {
storeFile file("keystore.jks")
storePassword System.getenv("KEYSTORE_PASSWORD")
keyAlias System.getenv("KEY_ALIAS")
keyPassword System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
}
}
}
Never set
org.gradle.java.homeingradle.properties— it breaks Linux CI runners.
The CI workflow creates android/key.properties at build time (from secrets) and cleans it up after. Your android/app/build.gradle should read from it:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
github-play-publisher → Done (no roles needed)Google Cloud Console → APIs & Services → search Google Play Android Developer API → Enable
github-play-publisher@YOUR-PROJECT.iam.gserviceaccount.comGoogle Play requires at least one manually uploaded AAB before the API can publish. If this is a brand-new app, upload the first build from your local machine before running the CI pipeline.
| Error | Cause | Fix |
|---|---|---|
Java home supplied is invalid | org.gradle.java.home hardcoded in gradle.properties | Remove that line |
signed with the wrong key | Keystore in secret doesn't match Play's registered upload key | Update KEYSTORE_FILE secret |
The caller does not have permission | Service account missing permissions or API not enabled | Re-check Manual Steps 2 and 3 |
Upload failed — wrong versionCode | versionCode not incremented (tag-based build) | Increment versionCode manually before tagging |
shallow update not allowed | Shallow git checkout when pushing version bump | Workflow uses fetch-depth: 0 — verify the checkout step |
| Workflow not triggering on tag | Tag not pushed to remote | Run git push origin TAG_NAME |
gh: command not found | gh CLI not installed | Install from https://cli.github.com |
keytool not found | JDK not installed or not on PATH | Set JAVA_HOME or install JDK 17 |
If the app uses Play App Signing (recommended):
npx android-cicd and choose "I already have a keystore: No"keytool -export -rfc -keystore upload.jks -alias ALIAS -storepass PASSWORD -file cert.pem
cert.pemKEYSTORE_FILE secret with the new keystore base64Before pushing a tag for alpha / beta / production:
TWA / Native / React Native — edit build.gradle:
versionCode 8 // increment
versionName "1.2.0"
Flutter — edit pubspec.yaml:
version: 1.2.0+8
Then tag and push:
git add .
git commit -m "chore: bump version to 1.2.0"
git tag v1.2.0
git push origin main --tags