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