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:
Dennis V
2026-05-23 10:51:43 +00:00
parent 884ef13ff7
commit 7ea41216c8
12 changed files with 2906 additions and 0 deletions

View File

@@ -0,0 +1,294 @@
---
name: android-keystore-generation
description: Generate production and local development keystores for Android release signing
category: android
version: 1.0.0
inputs:
- project_path: Path to Android project
- organization: Organization name for certificate
- country_code: 2-letter country code (optional)
outputs:
- keystores/production-release.jks
- keystores/local-dev-release.jks
- keystores/KEYSTORE_INFO.txt
verify: "ls keystores/*.jks && cat keystores/KEYSTORE_INFO.txt"
---
# Android Keystore Generation
Generates dual keystores for Android release signing: production (CI/CD only) and local development.
## Prerequisites
- JDK installed (`keytool` command available)
- Write access to project directory
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| project_path | Yes | . | Android project root |
| organization | Yes | - | Organization name for certificate DN |
| country_code | No | US | 2-letter country code |
## Process
### Organization Name
**ALWAYS prompt the user, but provide the auto-detected value as default.**
**Step 1: Detect organization from package name**
```bash
# Extract package name from build.gradle.kts
PACKAGE=$(grep "applicationId" app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
echo "Package: $PACKAGE"
# Extract second segment: com.{ORG}.app → ORG
ORG=$(echo $PACKAGE | cut -d. -f2)
echo "Detected organization: $ORG"
```
**Step 2: MANDATORY - Ask the user for confirmation**
⛔ **DO NOT SKIP THIS PROMPT**
Ask the user:
> "The detected organization name is **{ORG}**.
> Press Enter to use this, or type a different name:"
Wait for user response. Use their input if provided, otherwise use the detected default.
**Step 3: Store the confirmed organization name**
```bash
ORGANIZATION="{confirmed_org_name}"
echo "Using organization: $ORGANIZATION"
```
**Why this matters:** The organization appears in the certificate's Distinguished Name.
While it doesn't affect app functionality, users may want to customize it.
### Password Generation Options
**Choose one option for keystore passwords:**
#### Option 1 (Recommended): User-Provided Password
Ask the user to provide a password for the production keystore:
> "Please enter a password for the production keystore (minimum 12 characters, mix of letters, numbers, and symbols):"
**Security benefits:**
- Password never visible to the agent during the conversation
- User has complete control over password strength and storage
- Reduces risk of password exposure in logs or conversation history
**Instructions for user:**
```bash
# User will run keytool command manually with their chosen password
# Example:
keytool -genkeypair -v \
-keystore keystores/production-release.jks \
-storetype PKCS12 \
-alias upload \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-storepass "YOUR_PASSWORD_HERE" \
-keypass "YOUR_PASSWORD_HERE" \
-dname "CN=Android Release, OU=Android, O={ORGANIZATION}, C=US"
```
#### Option 2: Generated Password
If the user prefers a generated password, use the automated generation steps below.
**Note:** The agent will have access to this password during the session. The password will be stored in temporary files and KEYSTORE_INFO.txt.
> "Would you like me to generate a secure password? (The agent will see this password during generation)"
If yes, proceed with the automated generation steps.
### Step 1: Create Keystores Directory
```bash
mkdir -p keystores
```
### Step 2: Generate Production Keystore
**SECURITY:** This keystore is for CI/CD only. Never use locally.
**⚠️ IMPORTANT: Run each command in a SEPARATE bash call. Do NOT combine commands.**
**Step 2a: Generate password and save to file**
```bash
openssl rand -base64 24 | tr -d '/+=' | head -c 24 > /tmp/prod_password.txt
```
**Step 2b: Read and display the password**
```bash
cat /tmp/prod_password.txt
```
📝 **Copy this password now** - you'll need it for the keytool command and KEYSTORE_INFO.txt
**Step 2c: Generate the keystore**
Replace `{PASSWORD}` with the password from Step 2b, and `{ORGANIZATION}` with the confirmed organization name:
```bash
keytool -genkeypair -v \
-keystore keystores/production-release.jks \
-storetype PKCS12 \
-alias upload \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-storepass "{PASSWORD}" \
-keypass "{PASSWORD}" \
-dname "CN=Android Release, OU=Android, O={ORGANIZATION}, C=US"
```
**If keytool prompts for confirmation**, type `yes` and press Enter.
**Step 2d: Verify keystore was created**
```bash
ls -la keystores/production-release.jks
```
**Expected output:**
```
-rw------- 1 user staff 2557 Dec 11 10:30 keystores/production-release.jks
```
### Step 3: Generate Local Development Keystore
**⚠️ IMPORTANT: Run each command in a SEPARATE bash call. Do NOT combine commands.**
**Step 3a: Generate password and save to file**
```bash
openssl rand -base64 24 | tr -d '/+=' | head -c 24 > /tmp/local_password.txt
```
**Step 3b: Read and display the password**
```bash
cat /tmp/local_password.txt
```
📝 **Copy this password now** - you'll need it for the keytool command and KEYSTORE_INFO.txt
**Step 3c: Generate the keystore**
Replace `{PASSWORD}` with the password from Step 3b:
```bash
keytool -genkeypair -v \
-keystore keystores/local-dev-release.jks \
-storetype PKCS12 \
-alias local-dev \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-storepass "{PASSWORD}" \
-keypass "{PASSWORD}" \
-dname "CN=Local Development, OU=Development, O=Local, C=US"
```
**If keytool prompts for confirmation**, type `yes` and press Enter.
**Step 3d: Verify keystore was created**
```bash
ls -la keystores/local-dev-release.jks
```
**Expected output:**
```
-rw------- 1 user staff 2557 Dec 11 10:30 keystores/local-dev-release.jks
```
### Step 4: Create Credentials File
```bash
cat > keystores/KEYSTORE_INFO.txt << EOF
Production Keystore
===================
File: production-release.jks
Alias: upload
Store Password: $PROD_PASSWORD
Key Password: $PROD_PASSWORD (same as store - PKCS12 requirement)
⚠️ SECURITY: CI/CD ONLY - Never use on developer machines
GitHub Secrets:
SIGNING_KEY_STORE_BASE64: $(base64 -w 0 keystores/production-release.jks 2>/dev/null || base64 -i keystores/production-release.jks)
SIGNING_KEY_ALIAS: upload
SIGNING_STORE_PASSWORD: $PROD_PASSWORD
SIGNING_KEY_PASSWORD: $PROD_PASSWORD
---
Local Development Keystore
==========================
File: local-dev-release.jks
Alias: local-dev
Store Password: $LOCAL_PASSWORD
Key Password: $LOCAL_PASSWORD
Add to ~/.gradle/gradle.properties:
SIGNING_KEY_STORE_PATH=$(pwd)/keystores/local-dev-release.jks
SIGNING_KEY_ALIAS=local-dev
SIGNING_STORE_PASSWORD=$LOCAL_PASSWORD
SIGNING_KEY_PASSWORD=$LOCAL_PASSWORD
EOF
```
### Step 5: Update .gitignore
```bash
# Add to .gitignore if not present
grep -q "keystores/" .gitignore 2>/dev/null || echo "keystores/" >> .gitignore
grep -q "*.jks" .gitignore 2>/dev/null || echo "*.jks" >> .gitignore
```
## Verification
**MANDATORY:** Run these commands:
```bash
# Verify keystores exist
ls -la keystores/*.jks
# Verify credentials documented
cat keystores/KEYSTORE_INFO.txt
# Verify gitignored
grep "keystores" .gitignore
```
**Expected output:**
- Two .jks files in keystores/
- KEYSTORE_INFO.txt with passwords
- keystores/ in .gitignore
## Outputs
| Output | Location | Description |
|--------|----------|-------------|
| Production keystore | keystores/production-release.jks | For CI/CD only |
| Local keystore | keystores/local-dev-release.jks | For local testing |
| Credentials | keystores/KEYSTORE_INFO.txt | Passwords and setup info |
## Troubleshooting
### "keytool: command not found"
**Cause:** JDK not installed or not in PATH
**Fix:** Install JDK 17: `brew install openjdk@17` (macOS) or `apt install openjdk-17-jdk` (Linux)
### "openssl: command not found"
**Cause:** OpenSSL not installed
**Fix:** Use alternative password generation: `head -c 24 /dev/urandom | base64`
## Completion Criteria
- [ ] `keystores/production-release.jks` exists
- [ ] `keystores/local-dev-release.jks` exists
- [ ] `keystores/KEYSTORE_INFO.txt` exists with passwords
- [ ] `keystores/` is in `.gitignore`

