distribute_cli 2.7.0
distribute_cli: ^2.7.0 copied to clipboard
Quick way to distribute your app to playstore and appstore
Distribute CLI v2.7.x #
Distribute CLI is a command-line tool to automate building and distributing Flutter applications for Android and iOS. It provides a unified workflow for building, publishing, and managing app distribution with a single YAML configuration file.
Table of Contents #
- Features
- Prerequisites
- Installation
- Getting Started
- Commands Overview
- AI Assistant
- Job Reliability Options
- Variable Substitution
- Notifications
- Exit Codes and CI
- Logging and Secrets
- Contributing
- License
Features #
- Build and distribute Flutter apps for Android and iOS
- Unified configuration with
distribution.yaml - Supports Firebase App Distribution, Fastlane, GitHub Releases, and App Store Connect
- Built-in git and pubspec variables — auto-versioning without a wrapper script
- Configuration validation and an environment check (
validate,doctor) - Plain-language commands via
distribute ai— any OpenAI-compatible endpoint or Claude - Dry runs, per-job retries and
continue-on-error - CI-friendly exit codes, a per-job run summary and a
--jsonreport - Artifact report with size and SHA-256 for every built binary
- Slack / Discord / Telegram / webhook notifications, including on failure
- Credentials are automatically masked in the terminal and in
distribution.log
Prerequisites #
- Flutter — required to build
- Firebase CLI — only for the
firebasepublisher - Fastlane — only for the
fastlanepublisher - macOS with Xcode — only for iOS builds and the
xcrunpublisher
The CLI itself is a pure Dart package and does not require the Flutter SDK to install.
Installation #
dart pub global activate distribute_cli
Verify the installation:
distribute --version
Getting Started #
1. Initialize Your Project #
distribute init
This command sets up the required directories and generates a starter distribution.yaml.
2. Configure distribution.yaml #
Edit the generated distribution.yaml to define your build and publish tasks, jobs, and variables.
Example
name: "Distribution CLI"
description: "A CLI tool to build and publish your application."
variables:
ANDROID_PACKAGE: "com.example.app"
IOS_PACKAGE: "com.example.app"
APPLE_ID: "${{APPLE_ID}}"
APPLE_APP_SPECIFIC_PASSWORD: "${{APPLE_APP_SPECIFIC_PASSWORD}}"
tasks:
- name: "Android Build and deploy"
key: "android"
workflows:
- "build"
- "publish"
description: "Build and deploy the Android application to playstore."
jobs:
- name: "Build Android"
key: "build"
description: "Build the Android application using Gradle."
package_name: "${{ANDROID_PACKAGE}}"
builder:
android:
binary-type: "aab"
split-per-abi: false
build-mode: "release"
generate-debug-symbols: true
- name: "Publish Android"
key: "publish"
description: "Publish the Android application to playstore as internal test track."
package_name: "${{ANDROID_PACKAGE}}"
publisher:
fastlane:
file-path: "distribution/android/output"
binary-type: "aab"
track: "production"
metadata-path: "distribution/android/metadata"
track-promote-to: "production"
json-key: "distribution/google-key.json"
skip-upload-images: true
skip-upload-screenshots: true
in-app-update-priority: 5
track-promote-release-status: "completed"
upload-debug-symbols: true
- name: "Android Build APKs"
key: "android_apk"
workflows:
- "build"
description: "Build split APKs, without publishing them."
jobs:
- name: "Build Android"
key: "build"
description: "Build the Android application using Gradle."
package_name: "${{ANDROID_PACKAGE}}"
builder:
android:
binary-type: "apk"
split-per-abi: true
build-mode: "release"
generate-debug-symbols: true
target-platform: "android-arm"
- name: "iOS Build and deploy"
key: "ios"
description: "Build and deploy the iOS application to app store."
jobs:
- name: "Build iOS"
key: "build"
description: "Build the iOS application using Xcode."
package_name: "${{IOS_PACKAGE}}"
builder:
ios:
binary-type: "ipa"
build-mode: "release"
pub: true
- name: "Publish iOS"
key: "publish"
description: "Publish the iOS application to app store."
package_name: "${{IOS_PACKAGE}}"
publisher:
xcrun:
file-path: "distribution/ios/output"
username: "${{APPLE_ID}}"
password: "${{APPLE_APP_SPECIFIC_PASSWORD}}"
binary-type: "ipa"
Commands Overview #
The Distribute CLI provides several commands to manage your app distribution process.
- distribute init
- distribute validate
- distribute doctor
- distribute build
- distribute publish
- distribute run
- distribute create
- distribute ai
distribute init #
Initializes the project and creates a starter configuration.
Example
distribute init
distribute build <platform> #
Builds the app for the specified platform (android or ios).
Example
distribute build android
distribute publish <publisher> #
Publishes the built app using the specified publisher (e.g., firebase, fastlane).
Example
distribute publish fastlane
distribute run #
Executes all tasks and jobs defined in distribution.yaml.
Example
distribute run
Options
| Option | Description |
|---|---|
-o, --operation <key> |
Run a single task (android) or a single job (android.build) |
-l, --list |
Print the available task and job keys, then exit |
--dry-run |
Resolve and print every command without executing it |
--fail-fast |
Stop the run as soon as a task fails |
--json |
Print a machine readable run report to stdout; the log moves to stderr |
--json-file <path> |
Write the same report to a file |
--no-notify |
Skip the configured notifications for this invocation |
# See what would happen, without building or uploading anything
distribute run --dry-run
# Run only the Android build job
distribute run -o android.build
# Capture a report for the pipeline; progress still shows on the terminal
distribute run --json > report.json
Jobs inside a task run in the order given by workflows (or in declaration order
when workflows is omitted). If a job fails, the remaining jobs of that task
are skipped so a publish step never uploads the artifact of a failed build.
At the end of the run a summary is printed:
✓ android.build 1m 24s
✗ android.publish 12s exit 1
✗ 1/2 job(s) succeeded · 1m 36s
distribute doctor #
Checks that the machine can actually build and publish: probes Flutter, git, Fastlane, the Firebase CLI, Xcode and the archiver, verifies the configuration and credentials, and prints what the built-in variables expand to.
distribute doctor
Missing optional tools are warnings — a project that only publishes to Firebase has no reason to install Fastlane. The command only fails on required problems.
distribute validate #
Parses distribution.yaml and reports problems without building or uploading
anything. Ideal as the first step of a CI pipeline or as a pre-commit hook.
distribute validate
distribute validate --strict # treat warnings as errors
It reports:
- Errors – missing keys, duplicate task/job keys, workflows pointing at a
job that does not exist, invalid
binary-type. These fail the command. - Warnings – unresolved
${{VAR}}placeholders and missing credential files such asjson-keyorexport-options-plist.
distribute create #
Adds tasks and jobs to distribution.yaml. Every subcommand takes -w for an
interactive wizard, or the same values as options for scripted use.
The wizard
distribute create task -w # a new task
distribute create job builder -w # a build job inside an existing task
distribute create job publisher -w # an upload job inside an existing task
The wizard picks the task from a numbered list, suggests a key derived from the name, offers the package name detected from the project, and shows what it is about to write before touching the file:
distribute 2.7.0 · create builder job · distribution.yaml
? Which task does this job belong to?
› 1) Android release android build, publish
2) iOS release ios no jobs yet
1-2 (1) 2
? Job name (Build) Build iOS
? Job key (build_ios) build
? Description Archive and sign
? Package name (com.acme.myapp)
? Which platforms should this job build?
› 1) android APK or AAB via Gradle
2) ios IPA via Xcode
1-2 or "all" (1) 2
task ios
name Build iOS
key build
description Archive and sign
package com.acme.myapp
platforms ios
reference ios.build
? Add this job to distribution.yaml? (Y/n)
✓ added ios.build to distribution.yaml
run it with `distribute run -o ios.build`
A key that is already taken is rejected at the question, not after the last one,
and answering n at the end writes nothing. A job key only has to be unique
within its own task, because references are task.job.
create rewrites distribution.yaml from scratch, which drops every comment
and reflows the formatting. It warns before doing so — as part of the review in
the wizard, and as a plain warning in the scripted form, which does not stop.
Without the wizard
distribute create task -n "My Task" -k my_task -d "Description here"
distribute create job builder -t my_task -n "Build Android" -k build -P android
distribute create job publisher -t my_task -n "Publish Android" -k publish -T fastlane
-P may be repeated (-P android -P ios), as may -T. Use -c to write to a
configuration file other than distribution.yaml.
A wizard reads answers from stdin, so it works with piped input as long as the input supplies every answer. When the input runs out — an empty stdin, a CI job, Ctrl-D — it stops with exit
64and tells you to pass the values as options instead. It also refuses to run under--quietor--silent, which would hide the questions while still waiting for answers.
Job Reliability Options #
Two optional keys can be set on any job:
jobs:
- name: "Publish to Firebase"
key: "publish_firebase"
package_name: "${{ANDROID_PACKAGE}}"
retry: 2 # 2 extra attempts on failure (3 total)
continue-on-error: true # a failure here does not fail the run
publisher:
firebase:
file-path: "distribution/android/output"
app-id: "${{FIREBASE_APP_ID}}"
binary-type: "aab"
retry– how many additional attempts a failing job gets. Useful for publishers, where a throttled store API or a flaky network is transient.continue-on-error– the task keeps going and the run can still succeed. The job is reported asFAILED (ignored)in the summary.
Variable Substitution #
Use ${{KEY}} in distribution.yaml to reference environment variables or custom variables defined in the variables section.
Use %{{COMMAND}} to substitute the output of a shell command, for example
%{{git rev-parse --short HEAD}} or %{{git log --pretty=format:"%s" -n 10}}.
Unknown placeholders are left as-is so they remain visible while debugging.
distribute validate flags any that survive substitution.
Built-in variables #
These are available without declaring anything. They are resolved lazily — a
config that never mentions GIT_SHA never spawns git — and are overridden by
anything you define in variables: or export as an environment variable.
| Variable | Value |
|---|---|
GIT_SHA / GIT_SHORT_SHA |
Current commit hash |
GIT_BRANCH |
Current branch name |
GIT_TAG |
Nearest tag |
GIT_COMMIT_COUNT |
git rev-list --count HEAD — a monotonic build number |
GIT_COMMIT_MESSAGE / GIT_AUTHOR |
Subject and author of HEAD |
PUBSPEC_NAME |
name from pubspec.yaml |
PUBSPEC_VERSION |
Full version, e.g. 1.2.3+45 |
PUBSPEC_VERSION_NAME |
Version without the build number, e.g. 1.2.3 |
PUBSPEC_BUILD_NUMBER |
Build number only, e.g. 45 |
ANDROID_APPLICATION_ID / IOS_BUNDLE_ID |
Detected from the platform files |
BUILD_DATE / BUILD_TIMESTAMP |
2026-08-15 / ISO-8601 |
HOST_OS |
macos, linux or windows |
Auto-versioning then needs no wrapper script:
builder:
android:
binary-type: "aab"
build-name: "${{PUBSPEC_VERSION_NAME}}"
build-number: "${{GIT_COMMIT_COUNT}}"
Run distribute doctor to see what they currently expand to.
AI Assistant #
Ask for what you want in plain language and let a model pick the command:
distribute ai "tolong build yang ios"
openai · gpt-4o-mini · permission: manual
› distribute run -o ios.build
Builds the iOS binary only.
? Run this command? (Y/n)
The assistant does not compose a shell string. It fills in a constrained
schema whose only commands are run, validate and doctor, and whose
operation keys come from a generated list of the keys that exist in your
distribution.yaml. It cannot invent a task, and it cannot reach anything a
typed command could not — the chosen command runs through the same runner.
Setup #
distribute ai --setup
An interactive wizard: pick the provider, endpoint, model, and permission mode.
Secrets are typed without echoing. It then asks whether to store the settings
machine-wide (~/.distribute/ai.json, written chmod 600) or in the project.
Providers #
| Provider | Works with |
|---|---|
openai |
OpenAI, OpenRouter, Groq, Together, DeepSeek, local Ollama — anything serving POST {base-url}/chat/completions |
anthropic |
Claude, via the Messages API |
Permission modes #
| Mode | Behaviour |
|---|---|
manual |
Show the command and ask before running it. Default. |
auto |
Run the chosen command straight away. |
plan |
Print the command; never execute. |
Override for one invocation with -p:
distribute ai "release android" -p plan
Configuration #
The ai: section of distribution.yaml overrides the machine-wide store:
ai:
provider: "openai" # openai | anthropic
base-url: "https://api.openai.com/v1"
model: "gpt-4o-mini"
permission: "manual" # manual | plan; see the note below about auto
api-key: "${{OPENAI_API_KEY}}"
max-tokens: 2048 # ceiling on the model's reply
permission: autois not honoured fromdistribution.yaml. That file travels with the repository, so letting it grant itself the right to build and publish would mean cloning a repository and runningdistribute aicould upload on your behalf. Set it in~/.distribute/ai.json, or pass-p auto. The project file may still restrict tomanualorplan.The endpoint is printed in the header of every run. If
base-urlcomes from the project file while the key comes from your machine,distribute aiwarns before sending anything.
Settings are layered, most specific first:
- Command line flags (
--ai-provider,--ai-url,--ai-model,--ai-key,-p) - The
ai:section ofdistribution.yaml ~/.distribute/ai.jsonOPENAI_API_KEY/ANTHROPIC_API_KEY
api-keyaccepts${{VAR}}, and the wizard writes a placeholder rather than the literal key by default —distribution.yamlis normally committed. A literal key works if you want one, and you'll get a warning when it is written. Whatever the source, the resolved key is masked in the terminal and the log.
Notifications #
An optional top level notifications: section posts the run summary once every
task has finished:
notifications:
- provider: slack # slack | discord | telegram | webhook
webhook-url: "${{SLACK_WEBHOOK}}"
on: always # always | success | failure
title: "Android release"
- provider: telegram
webhook-url: "https://api.telegram.org/bot${{TG_TOKEN}}/sendMessage"
chat-id: "${{TG_CHAT_ID}}"
on: failure
Notifications are a run-level concept rather than a job, which is what makes
on: failure work — a job placed after a failing build would never be reached.
Set message: to replace the generated summary with your own text (variables
are substituted). A delivery failure is reported as a warning and never changes
the exit code. Use distribute run --no-notify to skip them for one invocation.
Dart defines #
dart-defines takes a comma-separated list and is expanded into one
--dart-define flag per pair:
builder:
android:
binary-type: "aab"
dart-defines: "FLAVOR=prod,API_URL=https://api.example.com"
becomes flutter build aab --dart-define=FLAVOR=prod --dart-define=API_URL=https://api.example.com.
Exit Codes and CI #
distribute run returns a non-zero exit code when any job fails, so it can be
used directly in a pipeline:
# GitHub Actions
- run: dart pub global activate distribute_cli
- run: distribute validate --strict
- run: distribute run
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
| Code | Meaning |
|---|---|
0 |
Every job succeeded (or failed with continue-on-error) |
1 |
At least one job failed, or the configuration is invalid |
64 |
Usage error: unknown command or flag, a missing required option, no command at all, or a wizard with nowhere to read answers from |
127 |
A required tool (flutter, fastlane, firebase) was not found in PATH |
130 |
distribute ai proposed a command and you declined it |
127 is returned by the standalone distribute build and distribute publish
commands. distribute run aggregates its jobs, so a missing tool surfaces as
1 — the underlying 127 is still shown in the summary and recorded per job as
exit-code in the --json report.
Logging and Secrets #
The terminal and the log file are deliberately different. The terminal gets a compact, symbol based view meant to be read while it scrolls:
distribute 2.7.0 · distribution.yaml · 2 task(s), 3 job(s)
▸ Android release
Build and ship to the Play Store internal track.
› Build Android build
$ flutter build aab --release --build-name=3.1.4 --build-number=42 --pub
✓ done 1m 24s
app-release.aab 42.7 MB 9f2a1c0b3d4e
✓ android.build 1m 24s
✗ android.publish 12s exit 1
✗ 1/2 job(s) succeeded · 1m 36s
The summary uses the same key you pass to -o, so a failed row can be pasted
straight back: distribute run -o android.publish.
Verbosity #
| Flag | Terminal shows |
|---|---|
| (default) | failures, warnings, progress |
-v, --verbose |
the above, plus the resolved job config and raw tool output |
-q, --quiet |
failures only, flattened to one line each |
--silent |
nothing at all; only the exit code |
The log file always records everything, at every verbosity — including under
--silent. Each line is timestamped and level prefixed so it stays greppable:
12:13:51.140 INFO Build Android build
12:13:51.141 INFO $ flutter build aab --release --pub
12:13:51.827 ERROR Target file "lib/main.dart" not found.
Use --log-file <path> to change its location, --no-color to disable ANSI
colors (also disabled automatically when the output is not a terminal or when
NO_COLOR is set), and set DISTRIBUTE_ASCII=1 to swap the unicode glyphs for
an ASCII fallback on legacy consoles.
Credentials are masked with *** in both the terminal and the log file. This
covers the values themselves, so a token echoed back by Fastlane or Firebase is
redacted too. Any option whose name contains token, password, passwd,
secret, credential, api-key, api-issuer, key-data or ai-key is
masked, in both its long and short forms — publish xcrun -p <password> and
publish fastlane -J <json> included.
distribution.logstill contains the full output of the tools it drives.distribute initadds it to.gitignorefor you - keep it out of version control.
Contributing #
Contributions are welcome! Please open issues or pull requests to help improve Distribute CLI.
License #
MIT License. See the LICENSE file for details.