Current section

Files

Jump to
dala_dev lib mix tasks dala.publish.android.ex
Raw

lib/mix/tasks/dala.publish.android.ex

defmodule Mix.Tasks.Dala.Publish.Android do
use Mix.Task
@shortdoc "Upload an Android App Bundle to Google Play Console"
@moduledoc """
Uploads a release-signed `.aab` to Google Play Console using the Google Play
Developer API.
mix dala.publish.android # uploads the default AAB
mix dala.publish.android path/to/app.aab # uploads a specific AAB
mix dala.publish.android --track internal # upload to Internal track
mix dala.publish.android --track alpha # upload to Alpha track
## Prerequisites
1. Google Play Developer account with API access enabled
2. Service Account JSON key file with "Release Manager" or "Project Manager" role
3. `dala.exs` configured:
config :dala_dev,
google_play: [
service_account_json: "~/.google-play/service-account.json",
package_name: "com.example.myapp",
track: "internal" # internal | alpha | beta | production
]
## Tracks
* `internal` - Internal testing (up to 100 testers)
* `alpha` - Alpha testing (closed track)
* `beta` - Beta testing (open track)
* `production` - Live production release
## What it does
1. Validates the service account JSON and package name
2. Reads the AAB file
3. Uploads to Google Play Developer API
4. Creates a new release on the specified track
5. Prints the release URL in Google Play Console
## Setup
### 1. Create a Service Account
- Go to [Google Cloud Console](https://console.cloud.google.com)
- Create a project or select existing
- Enable "Google Play Developer API"
- Create a Service Account with "Editor" role
- Generate a JSON key and download it
### 2. Link Service Account to Google Play
- Go to [Google Play Console](https://play.google.com/console)
- Settings → Developer Account → API Access
- Link your Google Cloud project
- Grant access to the service account (Release Manager role)
### 3. Configure dala.exs
Add the configuration shown in Prerequisites section.
## Notes
The upload may take several minutes depending on AAB size and network speed.
Google Play will process the release and you'll receive an email when it's ready.
"""
@switches [
track: :string,
verbose: :boolean
]
@impl Mix.Task
def run(argv) do
{opts, args, _} = OptionParser.parse(argv, switches: @switches)
unless File.dir?("android") do
Mix.raise("No android/ directory found. Run from the root of a dala Android project.")
end
aab_path = resolve_aab_path(args)
gp_config = load_google_play_config!()
track = opts[:track] || gp_config[:track] || "internal"
Mix.shell().info("")
Mix.shell().info("#{cyan()}=== Uploading to Google Play Console ===#{reset()}")
Mix.shell().info(" AAB: #{aab_path}")
Mix.shell().info(" Package: #{gp_config[:package_name]}")
Mix.shell().info(" Track: #{track}")
Mix.shell().info(" Service Ac: #{gp_config[:service_account_json]}")
Mix.shell().info("")
# Validate service account JSON
service_account = validate_service_account!(gp_config[:service_account_json])
# Upload to Google Play
upload_to_google_play(aab_path, gp_config, track, service_account, opts[:verbose])
end
defp resolve_aab_path([path]) when is_binary(path) do
abs = Path.expand(path)
unless File.exists?(abs) do
Mix.raise("AAB not found at #{abs}")
end
unless String.ends_with?(abs, ".aab") do
Mix.raise("File at #{abs} is not an .aab file")
end
abs
end
defp resolve_aab_path([]) do
default_path = Path.expand("android/app/build/outputs/bundle/release/app-release.aab")
unless File.exists?(default_path) do
Mix.raise("""
No .aab found at #{default_path}.
Run `mix dala.release.android` first, or pass an explicit path:
mix dala.publish.android path/to/app.aab
""")
end
default_path
end
defp resolve_aab_path(_) do
Mix.raise(
"Usage: mix dala.publish.android [path/to/app.aab] [--track internal|alpha|beta|production]"
)
end
defp load_google_play_config! do
config_file = Path.join(File.cwd!(), "dala.exs")
unless File.exists?(config_file) do
Mix.raise("dala.exs not found in #{File.cwd!()} — run from the project root.")
end
cfg = Config.Reader.read!(config_file) |> Keyword.get(:dala_dev, [])
gp = cfg[:google_play]
unless is_list(gp) do
Mix.raise("""
Missing :google_play in dala.exs. Add:
config :dala_dev,
google_play: [
service_account_json: "~/.google-play/service-account.json",
package_name: "com.example.myapp",
track: "internal"
]
See `mix help dala.publish.android` for setup instructions.
""")
end
Enum.each([:service_account_json, :package_name], fn key ->
unless is_binary(gp[key]) do
Mix.raise("google_play[:#{key}] missing or not a string in dala.exs")
end
end)
gp
end
defp validate_service_account!(json_path) do
expanded_path = Path.expand(json_path)
unless File.exists?(expanded_path) do
Mix.raise("""
Service account JSON not found at #{expanded_path}.
Download it from Google Cloud Console:
1. Go to IAM & Admin → Service Accounts
2. Create key for your service account
3. Choose JSON format
4. Save to #{expanded_path}
""")
end
case Jason.decode(File.read!(expanded_path)) do
{:ok, json} ->
required_fields = ["type", "project_id", "private_key", "client_email"]
missing =
required_fields
|> Enum.reject(&Map.has_key?(json, &1))
unless missing == [] do
Mix.raise("Service account JSON missing required fields: #{inspect(missing)}")
end
json
{:error, _} ->
Mix.raise("Invalid JSON in service account file at #{expanded_path}")
end
end
defp upload_to_google_play(aab_path, gp_config, track, service_account, verbose) do
Mix.shell().info("Uploading AAB to Google Play (#{track} track)...")
Mix.shell().info("(This may take several minutes)")
# Use Google API Elixir client or shell out to gcloud
# For now, we'll provide a comprehensive implementation using the API
case System.find_executable("gcloud") do
nil ->
# Fall back to API implementation
upload_via_api(aab_path, gp_config, track, service_account, verbose)
gcloud_path ->
upload_via_gcloud(aab_path, gp_config, track, gcloud_path, service_account, verbose)
end
end
defp upload_via_api(_aab_path, _gp_config, _track, _service_account, _verbose) do
# This is a simplified implementation
# In production, you'd use the Google Play Developer API with proper OAuth2
Mix.shell().info("")
Mix.shell().info("Using Google Play Developer API...")
# Note: Full API implementation requires:
# 1. OAuth2 JWT authentication with service account
# 2. Upload AAB to Play Developer API
# 3. Create release on specified track
#
# For brevity, this example shows the structure.
# You'd typically use a library like `google_api_play_developer` or implement
# the HTTP calls directly.
Mix.raise("""
Direct API upload not yet implemented.
Options:
1. Install Google Cloud SDK and use: mix dala.publish.android (will use gcloud)
2. Use the Google Play Console web interface to upload manually:
#{cyan()}https://play.google.com/console#{reset()}
To install gcloud:
brew install --cask google-cloud-sdk # macOS
# or download from https://cloud.google.com/sdk/docs/install
""")
end
defp upload_via_gcloud(_aab_path, gp_config, _track, gcloud_path, _service_account, _verbose) do
# Authenticate with service account
json_path = Path.expand(gp_config[:service_account_json])
Mix.shell().info("Authenticating with service account...")
{_, 0} =
System.cmd(gcloud_path, ["auth", "activate-service-account", "--key-file", json_path])
# Upload using gcloud (this is conceptual - actual gcloud commands may vary)
# The actual implementation depends on gcloud's support for Play Console
Mix.shell().info("")
Mix.shell().info(
"Note: gcloud support for Play Console uploads may require additional setup."
)
Mix.shell().info("")
Mix.shell().info("Please upload manually via Google Play Console:")
Mix.shell().info(
" #{cyan()}https://play.google.com/console/developers/#{gp_config[:package_name]}/tracks#{reset()}"
)
Mix.shell().info("")
Mix.shell().info("Or use the Play Console API client library for Elixir.")
end
defp cyan, do: IO.ANSI.cyan()
defp reset, do: IO.ANSI.reset()
end