View File

@@ -0,0 +1,513 @@
---
name: android-playstore-scan
description: Scan Android project and generate Play Console setup checklist (analysis only, no file modifications)
category: android
version: 1.0.0
inputs:
- project_path: Path to Android project
outputs:
- PLAY_CONSOLE_SETUP.md
verify: "test -f PLAY_CONSOLE_SETUP.md && grep -q 'Play Console Setup Guide' PLAY_CONSOLE_SETUP.md"
---
# Android Play Store Scanner
**ANALYSIS ONLY** - Scans your Android project and generates a comprehensive Play Console setup checklist. Does NOT modify any files.
## Overview
**Purpose:**
- Analyze project to detect app features and requirements
- Pre-fill Play Console setup answers based on code analysis
- Identify missing configurations (privacy policy, etc.)
- Generate actionable checklist for Play Console setup
**What this skill does:**
1. Scans AndroidManifest.xml for permissions and features
2. Analyzes build.gradle for SDKs and dependencies
3. Detects Health Connect, ads, analytics
4. Checks for privacy policy URL
5. Generates `PLAY_CONSOLE_SETUP.md` with pre-filled answers
6. Provides recommendations for missing items
**What this skill does NOT do:**
- ❌ Does not create or modify any project files
- ❌ Does not setup GitHub Actions or workflows
- ❌ Does not generate privacy policies (use `/devtools:privacy-policy-generate`)
- ❌ Does not configure signing or release builds
## Prerequisites
- Android project with AndroidManifest.xml
- Build configuration files (build.gradle.kts)
## Inputs
| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| project_path | Yes | . | Android project root |
## Process
### Step 1: Scan AndroidManifest.xml
```bash
echo "📱 Scanning AndroidManifest.xml..."
# Get package name
PACKAGE_NAME=$(grep "package=" app/src/main/AndroidManifest.xml | head -1 | sed 's/.*package="\([^"]*\)".*/\1/')
echo " Package: $PACKAGE_NAME"
# Get app name
APP_NAME=$(grep 'name="app_name"' app/src/main/res/values/strings.xml 2>/dev/null | sed 's/.*>\([^<]*\)<.*/\1/' || echo "Unknown")
echo " App name: $APP_NAME"
# Check permissions
echo " Scanning permissions..."
PERMISSIONS=$(grep "<uses-permission" app/src/main/AndroidManifest.xml | sed 's/.*android:name="\([^"]*\)".*/\1/')
# Detect features
INTERNET=$(echo "$PERMISSIONS" | grep -q "INTERNET" && echo "Yes" || echo "No")
LOCATION=$(echo "$PERMISSIONS" | grep -q "LOCATION" && echo "Yes" || echo "No")
CAMERA=$(echo "$PERMISSIONS" | grep -q "CAMERA" && echo "Yes" || echo "No")
HEALTH=$(echo "$PERMISSIONS" | grep -q "health.permission" && echo "Yes" || echo "No")
echo " Internet: $INTERNET"
echo " Location: $LOCATION"
echo " Camera: $CAMERA"
echo " Health Connect: $HEALTH"
```
### Step 2: Analyze Dependencies
```bash
echo "📦 Scanning build.gradle.kts..."
# Check for ads
ADS_DETECTED="No"
if grep -q "admob" app/build.gradle.kts 2>/dev/null; then
ADS_DETECTED="Yes (AdMob)"
elif grep -q "facebook-ads" app/build.gradle.kts 2>/dev/null; then
ADS_DETECTED="Yes (Facebook Audience Network)"
fi
echo " Ads: $ADS_DETECTED"
# Check for analytics
ANALYTICS_DETECTED=""
grep -q "firebase-analytics" app/build.gradle.kts 2>/dev/null && ANALYTICS_DETECTED="Firebase Analytics"
grep -q "google-analytics" app/build.gradle.kts 2>/dev/null && ANALYTICS_DETECTED="${ANALYTICS_DETECTED} Google Analytics"
echo " Analytics: ${ANALYTICS_DETECTED:-None}"
# Check for payments
PAYMENTS="No"
grep -q "billing" app/build.gradle.kts 2>/dev/null && PAYMENTS="Yes (Google Play Billing)"
echo " In-app purchases: $PAYMENTS"
# Check for auth
AUTH_DETECTED=""
grep -q "firebase-auth" app/build.gradle.kts 2>/dev/null && AUTH_DETECTED="Firebase Auth"
grep -q "play-services-auth" app/build.gradle.kts 2>/dev/null && AUTH_DETECTED="${AUTH_DETECTED} Google Sign-In"
echo " Authentication: ${AUTH_DETECTED:-None detected}"
```
### Step 3: Check Privacy Policy
```bash
echo "🔒 Checking for privacy policy..."
PRIVACY_POLICY_URL=""
# Check common locations
if [ -f docs/privacy-policy.md ]; then
PRIVACY_POLICY_URL="https://$(git config remote.origin.url | sed 's/.*github.com[:/]\(.*\)\.git/\1/')/privacy-policy"
echo " ✓ Found: docs/privacy-policy.md"
echo " Suggested URL: $PRIVACY_POLICY_URL"
elif grep -q "privacy" README.md 2>/dev/null; then
echo " ⚠️ Privacy policy mentioned in README but not found in docs/"
else
echo " ❌ Privacy policy not found"
PRIVACY_POLICY_URL="⚠️ ACTION REQUIRED"
fi
```
### Step 4: Detect Health Connect Data Types
If Health Connect is detected:
```bash
if [ "$HEALTH" = "Yes" ]; then
echo "💚 Analyzing Health Connect integration..."
HEALTH_DATA_TYPES=""
grep -q "STEPS" app/src/main/AndroidManifest.xml && HEALTH_DATA_TYPES="${HEALTH_DATA_TYPES}- Steps (read/write)\n"
grep -q "HEART_RATE" app/src/main/AndroidManifest.xml && HEALTH_DATA_TYPES="${HEALTH_DATA_TYPES}- Heart rate (read)\n"
grep -q "SLEEP" app/src/main/AndroidManifest.xml && HEALTH_DATA_TYPES="${HEALTH_DATA_TYPES}- Sleep sessions (read)\n"
grep -q "EXERCISE" app/src/main/AndroidManifest.xml && HEALTH_DATA_TYPES="${HEALTH_DATA_TYPES}- Exercise sessions (read/write)\n"
echo -e " Health data types:\n$HEALTH_DATA_TYPES"
fi
```
### Step 5: Estimate Content Rating
```bash
echo "🎯 Estimating content rating..."
# Simple heuristic - actual rating requires IARC questionnaire
ESTIMATED_RATING="Everyone (E)"
if grep -qi "violence\|weapon\|blood" app/src/main/res/values/strings.xml 2>/dev/null; then
ESTIMATED_RATING="Teen (T) - Violence detected"
elif grep -qi "gambling\|casino\|lottery" app/src/main/res/values/strings.xml 2>/dev/null; then
ESTIMATED_RATING="Mature (M) - Gambling detected"
fi
echo " Estimated: $ESTIMATED_RATING"
echo " Note: Complete official IARC questionnaire in Play Console"
```
### Step 6: Generate PLAY_CONSOLE_SETUP.md
Create comprehensive setup guide:
```markdown
# Play Console Setup Guide for: {APP_NAME}
Generated: {CURRENT_DATE}
---
## Summary
| Property | Value |
|----------|-------|
| **Package Name** | {PACKAGE_NAME} |
| **App Name** | {APP_NAME} |
| **Privacy Policy** | {PRIVACY_POLICY_STATUS} |
| **Uses Health Connect** | {HEALTH_DETECTED} |
| **Contains Ads** | {ADS_DETECTED} |
| **In-App Purchases** | {IAP_DETECTED} |
---
## 1. Privacy Policy
**Status:** {PRIVACY_STATUS}
{IF_NOT_FOUND}
**⚠️ ACTION REQUIRED:** No privacy policy found
**Recommended action:**
```
/devtools:privacy-policy-generate
```
This will create a privacy policy at `docs/privacy-policy.md` ready for GitHub Pages.
{ELSE}
**✓ Privacy policy found:** `docs/privacy-policy.md`
**Suggested URL:** `{PRIVACY_URL}`
**Next steps:**
1. Enable GitHub Pages (Settings → Pages → Source: docs/)
2. Wait 1-2 minutes for deployment
3. Verify URL is accessible
4. Add URL to Play Console
{END_IF}
---
## 2. App Access
**Detected:** {ACCESS_ANALYSIS}
{IF_AUTH_DETECTED}
**Answer:** "Yes, some functionality requires special access"
**Instructions to provide:**
- Test account credentials (if applicable)
- How to access restricted features
{ELSE}
**Answer:** "All functionality available without special access"
{END_IF}
---
## 3. Ads Declaration
**Detected SDKs:** {ADS_DETECTED}
{IF_ADS}
**Answer:** "Yes, my app contains ads"
**Ad networks:** {AD_NETWORKS_LIST}
{ELSE}
**Answer:** "No, my app does not contain ads"
{END_IF}
---
## 4. Content Rating
**Estimated Rating:** {ESTIMATED_RATING}
**Action Required:**
1. Go to Play Console → **App content** → **Content rating**
2. Complete IARC questionnaire
3. Answer questions about:
- Violence
- Sexual content
- Profanity
- Controlled substances
- Gambling
- User-generated content
---
## 5. Data Safety Form
**Data Collection Detected:**
| Data Type | Collected | Purpose |
|-----------|-----------|---------|
{IF_HEALTH}
| Health & Fitness | Yes | Health Connect integration |
{END_IF}
{IF_LOCATION}
| Location | Yes | {LOCATION_PURPOSE} |
{END_IF}
{IF_ANALYTICS}
| App activity | Yes | Analytics ({ANALYTICS_SERVICES}) |
{END_IF}
| Device ID | {DEVICE_ID_COLLECTED} | {DEVICE_ID_PURPOSE} |
**Security Practices:**
| Practice | Status | Notes |
|----------|--------|-------|
| Data encrypted in transit | {HTTPS_DETECTED} | HTTPS detected in network config |
| Data encrypted at rest | ⚠️ User to confirm | Depends on implementation |
| Users can request deletion | ⚠️ User to confirm | Add if applicable |
**Recommended answers:**
- Data is collected: {DATA_COLLECTED}
- Data is shared: {DATA_SHARED}
- Data collection is optional: {DATA_OPTIONAL}
---
{IF_HEALTH}
## 6. Health App Declaration
**⚠️ REQUIRED:** Your app uses Health Connect
**Health Data Types Detected:**
{HEALTH_DATA_TYPES_LIST}
**Action Required:**
1. Go to Play Console → **App content** → **Health**
2. Select "Yes, my app accesses health data"
3. List all health data types your app accesses
4. Explain how each data type is used
5. Confirm privacy policy includes health data disclosure
**Privacy Policy Requirements:**
- ✅ Must explain what health data is accessed
- ✅ Must explain why it's accessed
- ✅ Must explain how it's stored
- ✅ Must explain if it's shared with third parties
**Verify privacy policy includes Health Data section**
{END_IF}
---
## 7. Store Listing (Draft)
**Title:** {APP_NAME}
**Short Description (80 chars):**
{GENERATED_SHORT_DESC}
**Full Description:**
{GENERATED_FULL_DESC}
⚠️ Review and customize these descriptions before submitting
---
## 8. First Upload Checklist
Before you can use automated deployments, you must manually upload your first release:
### Preparation
- [ ] Run `/devtools:android-keystore-generation` (if not done)
- [ ] Run `/devtools:android-signing-config`
- [ ] Build release AAB: `./gradlew bundleRelease`
- [ ] Verify AAB is signed: `jarsigner -verify -verbose app/build/outputs/bundle/release/app-release.aab`
### Play Console First Upload
- [ ] Go to Play Console → **Release** → **Internal testing**
- [ ] Click **Create new release**
- [ ] Upload `app-release.aab`
- [ ] Complete store listing (title, description, screenshots)
- [ ] Complete all required declarations above
- [ ] Review and publish to internal testing
### Important Notes
⚠️ **First Upload Keystore = CI Keystore**
The keystore you use for the first upload becomes your **upload key**. You must use the same keystore in CI/CD.
**After first upload:**
- Note the SHA-1 fingerprint from Play Console
- Use the same production keystore for GitHub Actions
- Do NOT generate a different keystore for CI
### Play App Signing
**How it works:**
1. First upload: Google creates the **app signing key** (permanent)
2. Your keystore becomes the **upload key** (can be reset if lost)
3. Google re-signs your AAB with the app signing key before distribution
**No action needed** - Play App Signing is automatic for new apps.
---
## 9. Next Steps
After completing the checklist above:
```bash
# Setup automated Play Store publishing
/devtools:android-playstore-setup
```
This will:
- Configure Gradle Play Publisher plugin
- Create GitHub Actions workflows
- Setup release automation
---
## 10. Track Differences
Understanding Play Store release tracks:
| Track | Audience | Use Case | Review Time |
|-------|----------|----------|-------------|
| **Internal** | Up to 100 testers | Quick testing | Instant |
| **Closed (Alpha)** | Invited testers | Beta testing | < 24h |
| **Open (Beta)** | Anyone can join | Public beta | < 24h |
| **Production** | All users | Full release | 1-7 days |
**Recommended flow:**
1. Internal → Verify app works
2. Beta → Wider testing (optional)
3. Production → Full release
---
## Validation
Run validation checks:
```bash
# Validate Play Store API connection
python3 scripts/validate-playstore.py SERVICE_ACCOUNT.json {PACKAGE_NAME}
```
Expected: ✅ All validations passed
---
## Resources
- [Play Console Help](https://support.google.com/googleplay/android-developer)
- [Data safety form guide](https://support.google.com/googleplay/android-developer/answer/10787469)
- [Health Connect policy](https://support.google.com/googleplay/android-developer/answer/13996367)
- [Content rating questionnaire](https://support.google.com/googleplay/android-developer/answer/9859655)
---
*Generated by android-playstore-scan v1.0.0*
```
## Verification
**MANDATORY:** Run these commands:
```bash
# Verify setup guide created
test -f PLAY_CONSOLE_SETUP.md && echo "✓ Setup guide created"
# Verify key sections present
grep -q "Privacy Policy" PLAY_CONSOLE_SETUP.md && echo "✓ Privacy policy section"
grep -q "Data Safety" PLAY_CONSOLE_SETUP.md && echo "✓ Data safety section"
grep -q "First Upload" PLAY_CONSOLE_SETUP.md && echo "✓ First upload checklist"
```
**Expected output:**
- ✓ Setup guide created
- ✓ Privacy policy section
- ✓ Data safety section
- ✓ First upload checklist
## Outputs
| Output | Location | Description |
|--------|----------|-------------|
| Setup checklist | PLAY_CONSOLE_SETUP.md | Complete Play Console setup guide |
## What Happens Next
After reviewing `PLAY_CONSOLE_SETUP.md`:
1. **Address action items** (privacy policy, missing configs)
2. **Run setup skill:**
```
/devtools:android-playstore-setup
```
3. This will execute the actual setup based on the checklist
## Detected vs. Actual
**This skill detects:**
- ✅ Declared permissions in manifest
- ✅ Dependencies in build.gradle
- ✅ Health Connect integration
- ✅ Common SDKs (ads, analytics)
**This skill cannot detect:**
- ❌ Runtime permission usage patterns
- ❌ Actual network calls and endpoints
- ❌ User-generated content handling
- ❌ Content rating accuracy
**Always review the generated checklist and adjust based on your actual implementation.**
## Troubleshooting
### "Package name not found"
**Cause:** AndroidManifest.xml format issue
**Fix:** Verify `package` attribute exists in `<manifest>` tag
### "No permissions detected"
**Cause:** Permissions declared in Gradle instead of manifest
**Fix:** Check build.gradle.kts for permission declarations
### "Privacy policy detection failed"
**Cause:** Privacy policy in non-standard location
**Fix:** Update scan or manually specify URL in generated checklist
## Completion Criteria
- [ ] `PLAY_CONSOLE_SETUP.md` created
- [ ] All project features detected correctly
- [ ] Privacy policy status identified
- [ ] Health Connect integration detected (if applicable)
- [ ] Action items clearly marked
- [ ] Ready to proceed with actual setup

