Agent Skills: Skill: android-cicd

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, GitHub Secrets, and a multi-stage workflow (internal/alpha/beta/production) with auto-bump versionCode.

UncategorizedID: davila7/claude-code-templates/android-cicd

Install this agent skill to your local

pnpm dlx add-skill https://github.com/davila7/claude-code-templates/tree/HEAD/cli-tool/components/skills/development/android-cicd

Skill Files

Browse the full folder contents for android-cicd.

Download Skill

Loading file tree…

cli-tool/components/skills/development/android-cicd/SKILL.md

Skill Metadata

Name
android-cicd
Description
"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, GitHub Secrets, and a multi-stage workflow (internal/alpha/beta/production) with auto-bump versionCode."

Skill: android-cicd

Purpose

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.

When to Use

  • The project has an Android app tracked in a GitHub repository
  • No CI/CD pipeline exists yet for the Android build
  • Goal: automate publishing to Google Play on every push to main and on version tags
  • User wants to avoid manual versionCode bumping

Quick Start

Run 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.


Prerequisites

Before running the wizard, ensure:

  • Node.js ≥ 18
  • JDK 17 installed with keytool accessible (JAVA_HOME set, or installed via Eclipse Adoptium / Android Studio)
  • gh CLI installed and authenticated (gh auth login)
  • App already created in Google Play Console — at least one manual AAB/APK upload done (required before the API can publish)
  • App enrolled in Play App Signing (Google manages the signing key; you manage the upload key)
  • Google Play Android Developer API enabled in Google Cloud Console
  • Service account JSON key downloaded (see Manual Steps below)

Framework Detection

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.


Multi-Stage Pipeline

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

Auto-Bump versionCode

On every push to main, CI automatically:

  1. Reads the current versionCode from the version file for the detected framework
  2. Increments it by 1
  3. Commits the change with [skip ci] (prevents re-triggering the workflow)
  4. Pushes the commit back to main

Version 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.


Required GitHub Secrets

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 |


Signing Configuration

TWA / Native Android

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.home in gradle.properties — it breaks Linux CI runners.

Flutter

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
        }
    }
}

Manual Steps (Cannot Be Automated)

1. Create the service account

  1. Google Cloud Console → your project → IAM & AdminService Accounts
  2. Create service account → name: github-play-publisherDone (no roles needed)
  3. Click the service account → Keys tab → Add keyCreate new keyJSON → download

2. Enable the Play API

Google Cloud Console → APIs & Services → search Google Play Android Developer APIEnable

3. Invite the service account in Play Console

  1. Play Console → Users and permissionsInvite new user
  2. Email: github-play-publisher@YOUR-PROJECT.iam.gserviceaccount.com
  3. Account-level permissions:
    • ✅ Release apps to testing tracks
    • ✅ Manage testing tracks and edit testers
  4. Apply

4. First manual upload

Google 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.


Troubleshooting

| 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 |


Recovering a Lost Upload Keystore

If the app uses Play App Signing (recommended):

  1. Generate a new keystore: npx android-cicd and choose "I already have a keystore: No"
  2. Export the PEM certificate:
    keytool -export -rfc -keystore upload.jks -alias ALIAS -storepass PASSWORD -file cert.pem
    
  3. Play Console → app → App integrityApp signingRequest upload key reset
  4. Select "I forgot my password" → upload cert.pem
  5. Wait 1–2 business days for Google approval
  6. Update the KEYSTORE_FILE secret with the new keystore base64

Manually Bumping the Version (Tag Releases)

Before 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