feat(release): add keystore setup + Play Store deployment guide
- Generated PKCS12 keystore (production-release.jks, alias: upload) - Updated GitHub secrets: KEYSTORE_BASE64/PASSWORD/KEY_ALIAS/KEY_PASSWORD - Added keystores/ to .gitignore - Added distribution/PLAY_CONSOLE_SETUP.md: step-by-step guide to create GCP service account, link to Play Console, first manual upload, and configure PLAY_STORE_SERVICE_ACCOUNT_JSON secret - Installed android-playstore-setup skills (sub-skills) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
554
.agents/skills/android-playstore-setup/SKILL.md
Normal file
554
.agents/skills/android-playstore-setup/SKILL.md
Normal file
@@ -0,0 +1,554 @@
|
||||
---
|
||||
name: android-playstore-setup
|
||||
description: Complete Play Store setup - orchestrates scanning, privacy policy, version management, Fastlane, and workflows (Internal track only)
|
||||
category: android
|
||||
version: 4.0.0
|
||||
---
|
||||
|
||||
# Android Play Store Setup
|
||||
|
||||
This skill orchestrates complete Google Play Store deployment setup with automated publishing to the **internal testing track** using **Fastlane**.
|
||||
|
||||
## What This Does
|
||||
|
||||
**Scope:** Internal track deployment only (simplified for quick testing)
|
||||
|
||||
Sets up everything needed for automated Play Store deployment using **Fastlane**:
|
||||
1. **Scan Project** - Analyze project and generate setup checklist
|
||||
2. **Fastlane Setup** - Configure Fastlane with supply and screengrab
|
||||
3. **App Icon** - Generate and place icon assets
|
||||
4. **Screenshots** - Automated screenshot capture
|
||||
5. **Store Listing** - Feature graphic and metadata
|
||||
6. **Privacy Policy** - Generate privacy policy for GitHub Pages
|
||||
7. **Version Management** - Setup Git tag-based versioning
|
||||
8. **Signing Configuration** - Configure release signing
|
||||
9. **Service Account** - Play Store API access
|
||||
10. **GitHub Actions** - CI/CD workflows with Fastlane
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Google Play Developer account ($25 one-time)
|
||||
- Google Cloud Platform account (free)
|
||||
- Admin access to Play Console
|
||||
- Package name reserved in Play Console
|
||||
|
||||
## Workflow Overview
|
||||
|
||||
```
|
||||
1. Scan → 2. Review → 3. Setup → 4. Deploy
|
||||
↓ ↓ ↓ ↓
|
||||
📋 ✅ 🔧 🚀
|
||||
```
|
||||
|
||||
## Process
|
||||
|
||||
### Step 1: Scan Project (Analysis Only)
|
||||
|
||||
Run `/devtools:android-playstore-scan`
|
||||
|
||||
**What it does:**
|
||||
- Scans AndroidManifest.xml and build.gradle
|
||||
- Detects Health Connect, ads, analytics
|
||||
- Checks for privacy policy
|
||||
- Generates `PLAY_CONSOLE_SETUP.md` with pre-filled answers
|
||||
|
||||
**Output:** `PLAY_CONSOLE_SETUP.md`
|
||||
|
||||
**Action:** Review the generated file and address any warnings
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Generate Privacy Policy (If Needed)
|
||||
|
||||
If `PLAY_CONSOLE_SETUP.md` shows privacy policy is missing:
|
||||
|
||||
Run `/devtools:privacy-policy-generate`
|
||||
|
||||
**What it does:**
|
||||
- Scans project for app info
|
||||
- Detects Health Connect and third-party SDKs
|
||||
- Prompts for developer info
|
||||
- Generates `docs/privacy-policy.md`
|
||||
- Creates GitHub Pages setup guide
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
test -f docs/privacy-policy.md && echo "✓ Privacy policy created"
|
||||
```
|
||||
|
||||
**Next:** Enable GitHub Pages (Settings → Pages → Source: docs/)
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Setup Version Management
|
||||
|
||||
Run `/devtools:version-management` with platform=gradle
|
||||
|
||||
**What it does:**
|
||||
- Creates `scripts/version-manager.sh` (core)
|
||||
- Creates `scripts/gradle-version.sh` (Android adapter)
|
||||
- Creates `version.properties` with initial version
|
||||
- Updates `app/build.gradle.kts` to read from version.properties
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
./scripts/version-manager.sh latest
|
||||
./scripts/gradle-version.sh generate patch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Generate Keystores
|
||||
|
||||
Run `/devtools:android-keystore-generation`
|
||||
|
||||
**What it does:**
|
||||
- Generates production-release.jks (for CI/CD)
|
||||
- Generates local-dev-release.jks (for local testing)
|
||||
- Creates KEYSTORE_INFO.txt with credentials
|
||||
- Updates .gitignore
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
ls keystores/*.jks
|
||||
cat keystores/KEYSTORE_INFO.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Configure Signing
|
||||
|
||||
Run `/devtools:android-signing-config`
|
||||
|
||||
**What it does:**
|
||||
- Adds signing configuration to app/build.gradle.kts
|
||||
- Configures dual-source credentials (env vars + gradle.properties)
|
||||
- Updates local ~/.gradle/gradle.properties
|
||||
- Adds validation for release builds
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
./gradlew assembleRelease
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 6: Configure ProGuard (If Not Already Setup)
|
||||
|
||||
Run `/devtools:android-proguard-setup`
|
||||
|
||||
**What it does:**
|
||||
- Creates app/proguard-rules.pro with safe defaults
|
||||
- Enables minification and resource shrinking
|
||||
- Adds library-specific rules if needed
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
grep "isMinifyEnabled = true" app/build.gradle.kts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 7: Setup Fastlane
|
||||
|
||||
Run `/devtools:android-fastlane-setup`
|
||||
|
||||
**What it does:**
|
||||
- Creates Gemfile with fastlane and screengrab
|
||||
- Creates fastlane/Appfile with package name
|
||||
- Creates fastlane/Fastfile with deployment lanes
|
||||
- Creates fastlane/Screengrabfile for screenshot automation
|
||||
- Creates fastlane/metadata/ directory structure
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
bundle exec fastlane --version
|
||||
bundle exec fastlane lanes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 7a: Generate App Icon
|
||||
|
||||
Run `/devtools:android-app-icon`
|
||||
|
||||
**What it does:**
|
||||
- Analyzes project for app name and colors
|
||||
- Generates docs/APP_ICON_SETUP.md with IconKitchen instructions
|
||||
- Provides helper script to process IconKitchen downloads
|
||||
- Copies mipmap resources and Play Store icon
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
test -f fastlane/metadata/android/en-US/images/icon.png
|
||||
file fastlane/metadata/android/en-US/images/icon.png | grep "512 x 512"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 7b: Setup Screenshot Automation
|
||||
|
||||
Run `/devtools:android-screenshot-automation`
|
||||
|
||||
**What it does:**
|
||||
- Adds screengrab dependency to app/build.gradle.kts
|
||||
- Creates debug manifest with required permissions
|
||||
- Creates ScreenshotTest.kt for automated capture
|
||||
- Creates DemoModeRule.kt for clean status bar
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
bundle exec fastlane screenshots
|
||||
ls fastlane/metadata/android/en-US/images/phoneScreenshots/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 7c: Create Store Listing Assets
|
||||
|
||||
Run `/devtools:android-store-listing`
|
||||
|
||||
**What it does:**
|
||||
- Generates docs/STORE_LISTING_GUIDE.md
|
||||
- Creates metadata templates (title, description, etc.)
|
||||
- Provides feature graphic generation script
|
||||
- Guides user through asset creation
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
test -f fastlane/metadata/android/en-US/images/featureGraphic.png
|
||||
wc -c fastlane/metadata/android/en-US/*.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 8: Create Deployment Workflows
|
||||
|
||||
Run `/devtools:android-workflow-internal`
|
||||
|
||||
**What it does:**
|
||||
- Creates .github/workflows/build.yml (CI only - runs on push/PR)
|
||||
- Creates .github/workflows/release-internal.yml (Manual releases with Fastlane)
|
||||
- Adds Ruby setup and bundle caching
|
||||
- Uses `bundle exec fastlane deploy_internal` for deployment
|
||||
- All actions pinned to SHAs
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
test -f .github/workflows/build.yml
|
||||
test -f .github/workflows/release-internal.yml
|
||||
grep "bundle exec fastlane" .github/workflows/release-internal.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 9: Service Account Setup
|
||||
|
||||
Run `/devtools:android-service-account-guide`
|
||||
|
||||
**What it does:**
|
||||
- Provides step-by-step guide for Google Cloud setup
|
||||
- Documents service account creation
|
||||
- Creates Play Console setup documentation
|
||||
|
||||
**Manual steps required:**
|
||||
1. Create service account in Google Cloud
|
||||
2. Download JSON key
|
||||
3. Grant permissions in Play Console
|
||||
4. Add JSON to GitHub Secrets as `SERVICE_ACCOUNT_JSON_PLAINTEXT`
|
||||
|
||||
---
|
||||
|
||||
### Step 10: Add Keystore to GitHub Secrets
|
||||
|
||||
From `keystores/KEYSTORE_INFO.txt`, add these secrets to GitHub:
|
||||
|
||||
```bash
|
||||
# From KEYSTORE_INFO.txt, copy the base64 encoded keystore:
|
||||
SIGNING_KEY_STORE_BASE64: <base64_string>
|
||||
SIGNING_KEY_ALIAS: upload
|
||||
SIGNING_STORE_PASSWORD: <password>
|
||||
SIGNING_KEY_PASSWORD: <password>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Step 11: Validate API Connection
|
||||
|
||||
Run `/devtools:android-playstore-api-validation`
|
||||
|
||||
**What it does:**
|
||||
- Creates scripts/validate-playstore.py
|
||||
- Tests Play Store API connection
|
||||
- Verifies service account permissions
|
||||
|
||||
**Verify:**
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install google-auth google-api-python-client
|
||||
python3 scripts/validate-playstore.py /path/to/service-account.json com.example.app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 12: First Manual Upload (CRITICAL)
|
||||
|
||||
⚠️ **Before GitHub Actions can deploy, you MUST manually upload your first release.**
|
||||
|
||||
### Why?
|
||||
|
||||
1. Google Play requires manual first upload to complete store listing
|
||||
2. Your production keystore becomes the **upload key**
|
||||
3. Play App Signing is automatically enabled
|
||||
|
||||
### Steps:
|
||||
|
||||
```bash
|
||||
# 1. Build release bundle locally
|
||||
./gradlew bundleRelease
|
||||
|
||||
# 2. Verify it's signed
|
||||
jarsigner -verify -verbose app/build/outputs/bundle/release/app-release.aab
|
||||
|
||||
# 3. Manual upload via Play Console
|
||||
```
|
||||
|
||||
**In Play Console:**
|
||||
1. Go to **Release** → **Internal testing**
|
||||
2. Click **Create new release**
|
||||
3. Upload `app-release.aab`
|
||||
4. Complete store listing (title, description, icon)
|
||||
5. Complete app content declarations
|
||||
6. Publish to internal testing
|
||||
|
||||
**IMPORTANT:** The keystore used for this first upload must be the same one configured in GitHub Secrets!
|
||||
|
||||
---
|
||||
|
||||
## Step 13: Test Fastlane Deployment
|
||||
|
||||
After first manual upload is complete:
|
||||
|
||||
```bash
|
||||
# Push to main branch to trigger deployment
|
||||
git add .
|
||||
git commit -m "Setup Play Store deployment"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. GitHub Actions workflow triggers
|
||||
2. Runs unit tests
|
||||
3. Builds release bundle
|
||||
4. Deploys to internal testing track
|
||||
|
||||
**Monitor:** Go to repository → Actions tab
|
||||
|
||||
---
|
||||
|
||||
## Understanding Play App Signing
|
||||
|
||||
### Two Keys System
|
||||
|
||||
| Key Type | Purpose | Holder | Can Reset? |
|
||||
|----------|---------|--------|------------|
|
||||
| **App Signing Key** | Signs APKs for users | Google | No (permanent) |
|
||||
| **Upload Key** | Authenticates your uploads | You | Yes (via Play Console) |
|
||||
|
||||
### Automatic Setup
|
||||
|
||||
For apps created after August 2021, Play App Signing is **automatic**:
|
||||
1. First upload: Google generates app signing key
|
||||
2. Your production keystore = upload key
|
||||
3. Google re-signs with app signing key before distribution
|
||||
|
||||
**No action needed** - it just works!
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Checklist
|
||||
|
||||
```bash
|
||||
# Project files
|
||||
✓ Fastlane configured (Gemfile, Fastfile, Appfile)
|
||||
✓ Version management scripts in scripts/
|
||||
✓ Keystores in keystores/ (gitignored)
|
||||
✓ Privacy policy in docs/privacy-policy.md
|
||||
✓ Metadata in fastlane/metadata/android/en-US/
|
||||
✓ CI workflow in .github/workflows/build.yml
|
||||
✓ Release workflow in .github/workflows/release-internal.yml
|
||||
|
||||
# Build verification
|
||||
✓ ./gradlew assembleRelease succeeds
|
||||
✓ Unit tests pass
|
||||
✓ ProGuard enabled
|
||||
|
||||
# GitHub Secrets configured
|
||||
✓ SERVICE_ACCOUNT_JSON_PLAINTEXT
|
||||
✓ SIGNING_KEY_STORE_BASE64
|
||||
✓ SIGNING_KEY_ALIAS
|
||||
✓ SIGNING_STORE_PASSWORD
|
||||
✓ SIGNING_KEY_PASSWORD
|
||||
|
||||
# Play Console
|
||||
✓ First manual upload completed
|
||||
✓ Internal testing track active
|
||||
✓ Service account has permissions
|
||||
|
||||
# API validation
|
||||
✓ scripts/validate-playstore.py passes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### For Beta/Production Deployment
|
||||
|
||||
Once internal testing is working:
|
||||
|
||||
```bash
|
||||
# Add beta track
|
||||
/devtools:android-workflow-beta
|
||||
|
||||
# Add production track
|
||||
/devtools:android-workflow-production
|
||||
```
|
||||
|
||||
### Track Information
|
||||
|
||||
| Track | Audience | Review Time | Use Case |
|
||||
|-------|----------|-------------|----------|
|
||||
| **Internal** | Up to 100 testers | Instant | Quick testing, no review |
|
||||
| **Closed (Alpha)** | Invited testers | < 24h | Beta testing |
|
||||
| **Open (Beta)** | Anyone can join | < 24h | Public beta |
|
||||
| **Production** | All users | 1-7 days | Full release |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Package not found" in API validation
|
||||
- Ensure app exists in Play Console
|
||||
- Verify package name matches exactly
|
||||
- Complete first manual upload
|
||||
|
||||
### "Upload key mismatch"
|
||||
- Your first upload keystore ≠ GitHub Secrets keystore
|
||||
- Fix: Use Play Console → App signing → Request upload key reset
|
||||
- Re-upload with correct keystore
|
||||
|
||||
### "Permission denied" for service account
|
||||
- Grant "Release to production" permission in Play Console
|
||||
- Wait 5-10 minutes for permissions to propagate
|
||||
|
||||
### GitHub Actions fails to deploy
|
||||
- Verify all GitHub Secrets are set correctly
|
||||
- Check workflow logs for specific error
|
||||
- Ensure first manual upload was completed
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
You've successfully setup:
|
||||
- ✅ Privacy policy (GitHub Pages ready)
|
||||
- ✅ Version management (Git tag-based)
|
||||
- ✅ Release signing (production + local dev)
|
||||
- ✅ Fastlane deployment automation
|
||||
- ✅ GitHub Actions CI/CD (internal track)
|
||||
- ✅ Play Store API connection
|
||||
|
||||
**Your app is now ready for continuous integration and deployment!**
|
||||
|
||||
Every push to main or PR → Automatic build & test (CI) ✅
|
||||
Manual workflow trigger → Version management + deployment to internal track 🚀
|
||||
|
||||
**All checks must pass** before marking this skill as complete.
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
Do NOT mark complete unless ALL are verified:
|
||||
|
||||
✅ **Service Account Setup**
|
||||
- [ ] Service account created in Google Cloud
|
||||
- [ ] JSON key downloaded and stored securely
|
||||
- [ ] Play Developer API enabled
|
||||
- [ ] Service account linked to Play Console
|
||||
- [ ] "Release" permission granted
|
||||
|
||||
✅ **Store Metadata Structure**
|
||||
- [ ] fastlane/metadata/android/en-US/ directory exists
|
||||
- [ ] At least en-US locale configured
|
||||
- [ ] Metadata files created (title, description, changelogs)
|
||||
- [ ] docs/PLAY_STORE_TRACKS.md documentation created
|
||||
|
||||
✅ **API Validation**
|
||||
- [ ] scripts/validate-playstore.py exists
|
||||
- [ ] Validation script runs successfully
|
||||
- [ ] API connection confirmed
|
||||
- [ ] Package access confirmed
|
||||
|
||||
✅ **Documentation**
|
||||
- [ ] PLAY_CONSOLE_SETUP.md exists (project root)
|
||||
- [ ] GITHUB_SECRETS.md exists (if needed)
|
||||
|
||||
## Summary Report
|
||||
|
||||
After completion, provide this summary:
|
||||
|
||||
```
|
||||
✅ Android Play Store Setup Complete!
|
||||
|
||||
🔐 Service Account:
|
||||
✓ Created in Google Cloud
|
||||
✓ JSON key downloaded
|
||||
✓ Linked to Play Console
|
||||
✓ Permissions granted
|
||||
|
||||
📝 Store Metadata:
|
||||
✓ Structure created: fastlane/metadata/android/en-US/
|
||||
✓ Locales configured
|
||||
✓ Templates ready
|
||||
|
||||
✅ API Validation:
|
||||
✓ Validation script created
|
||||
✓ API connection tested
|
||||
✓ Package access confirmed
|
||||
|
||||
📋 Next Steps:
|
||||
|
||||
For GitHub:
|
||||
1. Add secrets (see GITHUB_SECRETS.md if it exists)
|
||||
2. Create "production" environment with reviewers
|
||||
|
||||
For Deployment:
|
||||
1. Run: /devtools:android-playstore-publish
|
||||
2. Generate deployment workflows
|
||||
|
||||
⚠️ CRITICAL REMINDERS:
|
||||
- NEVER commit service account JSON to git
|
||||
- Store JSON key in password manager
|
||||
- Add all 5 secrets to GitHub before deploying
|
||||
- Wait 5-10 minutes after granting permissions
|
||||
```
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
This skill is prerequisite for:
|
||||
- `android-playstore-publishing` - Uses service account for deployment
|
||||
- `android-playstore-pipeline` - Complete pipeline setup
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If any skill fails:
|
||||
1. Fix the specific issue in that skill
|
||||
2. Re-run that skill until it completes
|
||||
3. Continue with remaining skills
|
||||
4. Run final verification
|
||||
|
||||
Common issues:
|
||||
- **Service account not found** → Check Google Cloud project
|
||||
- **Permissions denied** → Grant "Release" permission
|
||||
- **API validation fails** → Wait 5-10 minutes for propagation
|
||||
@@ -0,0 +1,382 @@
|
||||
# GitHub Secrets Setup for Play Store Deployment
|
||||
|
||||
## Overview
|
||||
|
||||
GitHub Actions requires several secrets to deploy your app to the Play Store. This guide explains how to set up each secret.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Admin access to GitHub repository
|
||||
- Service account JSON file from android-playstore-setup
|
||||
- Production keystore from android-release-build-setup
|
||||
|
||||
## Required Secrets
|
||||
|
||||
Navigate to: **Your Repository → Settings → Secrets and variables → Actions → New repository secret**
|
||||
|
||||
---
|
||||
|
||||
### 1. SERVICE_ACCOUNT_JSON_PLAINTEXT
|
||||
|
||||
**What it is:** Complete plaintext contents of the Google Cloud service account JSON file (not base64 encoded)
|
||||
|
||||
**How to get the value:**
|
||||
|
||||
1. Locate the JSON file downloaded during android-playstore-setup
|
||||
- Filename: `service-account.json` (or similar)
|
||||
- Location: Where you saved it securely
|
||||
|
||||
2. Open the file in a text editor
|
||||
|
||||
3. Copy the **ENTIRE** contents
|
||||
- From the first `{` to the last `}`
|
||||
- Include all whitespace and newlines
|
||||
- Should be ~2,400 characters
|
||||
|
||||
4. In GitHub:
|
||||
- Name: `SERVICE_ACCOUNT_JSON_PLAINTEXT`
|
||||
- Value: Paste the copied JSON (plaintext, not base64 encoded)
|
||||
|
||||
**Example format (DO NOT use this, use your actual file):**
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "your-gcp-project",
|
||||
"private_key_id": "abc123...",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBg...\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "playstore-deploy@your-project.iam.gserviceaccount.com",
|
||||
"client_id": "123456789...",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/..."
|
||||
}
|
||||
```
|
||||
|
||||
**Verification:**
|
||||
- ✅ Starts with `{` and ends with `}`
|
||||
- ✅ Contains `"type": "service_account"`
|
||||
- ✅ Contains `"client_email"` with @...iam.gserviceaccount.com
|
||||
- ✅ Contains `"private_key"` section with BEGIN/END markers
|
||||
- ✅ Valid JSON (no syntax errors)
|
||||
|
||||
---
|
||||
|
||||
### 2. SIGNING_KEY_STORE_BASE64
|
||||
|
||||
**What it is:** Your production keystore file encoded as base64
|
||||
|
||||
**How to get the value:**
|
||||
|
||||
**On Linux/macOS:**
|
||||
```bash
|
||||
base64 -w 0 keystores/production-release.jks
|
||||
```
|
||||
|
||||
**On macOS (alternative):**
|
||||
```bash
|
||||
base64 -i keystores/production-release.jks | tr -d '\n'
|
||||
```
|
||||
|
||||
**On Windows (PowerShell):**
|
||||
```powershell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes('keystores\production-release.jks'))
|
||||
```
|
||||
|
||||
**On Windows (Git Bash):**
|
||||
```bash
|
||||
base64 -w 0 keystores/production-release.jks
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to your project directory
|
||||
2. Run the appropriate command above
|
||||
3. Copy the output (one long string, no line breaks)
|
||||
4. In GitHub:
|
||||
- Name: `SIGNING_KEY_STORE_BASE64`
|
||||
- Value: Paste the base64 string
|
||||
|
||||
**Verification:**
|
||||
- ✅ String is very long (~5,000+ characters)
|
||||
- ✅ Contains only alphanumeric characters, +, /, and = (padding)
|
||||
- ✅ No line breaks or spaces
|
||||
|
||||
**Common mistakes:**
|
||||
- ❌ Including `-----BEGIN CERTIFICATE-----` headers (wrong encoding method)
|
||||
- ❌ Line breaks in the base64 string
|
||||
- ❌ Encoding the wrong file (debug.keystore instead of production)
|
||||
|
||||
---
|
||||
|
||||
### 3. SIGNING_KEY_ALIAS
|
||||
|
||||
**What it is:** The alias used when creating your production keystore
|
||||
|
||||
**How to get the value:**
|
||||
|
||||
From `keystores/KEYSTORE_INFO.txt`:
|
||||
```
|
||||
Alias: upload
|
||||
```
|
||||
|
||||
Or list keystore contents:
|
||||
```bash
|
||||
keytool -list -v -keystore keystores/production-release.jks
|
||||
# Look for "Alias name:"
|
||||
```
|
||||
|
||||
**Typical values:**
|
||||
- `upload` (recommended by Google Play)
|
||||
- `release`
|
||||
- `production`
|
||||
- `key0` (default for some tools)
|
||||
|
||||
**Steps:**
|
||||
1. Check your keystore info file
|
||||
2. In GitHub:
|
||||
- Name: `SIGNING_KEY_ALIAS`
|
||||
- Value: The alias (e.g., `upload`)
|
||||
|
||||
**Verification:**
|
||||
- ✅ Matches the alias in your keystore
|
||||
- ✅ Case-sensitive (use exact match)
|
||||
|
||||
---
|
||||
|
||||
### 4. SIGNING_STORE_PASSWORD
|
||||
|
||||
**What it is:** Password for the keystore file itself
|
||||
|
||||
**How to get the value:**
|
||||
|
||||
From `keystores/KEYSTORE_INFO.txt`:
|
||||
```
|
||||
Store Password: your-store-password
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
1. Get password from secure location (password manager, keystore info file)
|
||||
2. In GitHub:
|
||||
- Name: `SIGNING_STORE_PASSWORD`
|
||||
- Value: The password (case-sensitive)
|
||||
|
||||
**Security notes:**
|
||||
- 🔒 Never commit this password to git
|
||||
- 🔒 Store in password manager
|
||||
- 🔒 Use strong password (16+ characters)
|
||||
|
||||
---
|
||||
|
||||
### 5. SIGNING_KEY_PASSWORD
|
||||
|
||||
**What it is:** Password for the specific key within the keystore
|
||||
|
||||
**How to get the value:**
|
||||
|
||||
From `keystores/KEYSTORE_INFO.txt`:
|
||||
```
|
||||
Key Password: your-key-password
|
||||
```
|
||||
|
||||
**Note:** Key password and store password are often the same, but can be different.
|
||||
|
||||
**Steps:**
|
||||
1. Get password from secure location
|
||||
2. In GitHub:
|
||||
- Name: `SIGNING_KEY_PASSWORD`
|
||||
- Value: The password (case-sensitive)
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After adding all secrets:
|
||||
|
||||
- [ ] All 5 secrets are listed in repository settings
|
||||
- [ ] No typos in secret names (they're case-sensitive!)
|
||||
- [ ] SERVICE_ACCOUNT_JSON_PLAINTEXT is valid JSON
|
||||
- [ ] SIGNING_KEY_STORE_BASE64 has no line breaks
|
||||
- [ ] Aliases and passwords match your keystore
|
||||
- [ ] Test deployment workflow to verify
|
||||
|
||||
## Testing Secrets
|
||||
|
||||
To verify secrets are correct without deploying:
|
||||
|
||||
1. Create a test GitHub Actions workflow:
|
||||
|
||||
```yaml
|
||||
name: Test Secrets
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test SERVICE_ACCOUNT_JSON_PLAINTEXT
|
||||
run: |
|
||||
echo "${{ secrets.SERVICE_ACCOUNT_JSON_PLAINTEXT }}" | jq -r '.client_email'
|
||||
# Should output: playstore-deploy@your-project.iam.gserviceaccount.com
|
||||
|
||||
- name: Test Keystore Decode
|
||||
run: |
|
||||
echo "${{ secrets.SIGNING_KEY_STORE_BASE64 }}" | base64 --decode > test.jks
|
||||
keytool -list -v -keystore test.jks \
|
||||
-storepass "${{ secrets.SIGNING_STORE_PASSWORD }}" \
|
||||
-alias "${{ secrets.SIGNING_KEY_ALIAS }}"
|
||||
# Should show keystore details
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: rm -f test.jks
|
||||
```
|
||||
|
||||
2. Run workflow manually
|
||||
3. Check logs for verification
|
||||
4. Delete test workflow after verification
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "Invalid service account JSON (SERVICE_ACCOUNT_JSON_PLAINTEXT)"
|
||||
|
||||
**Symptom:** Deployment fails with authentication error
|
||||
|
||||
**Causes:**
|
||||
- JSON is truncated (didn't copy all of it)
|
||||
- JSON has syntax errors
|
||||
- Wrong service account file
|
||||
|
||||
**Fix:**
|
||||
1. Re-copy the entire JSON file
|
||||
2. Validate JSON: https://jsonlint.com/
|
||||
3. Ensure file is from Google Cloud (has "type": "service_account")
|
||||
|
||||
---
|
||||
|
||||
### "Failed to decode keystore"
|
||||
|
||||
**Symptom:** Build fails when decoding keystore
|
||||
|
||||
**Causes:**
|
||||
- Base64 string has line breaks
|
||||
- Wrong file was encoded
|
||||
- Encoding method was incorrect
|
||||
|
||||
**Fix:**
|
||||
1. Re-encode using commands above
|
||||
2. Ensure output is single line
|
||||
3. Verify keystore file is correct one
|
||||
|
||||
---
|
||||
|
||||
### "Keystore password incorrect"
|
||||
|
||||
**Symptom:** Signing fails with password error
|
||||
|
||||
**Causes:**
|
||||
- Typo in password
|
||||
- Using wrong password (debug instead of release)
|
||||
- Password has special characters causing shell issues
|
||||
|
||||
**Fix:**
|
||||
1. Verify password from KEYSTORE_INFO.txt
|
||||
2. Test locally first: `keytool -list -keystore production-release.jks`
|
||||
3. If password has special characters, escape them
|
||||
|
||||
---
|
||||
|
||||
### "Alias not found"
|
||||
|
||||
**Symptom:** Cannot find key with specified alias
|
||||
|
||||
**Causes:**
|
||||
- Alias name typo
|
||||
- Wrong keystore file
|
||||
- Case sensitivity
|
||||
|
||||
**Fix:**
|
||||
1. List keystore contents: `keytool -list -v -keystore production-release.jks`
|
||||
2. Copy exact alias name (case-sensitive)
|
||||
3. Update GitHub secret with correct alias
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### 1. Least Privilege
|
||||
- Only grant secret access to necessary workflows
|
||||
- Use environment-specific secrets if needed
|
||||
- Regular audit of who has access
|
||||
|
||||
### 2. Rotation
|
||||
- Rotate service account keys annually
|
||||
- Update GitHub Secrets after rotation
|
||||
- Test deployment after rotation
|
||||
- Document rotation date
|
||||
|
||||
### 3. Monitoring
|
||||
- Enable GitHub Actions audit log
|
||||
- Monitor for unauthorized secret access
|
||||
- Review workflow run history
|
||||
- Check for failed authentication attempts
|
||||
|
||||
### 4. Backup
|
||||
- Keep service account JSON in password manager
|
||||
- Store keystore and passwords in company vault
|
||||
- Document secret values in secure location (not git!)
|
||||
- Have disaster recovery plan
|
||||
|
||||
### 5. Separation
|
||||
- Use different service accounts for dev/staging/prod
|
||||
- Consider separate repositories for different environments
|
||||
- Never share production secrets with development
|
||||
|
||||
---
|
||||
|
||||
## Updating Secrets
|
||||
|
||||
When you need to update a secret:
|
||||
|
||||
1. Navigate to: Repository → Settings → Secrets → Actions
|
||||
2. Find the secret to update
|
||||
3. Click "Update"
|
||||
4. Paste new value
|
||||
5. Click "Update secret"
|
||||
6. Test deployment to verify
|
||||
|
||||
**Note:** Updating a secret does NOT automatically re-run workflows. You need to trigger a new workflow run.
|
||||
|
||||
---
|
||||
|
||||
## Deleting Secrets
|
||||
|
||||
If you need to rotate or remove a secret:
|
||||
|
||||
1. **Before deleting:**
|
||||
- Ensure new secret is ready (if rotating)
|
||||
- Update workflows if secret name changes
|
||||
- Test with new secret
|
||||
|
||||
2. **Delete:**
|
||||
- Repository → Settings → Secrets → Actions
|
||||
- Click secret to delete
|
||||
- Click "Remove secret"
|
||||
- Confirm deletion
|
||||
|
||||
3. **After deleting:**
|
||||
- Add new secret immediately
|
||||
- Test deployment
|
||||
- Update documentation
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Encrypted Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)
|
||||
- [Google Cloud Service Accounts](https://cloud.google.com/iam/docs/service-accounts)
|
||||
- [Android App Signing](https://developer.android.com/studio/publish/app-signing)
|
||||
- [keytool Documentation](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html)
|
||||
@@ -0,0 +1,199 @@
|
||||
# Release Notes for Play Store
|
||||
|
||||
## Overview
|
||||
|
||||
This directory contains release notes (what's new) for different locales. These notes are displayed to users when they update your app. This structure is used by Fastlane for Play Store deployment.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Each locale has its own changelogs directory with a `default.txt` file:
|
||||
|
||||
```
|
||||
fastlane/metadata/android/
|
||||
├── en-US/
|
||||
│ └── changelogs/
|
||||
│ └── default.txt
|
||||
├── de-DE/
|
||||
│ └── changelogs/
|
||||
│ └── default.txt
|
||||
├── es-ES/
|
||||
│ └── changelogs/
|
||||
│ └── default.txt
|
||||
├── fr-FR/
|
||||
│ └── changelogs/
|
||||
│ └── default.txt
|
||||
└── ...
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
- **Filename:** `default.txt`
|
||||
- **Format:** Plain text (UTF-8 encoding)
|
||||
- **Max length:** 500 characters
|
||||
- **Line breaks:** Supported but count toward character limit
|
||||
|
||||
## Content Guidelines
|
||||
|
||||
### What to Include
|
||||
- New features and improvements
|
||||
- Bug fixes (if significant to users)
|
||||
- Performance enhancements
|
||||
- UI/UX changes
|
||||
- Security updates (if user-facing)
|
||||
|
||||
### What NOT to Include
|
||||
- Internal code changes
|
||||
- Developer-only features
|
||||
- "Bug fixes and improvements" (too vague)
|
||||
- Marketing speak or promotional content
|
||||
- Future features (only released features)
|
||||
|
||||
### Writing Style
|
||||
- ✅ Use bullet points for clarity
|
||||
- ✅ Start with most important changes
|
||||
- ✅ Use active voice ("Added", "Fixed", "Improved")
|
||||
- ✅ Be specific and concise
|
||||
- ✅ Use user-friendly language
|
||||
- ❌ Avoid technical jargon
|
||||
- ❌ Don't use ALL CAPS
|
||||
- ❌ Don't use excessive punctuation!!!
|
||||
|
||||
## Examples
|
||||
|
||||
### Good Example (186 characters)
|
||||
```
|
||||
- New: Dark mode support throughout the app
|
||||
- Improved: 50% faster app startup time
|
||||
- Fixed: Crash when uploading large photos
|
||||
- Updated: Refreshed settings screen design
|
||||
```
|
||||
|
||||
### Bad Example (Too vague)
|
||||
```
|
||||
- Bug fixes and performance improvements
|
||||
- Various updates
|
||||
- Made the app better
|
||||
```
|
||||
|
||||
### Bad Example (Too technical)
|
||||
```
|
||||
- Refactored UserRepository to use Kotlin Flow
|
||||
- Migrated from RxJava to Coroutines
|
||||
- Updated Gradle dependencies to latest versions
|
||||
```
|
||||
|
||||
## Supported Locales
|
||||
|
||||
Common Play Store locales:
|
||||
|
||||
| Locale | Language | Region |
|
||||
|--------|----------|--------|
|
||||
| en-US | English | United States |
|
||||
| en-GB | English | United Kingdom |
|
||||
| de-DE | German | Germany |
|
||||
| es-ES | Spanish | Spain |
|
||||
| fr-FR | French | France |
|
||||
| it-IT | Italian | Italy |
|
||||
| ja-JP | Japanese | Japan |
|
||||
| ko-KR | Korean | South Korea |
|
||||
| pt-BR | Portuguese | Brazil |
|
||||
| ru-RU | Russian | Russia |
|
||||
| zh-CN | Chinese | Simplified |
|
||||
| zh-TW | Chinese | Traditional |
|
||||
| ar | Arabic | - |
|
||||
| hi-IN | Hindi | India |
|
||||
| id | Indonesian | - |
|
||||
|
||||
For complete list, see: https://support.google.com/googleplay/android-developer/answer/9844778
|
||||
|
||||
## Updating Release Notes
|
||||
|
||||
### For Each Release
|
||||
|
||||
1. Create release notes in primary language (usually en-US)
|
||||
2. Keep under 500 characters
|
||||
3. Translate for other supported locales
|
||||
4. Test that notes display correctly in Play Console
|
||||
5. Commit changes before building release
|
||||
|
||||
### Translation Tips
|
||||
|
||||
- Use professional translation service for accuracy
|
||||
- Native speakers review for cultural appropriateness
|
||||
- Keep formatting consistent across locales
|
||||
- Test character limits in each language (some translate longer)
|
||||
|
||||
### Automation
|
||||
|
||||
Release notes are automatically included by Fastlane during deployment:
|
||||
|
||||
```ruby
|
||||
# In fastlane/Fastfile
|
||||
lane :deploy_internal do
|
||||
# Fastlane automatically looks for changelogs in fastlane/metadata/android/{locale}/changelogs/
|
||||
upload_to_play_store(
|
||||
track: "internal",
|
||||
aab: "app/build/outputs/bundle/release/app-release.aab"
|
||||
)
|
||||
end
|
||||
```
|
||||
|
||||
Deployment command:
|
||||
```bash
|
||||
bundle exec fastlane deploy_internal
|
||||
```
|
||||
|
||||
Fastlane automatically includes release notes from `fastlane/metadata/android/{locale}/changelogs/default.txt` when deploying.
|
||||
|
||||
## Character Count Checker
|
||||
|
||||
To check character count:
|
||||
|
||||
```bash
|
||||
# Count characters in en-US release notes
|
||||
wc -m fastlane/metadata/android/en-US/changelogs/default.txt
|
||||
```
|
||||
|
||||
Or use online tool: https://www.charactercountonline.com/
|
||||
|
||||
## Testing
|
||||
|
||||
Before deploying, verify:
|
||||
|
||||
1. File exists for each supported locale
|
||||
2. Files are plain text (UTF-8)
|
||||
3. Character count < 500 for each file
|
||||
4. Content is user-friendly and clear
|
||||
5. No typos or grammatical errors
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Release notes not showing in Play Console"**
|
||||
- Check file name is exactly `default.txt`
|
||||
- Verify UTF-8 encoding
|
||||
- Ensure file is not empty
|
||||
- Check locale code matches Play Console format
|
||||
- Verify file is in correct location: `fastlane/metadata/android/{locale}/changelogs/default.txt`
|
||||
|
||||
**"Character limit exceeded"**
|
||||
- Remove unnecessary words
|
||||
- Use abbreviations carefully
|
||||
- Split across multiple short bullets
|
||||
- Remove marketing fluff
|
||||
|
||||
**"Notes show differently on device"**
|
||||
- Play Store may truncate if too long
|
||||
- Test on actual device
|
||||
- Keep most important info at the top
|
||||
|
||||
## Version-Specific Notes
|
||||
|
||||
Fastlane uses the release notes from `fastlane/metadata/android/{locale}/changelogs/` for each deployment. Simply update the `default.txt` files before building and deploying a new version.
|
||||
|
||||
For tracking historical release notes, consider maintaining them in a separate `CHANGELOG.md` file or using git tags.
|
||||
|
||||
## References
|
||||
|
||||
- [Release Notes Guidelines](https://support.google.com/googleplay/android-developer/answer/7159011)
|
||||
- [Localization Best Practices](https://developer.android.com/distribute/best-practices/launch/localization-checklist)
|
||||
- [Play Console Help](https://support.google.com/googleplay/android-developer)
|
||||
368
.agents/skills/android-playstore-setup/templates/TRACKS.md
Normal file
368
.agents/skills/android-playstore-setup/templates/TRACKS.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# Play Store Release Tracks
|
||||
|
||||
## Overview
|
||||
|
||||
Google Play offers multiple release tracks for different stages of your app's release process. Each track serves a specific purpose and audience.
|
||||
|
||||
## Track Types
|
||||
|
||||
### 1. Internal Testing Track
|
||||
|
||||
**Purpose:** Rapid testing with your team
|
||||
|
||||
**Characteristics:**
|
||||
- **Audience:** Up to 100 internal testers
|
||||
- **Review time:** None (instant availability)
|
||||
- **Rollout:** Immediate (100% to all testers)
|
||||
- **Visibility:** Only invited testers can see and install
|
||||
- **Version requirements:** Can have lower version code than production
|
||||
|
||||
**Best for:**
|
||||
- Daily/continuous deployment from CI/CD
|
||||
- QA team testing
|
||||
- Dogfooding (internal employee usage)
|
||||
- Quick iteration and bug fixes
|
||||
|
||||
**Setup:**
|
||||
1. Play Console → Release → Testing → Internal testing
|
||||
2. Create email list of testers
|
||||
3. Share opt-in URL with team
|
||||
4. Testers opt-in and can immediately download
|
||||
|
||||
**Limitations:**
|
||||
- Maximum 100 testers
|
||||
- Updates are immediate (can't schedule)
|
||||
- No staged rollout option
|
||||
|
||||
---
|
||||
|
||||
### 2. Closed Testing (Alpha/Beta Tracks)
|
||||
|
||||
**Purpose:** Private testing with selected external testers
|
||||
|
||||
**Characteristics:**
|
||||
- **Audience:** Unlimited testers via email lists or Google Groups
|
||||
- **Review time:** Minimal (typically < 24 hours)
|
||||
- **Rollout:** Immediate to all testers or staged
|
||||
- **Visibility:** Only invited testers
|
||||
- **Version requirements:** Can have lower version code than production
|
||||
|
||||
**Best for:**
|
||||
- Beta tester program
|
||||
- Customer advisory board testing
|
||||
- Partner/client testing
|
||||
- Pre-release validation with real users
|
||||
|
||||
**Setup:**
|
||||
1. Play Console → Release → Testing → Closed testing
|
||||
2. Create track (e.g., "alpha" or "beta")
|
||||
3. Add testers:
|
||||
- Email list (manual CSV upload)
|
||||
- Google Group (automatic membership sync)
|
||||
4. Share opt-in URL with testers
|
||||
|
||||
**Multiple closed tracks:**
|
||||
You can create multiple closed tracks for different purposes:
|
||||
- alpha: Early unstable builds
|
||||
- beta: Stable pre-release builds
|
||||
- partners: Partner testing
|
||||
- qa-external: External QA testing
|
||||
|
||||
---
|
||||
|
||||
### 3. Open Testing (Public Beta)
|
||||
|
||||
**Purpose:** Public beta program
|
||||
|
||||
**Characteristics:**
|
||||
- **Audience:** Anyone with the opt-in link
|
||||
- **Review time:** Standard review (1-7 days typically)
|
||||
- **Rollout:** Configurable (staged or full)
|
||||
- **Visibility:** Discoverable in Play Store (with limitations)
|
||||
- **Version requirements:** Must be higher than production
|
||||
|
||||
**Best for:**
|
||||
- Public beta program
|
||||
- Early adopter community
|
||||
- Gathering feedback before production
|
||||
- Testing at scale
|
||||
|
||||
**Setup:**
|
||||
1. Play Console → Release → Testing → Open testing
|
||||
2. Configure:
|
||||
- Countries (where beta is available)
|
||||
- Maximum testers (optional limit)
|
||||
- Feedback settings
|
||||
3. Publish opt-in URL or Play Store listing link
|
||||
|
||||
**Considerations:**
|
||||
- Wider audience = more diverse feedback
|
||||
- Can impact app ratings if buggy
|
||||
- Higher review scrutiny than closed testing
|
||||
- Beta users see "Early Access" badge
|
||||
|
||||
---
|
||||
|
||||
### 4. Production Track
|
||||
|
||||
**Purpose:** Public release to all users
|
||||
|
||||
**Characteristics:**
|
||||
- **Audience:** All users in selected countries
|
||||
- **Review time:** Standard review (1-7 days, can be longer)
|
||||
- **Rollout:** Configurable (staged rollout recommended)
|
||||
- **Visibility:** Fully visible in Play Store
|
||||
- **Version requirements:** Must be higher than previous production
|
||||
|
||||
**Best for:**
|
||||
- Official public releases
|
||||
- App updates for all users
|
||||
- Final release after testing phases
|
||||
|
||||
**Staged Rollout Options:**
|
||||
- **5% → 10% → 20% → 50% → 100%** (recommended)
|
||||
- Pause at any stage if issues detected
|
||||
- Gradually increase to monitor stability
|
||||
- Full rollback available if needed
|
||||
|
||||
**Review factors:**
|
||||
- App content and policies
|
||||
- Metadata and store listing
|
||||
- Previous policy violations (if any)
|
||||
- Random extended reviews sometimes occur
|
||||
|
||||
---
|
||||
|
||||
## Release Workflow
|
||||
|
||||
### Recommended Flow
|
||||
|
||||
```
|
||||
Development
|
||||
↓
|
||||
Internal Testing (continuous, every commit)
|
||||
↓
|
||||
Closed Testing / Alpha (weekly releases)
|
||||
↓
|
||||
Open Testing / Beta (bi-weekly releases)
|
||||
↓
|
||||
Production (monthly major releases)
|
||||
```
|
||||
|
||||
### Alternative Flow (Simpler)
|
||||
|
||||
```
|
||||
Development
|
||||
↓
|
||||
Internal Testing (daily/continuous)
|
||||
↓
|
||||
Production with Staged Rollout (weekly/bi-weekly)
|
||||
```
|
||||
|
||||
### Hotfix Flow
|
||||
|
||||
```
|
||||
Critical Bug Detected
|
||||
↓
|
||||
Fix in Development
|
||||
↓
|
||||
Internal Testing (verify fix)
|
||||
↓
|
||||
Production with Fast Rollout (20% → 50% → 100%)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Track Promotion
|
||||
|
||||
### Promoting Between Tracks
|
||||
|
||||
You can promote a release from one track to another without rebuilding:
|
||||
|
||||
**In Play Console:**
|
||||
1. Navigate to: Release → [Source Track] → Releases
|
||||
2. Find the release to promote
|
||||
3. Click "Promote release"
|
||||
4. Select target track
|
||||
5. Update release notes if needed
|
||||
6. Click "Review release"
|
||||
7. Submit
|
||||
|
||||
**Benefits:**
|
||||
- Same exact APK/AAB (no rebuild needed)
|
||||
- Saves time and ensures consistency
|
||||
- Keeps version code sequential
|
||||
|
||||
**Promotion Paths:**
|
||||
- Internal → Alpha → Beta → Production
|
||||
- Internal → Production (skip beta)
|
||||
- Alpha → Production (skip beta)
|
||||
|
||||
**Restrictions:**
|
||||
- Cannot promote to track with higher version code already
|
||||
- Cannot promote from production to testing tracks
|
||||
|
||||
---
|
||||
|
||||
## Version Code Strategy
|
||||
|
||||
### Option 1: Continuous Versioning (Recommended)
|
||||
|
||||
All tracks use sequential version codes:
|
||||
|
||||
```
|
||||
1 → Internal
|
||||
2 → Internal
|
||||
3 → Alpha
|
||||
4 → Internal
|
||||
5 → Alpha
|
||||
6 → Beta
|
||||
7 → Production
|
||||
8 → Internal
|
||||
...
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Simple and clear
|
||||
- Easy to track
|
||||
- No mental overhead
|
||||
|
||||
**Cons:**
|
||||
- Version numbers increase quickly
|
||||
- Can't easily tell track from version code
|
||||
|
||||
### Option 2: Track-Based Versioning
|
||||
|
||||
Different ranges for different tracks:
|
||||
|
||||
```
|
||||
Internal: 1000-1999 (e.g., 1001, 1002, 1003)
|
||||
Alpha: 2000-2999 (e.g., 2001, 2002)
|
||||
Beta: 3000-3999 (e.g., 3001, 3002)
|
||||
Production: 1-999 (e.g., 1, 2, 3)
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Can tell track from version code
|
||||
- Organized by track
|
||||
|
||||
**Cons:**
|
||||
- More complex to manage
|
||||
- Promotion requires version code bump
|
||||
- Can run out of range
|
||||
|
||||
### Option 3: Semantic Versioning Encoded
|
||||
|
||||
Encode semantic version in version code:
|
||||
|
||||
```
|
||||
Version 1.2.3 = 10203 (Major.Minor.Patch)
|
||||
Version 2.0.0 = 20000
|
||||
Version 2.1.5 = 20105
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
- Version code matches version name
|
||||
- Clear relationship
|
||||
|
||||
**Cons:**
|
||||
- Limited to 2.1.4.7 (max version code is 2100000000)
|
||||
- Must update both version code and name together
|
||||
|
||||
---
|
||||
|
||||
## Release Notes Per Track
|
||||
|
||||
Different tracks can have different release notes:
|
||||
|
||||
**Internal Testing:**
|
||||
```
|
||||
- Fixed crash in user profile
|
||||
- Updated API endpoints
|
||||
- Added logging for debugging
|
||||
```
|
||||
(Can be technical for internal team)
|
||||
|
||||
**Production:**
|
||||
```
|
||||
- Improved app stability
|
||||
- Enhanced user profile experience
|
||||
- Performance optimizations
|
||||
```
|
||||
(User-friendly language)
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Internal Testing for CI/CD
|
||||
- Deploy every commit or daily
|
||||
- Catch issues early
|
||||
- Team always has latest version
|
||||
|
||||
### 2. Beta Test Before Production
|
||||
- Minimum 1 week in beta
|
||||
- Monitor crash rates and reviews
|
||||
- Fix issues before production
|
||||
|
||||
### 3. Use Staged Rollout for Production
|
||||
- Start with 5-10%
|
||||
- Monitor for 24-48 hours
|
||||
- Increase gradually
|
||||
- Pause if issues detected
|
||||
|
||||
### 4. Keep Testers Engaged
|
||||
- Thank beta testers publicly
|
||||
- Respond to feedback
|
||||
- Share roadmap updates
|
||||
- Offer early access to features
|
||||
|
||||
### 5. Monitor Metrics
|
||||
- Crash-free rate per track
|
||||
- ANR (App Not Responding) rate
|
||||
- User feedback and ratings
|
||||
- Install/uninstall rates
|
||||
|
||||
---
|
||||
|
||||
## Track Comparison Table
|
||||
|
||||
| Feature | Internal | Closed | Open | Production |
|
||||
|---------|----------|--------|------|------------|
|
||||
| Max Testers | 100 | Unlimited | Unlimited | Unlimited |
|
||||
| Review Time | None | < 1 day | 1-7 days | 1-7 days |
|
||||
| Staged Rollout | No | Optional | Yes | Yes |
|
||||
| Public Visibility | No | No | Limited | Yes |
|
||||
| Feedback Channel | Email | In-app | In-app + Reviews | Reviews |
|
||||
| Minimum Updates | Immediate | Hours | Days | Days |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Cannot create closed track"**
|
||||
- Ensure app has been published once
|
||||
- Check permissions (need release manager role)
|
||||
|
||||
**"Testers not receiving updates"**
|
||||
- Verify testers opted in via link
|
||||
- Check email list is correct
|
||||
- Internal track: max 100 testers
|
||||
|
||||
**"Version code error on promotion"**
|
||||
- Target track already has higher version code
|
||||
- Increment version code before promotion
|
||||
|
||||
**"Review taking too long"**
|
||||
- Standard: 1-7 days
|
||||
- Contact Play Console support after 7 days
|
||||
- Check for policy violations in email
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Release Tracks Overview](https://support.google.com/googleplay/android-developer/answer/9845334)
|
||||
- [Testing with Internal Tracks](https://support.google.com/googleplay/android-developer/answer/9303479)
|
||||
- [Staged Rollouts](https://support.google.com/googleplay/android-developer/answer/6346149)
|
||||
- [App Review Process](https://support.google.com/googleplay/android-developer/answer/9859455)
|
||||
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate Google Play Store API connection.
|
||||
Tests that service account has proper access to Play Console.
|
||||
|
||||
Usage:
|
||||
python validate-playstore.py <service-account.json> <package-name>
|
||||
|
||||
Example:
|
||||
python validate-playstore.py service-account.json com.example.app
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_service_account_json(json_path):
|
||||
"""Validate service account JSON file format and required fields."""
|
||||
print("Validating service account JSON...")
|
||||
|
||||
# Check file exists
|
||||
if not Path(json_path).exists():
|
||||
print(f"❌ File not found: {json_path}")
|
||||
return False
|
||||
|
||||
# Load and parse JSON
|
||||
try:
|
||||
with open(json_path) as f:
|
||||
data = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"❌ Invalid JSON format: {e}")
|
||||
return False
|
||||
|
||||
# Check required fields
|
||||
required_fields = {
|
||||
"type": "service_account",
|
||||
"project_id": str,
|
||||
"private_key_id": str,
|
||||
"private_key": str,
|
||||
"client_email": str,
|
||||
"client_id": str
|
||||
}
|
||||
|
||||
for field, expected_type in required_fields.items():
|
||||
if field not in data:
|
||||
print(f"❌ Missing required field: {field}")
|
||||
return False
|
||||
|
||||
if field == "type" and data[field] != expected_type:
|
||||
print(f"❌ Invalid type: {data[field]} (expected 'service_account')")
|
||||
return False
|
||||
|
||||
# Validate email format
|
||||
email = data["client_email"]
|
||||
if not email.endswith(".iam.gserviceaccount.com"):
|
||||
print(f"⚠️ Warning: Unexpected email format: {email}")
|
||||
print(f" Expected format: name@project.iam.gserviceaccount.com")
|
||||
|
||||
# Validate private key format
|
||||
private_key = data["private_key"]
|
||||
if not private_key.startswith("-----BEGIN PRIVATE KEY-----"):
|
||||
print(f"❌ Invalid private_key format")
|
||||
return False
|
||||
|
||||
print(f"✅ Service account JSON is valid")
|
||||
print(f" Email: {email}")
|
||||
print(f" Project: {data['project_id']}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_api_connection(json_path, package_name):
|
||||
"""Test connection to Google Play Developer API."""
|
||||
print(f"\nTesting API connection for package: {package_name}...")
|
||||
|
||||
# Check if required packages are installed
|
||||
try:
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient.discovery import build
|
||||
from googleapiclient.errors import HttpError
|
||||
except ImportError:
|
||||
print("❌ Required packages not installed")
|
||||
print("\n Install with:")
|
||||
print(" pip install google-auth google-api-python-client")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Authenticate with service account
|
||||
credentials = service_account.Credentials.from_service_account_file(
|
||||
json_path,
|
||||
scopes=['https://www.googleapis.com/auth/androidpublisher']
|
||||
)
|
||||
|
||||
print("✅ Service account credentials loaded")
|
||||
|
||||
# Build API service
|
||||
service = build('androidpublisher', 'v3', credentials=credentials)
|
||||
|
||||
print("✅ Play Developer API service created")
|
||||
|
||||
# Try to create an edit (validates API access and app existence)
|
||||
edit_request = service.edits().insert(body={}, packageName=package_name)
|
||||
edit_result = edit_request.execute()
|
||||
edit_id = edit_result['id']
|
||||
|
||||
print(f"✅ Successfully connected to Play Developer API")
|
||||
print(f" Can access package: {package_name}")
|
||||
|
||||
# Try to get tracks info
|
||||
try:
|
||||
tracks_response = service.edits().tracks().list(
|
||||
packageName=package_name,
|
||||
editId=edit_id
|
||||
).execute()
|
||||
|
||||
tracks = [track['track'] for track in tracks_response.get('tracks', [])]
|
||||
if tracks:
|
||||
print(f" Available tracks: {', '.join(tracks)}")
|
||||
else:
|
||||
print(f" No releases yet (normal for new apps)")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Could not list tracks: {str(e)}")
|
||||
|
||||
# Clean up edit
|
||||
service.edits().delete(packageName=package_name, editId=edit_id).execute()
|
||||
|
||||
return True
|
||||
|
||||
except HttpError as e:
|
||||
error_details = str(e)
|
||||
|
||||
if "404" in error_details:
|
||||
print(f"❌ App not found in Play Console")
|
||||
print(f"\n Possible causes:")
|
||||
print(f" → Package name is incorrect: {package_name}")
|
||||
print(f" → App hasn't been created in Play Console yet")
|
||||
print(f" → Service account doesn't have access to this app")
|
||||
print(f"\n Fix:")
|
||||
print(f" → Verify package name matches exactly")
|
||||
print(f" → Create app in Play Console first")
|
||||
print(f" → Check service account permissions")
|
||||
|
||||
elif "403" in error_details:
|
||||
print(f"❌ Permission denied")
|
||||
print(f"\n Possible causes:")
|
||||
print(f" → Service account not linked in Play Console")
|
||||
print(f" → Service account lacks required permissions")
|
||||
print(f"\n Fix:")
|
||||
print(f" → Go to Play Console → Setup → API access")
|
||||
print(f" → Grant 'Release' permission to service account")
|
||||
print(f" → Wait 5-10 minutes for permissions to propagate")
|
||||
|
||||
elif "401" in error_details:
|
||||
print(f"❌ Authentication failed")
|
||||
print(f"\n Possible causes:")
|
||||
print(f" → Service account JSON is invalid")
|
||||
print(f" → Service account has been deleted")
|
||||
print(f"\n Fix:")
|
||||
print(f" → Re-download service account JSON from Google Cloud")
|
||||
print(f" → Verify JSON file is complete and valid")
|
||||
|
||||
else:
|
||||
print(f"❌ API error: {error_details}")
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Unexpected error: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main validation function."""
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python validate-playstore.py <service-account.json> <package-name>")
|
||||
print("\nExample:")
|
||||
print(" python validate-playstore.py service-account.json com.example.app")
|
||||
sys.exit(1)
|
||||
|
||||
json_path = sys.argv[1]
|
||||
package_name = sys.argv[2]
|
||||
|
||||
print("=" * 60)
|
||||
print("Play Store API Validation")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Step 1: Validate JSON file
|
||||
if not validate_service_account_json(json_path):
|
||||
print("\n" + "=" * 60)
|
||||
print("❌ Validation FAILED - JSON file is invalid")
|
||||
print("=" * 60)
|
||||
sys.exit(1)
|
||||
|
||||
# Step 2: Test API connection
|
||||
if not test_api_connection(json_path, package_name):
|
||||
print("\n" + "=" * 60)
|
||||
print("❌ Validation FAILED - API connection failed")
|
||||
print("=" * 60)
|
||||
sys.exit(1)
|
||||
|
||||
# Success!
|
||||
print("\n" + "=" * 60)
|
||||
print("✅ All validations passed!")
|
||||
print("=" * 60)
|
||||
print("\nNext steps:")
|
||||
print("1. Add SERVICE_ACCOUNT_JSON to GitHub Secrets")
|
||||
print(" → Repository → Settings → Secrets → Actions")
|
||||
print(" → New repository secret")
|
||||
print(" → Name: SERVICE_ACCOUNT_JSON")
|
||||
print(" → Value: [paste entire JSON file contents]")
|
||||
print("\n2. Run android-playstore-publishing skill")
|
||||
print(" → Generate GitHub Actions workflow")
|
||||
print("\n3. Deploy your app!")
|
||||
print(" → Push to repository or trigger workflow manually")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,3 @@
|
||||
- Initial release
|
||||
- Feature highlights go here
|
||||
- Keep under 500 characters per Play Store guidelines
|
||||
Reference in New Issue
Block a user