View 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

View File

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

View File

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

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

View File

@@ -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()

View File

@@ -0,0 +1,3 @@
- Initial release
- Feature highlights go here
- Keep under 500 characters per Play Store guidelines

View File

@@ -0,0 +1,233 @@
---
name: android-service-account-guide
description: Step-by-step guide for creating Google Cloud service account for Play Store API access
category: android
version: 1.0.0
inputs:
- package_name: Android app package name
outputs:
- Service account JSON (user downloads manually)
- Documentation: distribution/PLAY_CONSOLE_SETUP.md
verify: "User confirms service account created"
---
# Android Service Account Guide
Step-by-step guide for creating a Google Cloud service account with Play Store API access. This is a manual process with documentation.
## Prerequisites
- Google Play Developer account ($25 one-time)
- Google Cloud Platform account (free)
- Admin access to Play Console
## Process
### Step 1: Create Documentation
Create `distribution/PLAY_CONSOLE_SETUP.md`:
```markdown
# Google Play Console Setup
Complete guide for setting up service account and API access.
## Step 1: Create Google Cloud Project
1. Go to: https://console.cloud.google.com/
2. Click "Select a project" → "New Project"
3. Name: "Android App Deployment"
4. Click "Create"
5. Wait for project creation (30 seconds)
## Step 2: Create Service Account
1. In Cloud Console, go to: IAM & Admin → Service Accounts
2. Click "Create Service Account"
3. Name: `playstore-deploy`
4. Description: "Automated Play Store deployment"
5. Click "Create and Continue"
6. Skip role assignment (click "Continue")
7. Click "Done"
## Step 3: Create Service Account Key
1. Find your service account in the list
2. Click ⋮ (three dots) → "Manage keys"
3. Click "Add Key" → "Create new key"
4. Select "JSON"
5. Click "Create"
6. **CRITICAL:** Save the downloaded JSON file securely
- Store in password manager
- Never commit to git
- This is your only copy!
## Step 4: Enable Play Developer API
1. In Cloud Console, go to: APIs & Services → Library
2. Search: "Google Play Android Developer API"
3. Click on it
4. Click "Enable"
5. Wait for activation (30 seconds)
## Step 5: Link to Play Console
1. Go to: https://play.google.com/console/
2. Select your app
3. Go to: Setup → API access
4. Click "Link a Google Cloud project"
5. Select your project from dropdown
6. Click "Link"
## Step 6: Grant Service Account Access
1. Still in Play Console → API access
2. Find your service account in "Service accounts" section
3. Click "Grant access"
4. Check: "Release to production, exclude devices, and use Play App Signing"
5. Click "Apply"
6. Click "Invite user"
## Step 7: Verify Setup
Service account email format:
`playstore-deploy@PROJECT_ID.iam.gserviceaccount.com`
✅ Checklist:
- [ ] Service account created
- [ ] JSON key downloaded and stored securely
- [ ] Play Developer API enabled
- [ ] Cloud project linked to Play Console
- [ ] Service account has "Release" permission
- [ ] Permissions have propagated (wait 5-10 minutes)
## Security Notes
🔒 **Service Account JSON:**
- Contains sensitive credentials
- Store in password manager
- Never commit to version control
- Rotate keys annually
- One key per environment (dev/prod)
🔒 **Permissions:**
- Grant minimum required permissions only
- Review access logs regularly
- Revoke unused accounts
- Use 2FA on Google account
```
### Step 2: Guide User Through Process
**Interactive guidance:**
1. Ask: "Do you have a Google Play Developer account?"
2. Ask: "What is your app's package name?"
3. Display the step-by-step instructions
4. Wait for user confirmation at each major step
5. Verify service account email format
**No automated actions** - this skill is pure documentation and guidance.
### Step 3: Create GitHub Secrets Documentation
Create `distribution/GITHUB_SECRETS.md`:
```markdown
# GitHub Secrets Setup
Add these secrets to your GitHub repository for automated deployment.
## Required Secrets
Go to: Repository → Settings → Secrets and variables → Actions → New repository secret
### 1. SERVICE_ACCOUNT_JSON_PLAINTEXT
**Value:** Entire plaintext contents of the JSON file downloaded in service account setup (not base64 encoded)
**How to add:**
1. Open the service account JSON file
2. Copy entire contents (including { and })
3. Paste as secret value
4. Click "Add secret"
### 2. SIGNING_KEY_STORE_BASE64
**Value:** Base64-encoded production keystore
**How to create:**
```bash
base64 -w 0 keystores/production-release.jks
# OR on macOS:
base64 -i keystores/production-release.jks
```
### 3. SIGNING_KEY_ALIAS
**Value:** `upload` (from KEYSTORE_INFO.txt)
### 4. SIGNING_STORE_PASSWORD
**Value:** Production keystore password (from KEYSTORE_INFO.txt)
### 5. SIGNING_KEY_PASSWORD
**Value:** Production key password (same as store password for PKCS12)
## Verification
After adding secrets:
1. Go to: Repository → Settings → Secrets and variables → Actions
2. Verify all 5 secrets are listed
3. Secrets are encrypted and cannot be viewed after creation
4. Use workflow runs to verify secrets work
## Security Notes
- Never log secret values
- Rotate SERVICE_ACCOUNT_JSON_PLAINTEXT annually
- Keep KEYSTORE_INFO.txt secure (not in git)
- Use environment protection for production deployments
```
## Verification
**User confirmation required:**
Ask user to confirm:
- [ ] Service account created in Google Cloud
- [ ] JSON key downloaded and stored in password manager
- [ ] Play Developer API enabled
- [ ] Service account linked to Play Console
- [ ] Service account has "Release" permission
- [ ] Waited 5-10 minutes for permissions to propagate
## Outputs
| Output | Location | Description |
|--------|----------|-------------|
| Setup guide | distribution/PLAY_CONSOLE_SETUP.md | Complete setup instructions |
| Secrets guide | distribution/GITHUB_SECRETS.md | GitHub Secrets documentation |
| Service account JSON | User's secure storage | Downloaded by user manually |
## Troubleshooting
### "Cannot create service account"
**Cause:** Billing not enabled
**Fix:** Link billing account in Google Cloud (API is free)
### "Service account not appearing in Play Console"
**Cause:** Propagation delay
**Fix:** Wait 1-2 minutes, refresh page, clear browser cache
### "API enable button grayed out"
**Cause:** Wrong project selected or insufficient permissions
**Fix:** Verify project selection, check you have Owner/Editor role
## Completion Criteria
- [ ] distribution/PLAY_CONSOLE_SETUP.md created
- [ ] distribution/GITHUB_SECRETS.md created
- [ ] User confirms service account created
- [ ] User confirms JSON key downloaded and secured
- [ ] User confirms permissions granted in Play Console

2
.gitignore vendored
View File

@@ -10,3 +10,5 @@ ios/Pods/
*.keystore *.keystore
.env .env
.env.* .env.*
keystores/
*.jks

View File

@@ -0,0 +1,108 @@
# Google Play Console Setup — OpenCode Mobile
Package: `ai.opencode.mobile`
Developer: Vibe Technologies, LLC
## Status
- [x] Keystore generated (`keystores/production-release.jks`, alias: `upload`)
- [x] GitHub secrets set: `KEYSTORE_BASE64`, `KEYSTORE_PASSWORD`, `KEY_ALIAS`, `KEY_PASSWORD`
- [ ] Google Cloud project created
- [ ] Service account created + JSON key downloaded
- [ ] Play Developer API enabled
- [ ] Service account linked to Play Console
- [ ] App created in Play Console (`ai.opencode.mobile`)
- [ ] First AAB manually uploaded (internal track)
- [ ] GitHub secret set: `PLAY_STORE_SERVICE_ACCOUNT_JSON`
---
## Step 1: Create Google Cloud Project
1. Go to: https://console.cloud.google.com/
2. "Select a project" → "New Project"
3. Name: `opencode-mobile-deploy`
4. Click "Create"
## Step 2: Create Service Account
1. IAM & Admin → Service Accounts
2. "Create Service Account"
3. Name: `playstore-deploy`
4. Description: `Automated Play Store deployment for ai.opencode.mobile`
5. "Create and Continue" → skip role → "Done"
## Step 3: Download JSON Key
1. Click service account → "Keys" tab
2. "Add Key" → "Create new key" → JSON → "Create"
3. File downloads automatically — save to Bitwarden as `PLAY_STORE_SERVICE_ACCOUNT_JSON`
4. **NEVER commit to git**
## Step 4: Enable Play Developer API
1. APIs & Services → Library
2. Search: `Google Play Android Developer API`
3. Click "Enable"
## Step 5: Link Cloud Project to Play Console
1. https://play.google.com/console/
2. All apps → (select `ai.opencode.mobile`)
3. Setup → API access
4. "Link a Google Cloud project" → select `opencode-mobile-deploy`
5. "Link"
## Step 6: Grant Service Account Permissions
1. Play Console → Setup → API access → Service accounts
2. Find `playstore-deploy@...` → "Grant access"
3. Check: **"Release to production, exclude devices, and use Play App Signing"**
4. "Apply" → "Invite user"
## Step 7: First Manual Upload (REQUIRED before CI can deploy)
Google Play requires at least one manual upload before automated CI uploads work.
```bash
# Build AAB locally (in the opencode-mobile repo)
cd android
RELEASE_STORE_FILE=../keystores/production-release.jks \
RELEASE_STORE_PASSWORD=$(cat ../keystores/KEYSTORE_INFO.txt | grep "Store Password:" | head -1 | awk '{print $NF}') \
RELEASE_KEY_ALIAS=upload \
RELEASE_KEY_PASSWORD=$(cat ../keystores/KEYSTORE_INFO.txt | grep "Store Password:" | head -1 | awk '{print $NF}') \
./gradlew bundleRelease
```
Then in Play Console:
1. Internal testing → "Create new release"
2. Upload `android/app/build/outputs/bundle/release/app-release.aab`
3. Complete store listing (title, description, icon)
4. Complete app content declarations
5. "Save and publish"
## Step 8: Add Service Account JSON to GitHub
```bash
# After downloading the JSON key from Google Cloud:
cat /path/to/service-account.json | \
gh secret set PLAY_STORE_SERVICE_ACCOUNT_JSON --repo dzianisv/opencode-mobile
```
## Step 9: Test CI Deployment
Trigger workflow manually:
```bash
gh workflow run publish-play-store.yml --repo dzianisv/opencode-mobile
```
---
## Troubleshooting
| Error | Cause | Fix |
|-------|-------|-----|
| `Tag number over 30 is not supported` | Corrupt keystore | ✅ Fixed — new PKCS12 keystore generated |
| `Package not found` | App not in Play Console | Complete Step 7 (first manual upload) |
| `Permission denied` for service account | Permissions not propagated | Wait 5-10 min after Step 6 |
| `serviceAccountJsonPlainText` invalid | Wrong JSON format | Use raw JSON, not base64 |

29
skills-lock.json Normal file
View File

@@ -0,0 +1,29 @@
{
"version": 1,
"skills": {
"android-keystore-generation": {
"source": "hitoshura25/claude-devtools",
"sourceType": "github",
"skillPath": "skills/android-keystore-generation/SKILL.md",
"computedHash": "15c6e1347dc19f0ffd75638619aeee25a9e8dc1e00839484a75ea31c4f1ad697"
},
"android-playstore-scan": {
"source": "hitoshura25/claude-devtools",
"sourceType": "github",
"skillPath": "skills/android-playstore-scan/SKILL.md",
"computedHash": "95fbe285b35302e69cd79669fa14ccdf35a69cf16474621cd0dd7ad066191609"
},
"android-playstore-setup": {
"source": "hitoshura25/claude-devtools",
"sourceType": "github",
"skillPath": "skills/android-playstore-setup/SKILL.md",
"computedHash": "6b5a88d34cf4fef6dc2d813eeb21f54ae84a4446a850745eaa29a5a1a9a4c6f8"
},
"android-service-account-guide": {
"source": "hitoshura25/claude-devtools",
"sourceType": "github",
"skillPath": "skills/android-service-account-guide/SKILL.md",
"computedHash": "8d2cc33e53db6815429c575fb6cf32ddb43781b422b51f135531275073b6ce10"
}
}
}