distribute_cli 2.7.1
distribute_cli: ^2.7.1 copied to clipboard
Quick way to distribute your app to playstore and appstore
2.7.1 #
Added #
distribute changelog— release notes from the git history. The default range is everything since the previous tag, which is what one release actually contains;git describealone would return HEAD's own tag and produce an empty range for exactly the release being cut. Conventional commit prefixes are used for grouping when they are there, and ignored when they are not.--aihands the result to the configured model for an editorial pass, with an instruction that forbids inventing or dropping a change.${{CHANGELOG}},${{CHANGELOG_PLAIN}}and${{CHANGELOG_RANGE}}— the same notes as variables, so a publisher can fillrelease-notesorrelease-bodywithout a wrapper script. The history is read once per run and shared by every job that references it.- A
changelog:section indistribution.yamlconfiguring the range, the format, grouping, hashes, merges and the optional AI pass. - A spinner while a build or upload runs. Those stages produce nothing on
screen below
--verboseand can last minutes, so the CLI looked hung. One line, rewritten in place, carrying the step and its elapsed time. It is drawn only on a real terminal and never under--quiet,--silentor--verbose, so piped output, CI logs and the log file are untouched — and any log line printed while it runs erases it first, so the two never share a row. The line is truncated to the pane width: a wrapped spinner cannot be erased, because a carriage return only returns to the start of the last row.
Fixed #
distribute changelograngit log <rev>without a--separator, so a revision that is also a path — areleasebranch beside arelease/directory, or a tracked file namedHEAD— made git refuse with "ambiguous argument". That failure was swallowed and reported as "no commits" with exit0. Every git failure is now surfaced with git's own message.- The previous tag was chosen by creation date rather than by ancestry, so a tag cut on a side branch, a retroactively added tag, or two tags created in the same second could make a release re-list commits that had already shipped while dropping ones that had not. The boundary is now the nearest tagged ancestor of the range's end.
${{CHANGELOG}}ignoredchangelog: format:and always rendered markdown, andchangelog: ai:andprompt:never reached it at all — both documented as working.${{CHANGELOG_PLAIN}}remains flat whatever the format says.distribute changelog --aisent the API key as the literal${{VAR}}placeholder instead of resolving it, and printed its progress line onto stdout where it landed in the middle of the redirected notes.- An empty range or a shallow clone now says so, and an empty range points out
that whatever
-onames still holds the previous run's notes. - An explicitly typed
--configthat does not exist is an error rather than being silently ignored.
2.7.0 #
Everything from 2.4.0 onwards is one upgrade for anyone coming from 2.3.5. The behaviour changes worth knowing about before you update:
distribute run --jsonis a flag rather than an option (use--json-filefor a path);permission: autois no longer honoured fromdistribution.yaml; a%{{command}}substitution that fails now stops the job instead of silently substituting its error text; and a publisher refuses an artifact whose build mode does not match, rather than promoting whatever it finds.
Added #
distribute ai "<request>"— ask a model to pick the right command:distribute ai "tolong build yang ios"→distribute run -o ios.build. The assistant never composes a shell string. It fills in a constrained schema whose only commands arerun,validateanddoctor, and whose operation keys come from a generated list of the keys that exist in this project — so it cannot invent a task, and cannot reach anything a typed command could not. Chosen commands run through the sameCommandRunneras a hand-typed one.- Two provider backends behind one interface.
AiProviderhas an OpenAI-compatible adapter (OpenAI, OpenRouter, Groq, Together, DeepSeek, a local Ollama — anything servingPOST /chat/completions) and an Anthropic Messages API adapter. They differ in more than a URL — bearer token vsx-api-key, nestedfunctionvsinput_schema, a JSON-string argument blob vs a parsed map, and Anthropic's refusals arriving as a successful HTTP 200 that has to be checked before reading any content. - Three permission modes, set by
permission:or-p:manual(show the command, confirm before running — the default),auto(run it straight away),plan(print it, never run). distribute ai --setup— an interactive wizard with numbered menus and non-echoing secret entry. It asks whether to store the settings machine-wide (~/.distribute/ai.json, writtenchmod 600) or in the project.ai:section indistribution.yaml, which overrides the machine-wide store. Layering, most specific first: CLI flags →distribution.yaml→ machine-wide store → environment (OPENAI_API_KEY/ANTHROPIC_API_KEY).api-keyaccepts${{VAR}}, and the wizard defaults to writing a placeholder rather than the literal key, sincedistribution.yamlis normally committed. Whatever the source, the resolved key is registered with the logger and masked everywhere.
Changed #
- The
createwizards were rebuilt. They used to dump a list of tasks and ask you to retype a key from it, reject a duplicate only after every question had been answered, and write the file without showing you what it was about to add. Now the task is picked from a numbered list that shows the jobs it already has, the key is suggested from the name and validated at the question, the detected package name is offered as a default instead of being forced, and platforms and publishers are multi-select (1,3,1-2orall). The result is shown as a review block and confirmed before anything is written; answering no leaves the file untouched. distribute createaccepts-c/--configlike every other command. It writes to the configuration file, so needing the global--configfor it was a trap.distribute createsilently deleted every comment indistribution.yaml. The document is re-encoded from scratch, so adding one task threw away every#line and all the blank-line grouping in a file that is hand-maintained and committed. It now says so: the wizard shows the warning as part of the review, before the single confirm, and the scripted form warns without blocking, since automation asked for the change explicitly.
Fixed #
- A failing
%{{command}}substituted its own error text into the build.build-name: "%{{git describe}}"outside a git repository produced--build-name=fatal: not a git repository ..., which split into extra arguments on the flutter command line — and the run reported success. A missing binary substituted an empty string just as quietly. Both now stop the job and name the command that failed. - A variable declared with no value blanked the real environment variable.
FIREBASE_TOKEN:with nothing after it overwrote the exported credential with an empty string, resolved the placeholder to nothing, and leftvalidatereporting no problems. A key with no value is now a declaration, not an assignment. - The publisher promoted whatever it found, including a debug APK. With an
empty output directory the scoring made a lone
app-debug.apkthe best candidate and uploaded it. An artifact labelled with a different build mode is now refused outright rather than ranked lower. - Copying an artifact onto itself deleted it. Pointing
output:at the directory the build already writes to made source and target the same file; the target was deleted and then copied "from", and the run only reported a failed copy. xcrun --file-pathwas declared, marked mandatory, and never read. The publisher uploaded whatever sat indistribution/ios/output, so passing an explicit path silently shipped a different, usually stale, IPA.- The debug-symbol archive could come from the wrong build variant. Matching
was by substring and ties were broken by name length, so flavor
devalso matcheddevQaRelease, and a leftoverdebugReleasebeat an exactrelease. Variants are matched exactly now, with the loose match kept as a fallback ordered by modification time. - An unset value reached publishers as the literal string
"null". The round trip through variable substitution stringified everything, so an omittedtarget-commitishwas sent to the GitHub API as a branch namednull. distribute buildprinted nothing at all when it failed. flutter's own diagnostics go to the verbose channel, and the standalone build commands return straight to the process exit code, so the console stayed empty.distribute run --dry-runfailed for any configuration with a GitHub publisher, because it insisted on an artifact the rehearsal had not built.run -o task.jobcould exit 0 having run nothing. A job present injobsbut absent from the task'sworkflowswas re-filtered out after being selected. An explicit job key now overrides the workflow ordering,--listmarks jobs a whole-task run would skip, and a run that executed nothing is a failure rather than a silent success.- A malformed
pubspec.yamltook the whole CLI down with exit 255. Project metadata is read before the error handling was installed; it is optional, so a failure there now warns and continues. - Mistyped YAML no longer surfaces as a raw Dart type error with no location.
workflows: j,key: 7,builder.android: apk, a non-textwebhook-urland the fastlaneversion-code/rollout/version-codes-to-retainkeys all produce a message naming the file and the key.continue-on-error: "true"andversion-code: "42"— the quoted forms editors produce — are understood now rather than rejected. distribute doctorreported a tool that is not installed as "found but exited with code 127".--json-file -produced no report at all.- The failure hint no longer points at a log file when
--log-file ""disabled file logging. - Colours are decided from the stream the human output actually goes to, so
--jsonpiped to a parser keeps its colours on the terminal. validatewarns when a job appears twice inworkflows, and when the inertoutput:key is set.- The
buildandpublishsub-command descriptions were three lines of prose each; they are one line now, andexample/example.mdreproduces the real help output verbatim. - The publisher wizard's tool question read stdin directly, so it was the one prompt that neither validated its answer nor stopped at end-of-input.
distribute create job -wagainst a configuration with no tasks asked every question before failing; it now says to create a task first and stops.- The README documented
distribute create task builder -w, which is not a command. - A wizard started without a terminal never stopped.
stdin.readLineSyncreturns null once input ends and keeps returning null, sodistribute create task -win CI recursed until the stack overflowed — 14,000 frames of trace instead of an error — and the newer prompts spun on the same empty answer forever. Every prompt now treats end-of-input as an abort and exits64with a message saying to pass the values as options.confirmandselectabort too rather than silently taking their default, which would have answered "yes" to questions like write the API key intodistribution.yaml? - Asking for a secret no longer fails outright where terminal echo cannot be switched off. Hiding the input is attempted first and, if the terminal refuses, the prompt warns that what is typed will be visible instead of crashing.
distribute run --jsonswallowed the next flag as its value.--jsontook a path, sodistribute run --json --silentwrote the report to a file literally named--silentand the run was never silenced.--jsonis now a flag that prints the report to stdout, and--json-file <path>writes it to disk. When the report goes to stdout the human readable log moves to stderr, sodistribute run --json > report.jsonproduces a parseable file while progress still shows on the terminal.- A mistyped configuration key was silently ignored.
binary-typ: aabparses fine and quietly builds an APK — a wrong artifact that only surfaces once it reaches a store.distribute validatenow reports unknown keys at every level with a nearest-match suggestion. They stay warnings, not errors, so a configuration written for a newer CLI still runs on an older one. - A configuration with an
arguments:key crashed. The field was typed as a map of parsed objects while being populated with raw YAML maps, so any config declaring it died with aTypeErrorbefore anything ran.validatealso points out that nothing reads the key yet. distributewith no command exited0. A script that reached this by mistake read it as success; it is a usage error and now exits64.- A missing mandatory option exited
1, indistinguishable from a failed build. Theargspackage reports it as anArgumentErrorrather than aUsageException, so it looked like a crash. It now exits64like every other usage error. - The main example in the README declared a
workflowsentry that referenced no job, so the very first configuration a reader copies failed to validate. - Pointing
--configat a directory reported it as "not found", which sends the reader looking for the wrong problem. It now says what is actually wrong. - Help text corrections:
create taskno longer describes itself as creating "a task or job", andrun --operationdocuments thetask.jobform it actually accepts. - The global
--configoption never worked. Every sub-command declares its own--configwith adistribution.yamldefault, and that default shadowed the global option on every invocation — sodistribute --config custom.yaml runsilently readdistribution.yaml. A sub-command's flag now wins only when it was actually typed. - A command chosen by the assistant is run with the configuration file the
assistant was pointed at, instead of falling back to
distribution.yaml. distribute ai --setupwarns before rewriting adistribution.yamlthat contains comments — re-encoding the document drops them — and offers the YAML to paste in by hand instead.- An invalid
provider:orpermission:in theai:section now names every place the value could have come from, rather than reporting a bareInvalid argument(s). - The Anthropic adapter no longer sends
output_config.effort. It suits the task, but older Claude models reject it outright and the model is the user's choice. - Writing the machine-wide AI store no longer fails when
chmodis unavailable.
Security #
- Two credentials reached the log file in the clear. The run header masks
credential options by name, but only matched their long spellings, so
publish xcrun -p <app-specific-password>andpublish fastlane -J '<service-account-json>'were written out verbatim. Short forms are now masked too, per sub-command —-pis a password underxcrunbut the package name undercreate job, so the letter alone is not enough to decide. A test walks the real argument parsers and fails if a credential option grows an abbreviation that is not covered. permission: autois no longer honoured fromdistribution.yaml. That file travels with the repository, so a cloned project could pre-authorise its own builds and uploads with no confirmation. The project file may still restrict tomanualorplan; grantingautonow requires-p autoor the machine-wide store. The demotion is announced, with the two ways to enable it.- The endpoint is printed, and a redirected key is flagged. A project file
setting
base-urlwhile the API key came from the machine-wide store would send that key wherever the repository pointed, and the URL was never shown. It is now part of the header, and that combination warns. - Project data is fenced in the model prompt. Task and job names and descriptions were interpolated raw, after the tool's own rules, so a task description could append a section that read as new instructions and re-target the operation the assistant chose. Values are now flattened, capped and wrapped in a delimiter the prompt tells the model to treat as data.
- Model-supplied text can no longer repaint the terminal. Escape sequences were only stripped when colours were off, so a reply could erase and rewrite the confirmation line the user was about to answer. Everything that comes back over the network is stripped and truncated before it is printed.
- A reply truncated by the token limit is refused. Both adapters accepted a
tool_useblock cut short bymax_tokensas if it were complete — a partial{"command":"run"}becomes a full-configuration run. - Overriding
--ai-providerno longer leaves the previous provider's key, model and endpoint in place, which produced a configuration nobody asked for and a 404 blaming settings the user never touched.
2.6.0 #
Changed #
- Redesigned the terminal output. The
[INFO]/[SUCCESS]prefixes and=======banners are gone, replaced by a symbol based, indented view (▸task,›job,✓/✗/!outcomes) with de-emphasised secondary text. Only what changes state is coloured, so failures actually stand out. - The terminal and the log file no longer share a format. The log file now
gets one timestamped, level prefixed line per message
(
12:13:51.140 ERROR ...) and records every level regardless of the terminal verbosity - including under--silent. - A job now prints the command it is about to run instead of dumping its whole
configuration. The full config moved behind
--verbose. - The run summary is keyed by the operation ref (
android.publish), so a failed row can be pasted straight intodistribute run -o <ref>. - Failures are reported once, by the runner, instead of by both the runner and the builder.
Fixed #
- Every flavored Android release build failed. Debug symbols were looked for
at the hardcoded
merged_native_libs/release/mergeReleaseNativeLibs/, but Gradle names that directory after the variant -prodRelease/mergeProdReleaseNativeLibs- and older AGP versions omit the task subdirectory entirely. The directory was therefore never found, and a missing directory returned a non-zero exit code, so a build that had already produced and copied its artifact was reported as failed. The layout is now discovered by searching, and symbol problems are warnings: they can no longer fail a build whose binary is already on disk.generate-debug-symbolsdefaults totrue, so this affected every flavored release. - Debug symbols ignored the job's
outputand were always written todistribution/android/output, while the fastlane publisher looks fordebug_symbols.zipnext to the binary - so a custom output silently lost them. - A stale binary from a previous build was copied into the output directory.
After a debug build followed by a release build,
app-debug.apkwas copied next toapp-release.apk; the GitHub publisher uploads every matching file in that directory, so a debug binary could ship in a release. Only the best-matching tier is copied now (a--split-per-abiset still copies in full), and artifacts left by an earlier build are pruned. - The iOS bundle identifier could resolve to the unit test target. The first
PRODUCT_BUNDLE_IDENTIFIERinproject.pbxprojwas taken verbatim, so a reordered project yielded...RunnerTests, quoted values kept their quotes, and an unresolved$(APP_ID)was returned as-is - each of which becomes a wrong--bundle-idat upload time. CompressFiles.compressignored its second parameter and always wrotedebug_symbols.zip; it also failed on Windows when the archive already existed, and threw instead of reporting a missing archiver.create job builder/create job publisherwithout--platform/--toolsreported a raw exception that never named the missing option.- The notifier only bounded its receive timeout, so an unroutable webhook host could hold the run open for the OS-level TCP timeout after all work was done.
- Streamed tool output broke the log format. A child process delivers its output as arbitrary chunks, not lines, so every line after the first in a chunk was written without a timestamp, a level or an indent. Each line is now emitted as its own record.
- ANSI escape codes from child processes leaked into
distribution.log, leaving it unreadable in an editor and awkward to grep. They are now stripped from the file (and from the terminal when colors are disabled). - A chunk ending in a newline no longer appends a blank log record.
--log-file <dir>deleted the directory and everything in it. Anything that is not a regular file is now refused.- An empty
--log-filedisables file logging, as the help text already claimed. --silentis now absolute: it can no longer be overridden per logger instance.
Added #
-q, --quiet- print failures only, flattened to one line each.--silent- print nothing; the exit code is the only signal. The log file is still written in full.- The log file opens with a run header carrying the version, the ISO timestamp, the working directory and the invocation - with credential options masked, since the header is written before any job registers its secrets.
DISTRIBUTE_ASCII=1swaps the unicode glyphs for an ASCII fallback on legacy consoles.
2.5.0 #
Fixed #
distribute publish fastlanecrashed withLateInitializationError. The standalone command dereferenced the enclosing job, which only exists when the publisher comes fromdistribution.yaml. The package name is now resolved from--package-name, then the job, then the detectedapplicationId.distribute publish firebasecrashed before doing anything, because it read an option namedcli-tokenthat the parser never declared. It now reads--token, and also honours the mandatory--file-pathit used to ignore.xcrun'svalidate-appsent-vto altool, which means verbose, not validate — so the archive was never actually validated. Validation now runs as its own--validate-apppass before the upload, and aborts the upload on failure.fastlane/firebasetool probes never drained stderr. A tool writing more than the OS pipe buffer (fastlane actionsdoes) would block forever. Both streams are now drained on every spawned process.xcrun'supload-packageoption was accepted but silently ignored.distribute createcrashed on a config without avariables:section, and on an empty YAML file.distribute initno longer callsexit()from inside a helper, so the exit code and the log file stay consistent.
Added #
- Built-in variables —
${{GIT_SHA}},${{GIT_SHORT_SHA}},${{GIT_BRANCH}},${{GIT_TAG}},${{GIT_COMMIT_COUNT}},${{GIT_COMMIT_MESSAGE}},${{GIT_AUTHOR}},${{PUBSPEC_VERSION}},${{PUBSPEC_VERSION_NAME}},${{PUBSPEC_BUILD_NUMBER}},${{PUBSPEC_NAME}},${{ANDROID_APPLICATION_ID}},${{IOS_BUNDLE_ID}},${{BUILD_DATE}},${{BUILD_TIMESTAMP}},${{HOST_OS}}. Auto-versioning no longer needs a wrapper script:build-number: "${{GIT_COMMIT_COUNT}}". Resolved lazily, and overridable. distribute doctor— probes every external tool, the configuration and the credentials, and prints what the built-in variables expand to.- Run notifications — a top level
notifications:section posts the run summary to Slack, Discord, Telegram or a generic webhook, withon: always|success|failure. Modelled at run level soon: failureactually fires. Delivery problems never change the exit code. - Artifact report — every built binary is listed with its size and SHA-256 in the run summary.
distribute run --json— machine readable run report on stdout, or--json-file <path>to write it to disk.distribute run --no-notifyto suppress notifications for one invocation.
2.4.0 #
Fixed #
distribute runnow exits with a non-zero code when a job fails. Previously every run exited0, so CI pipelines silently reported broken builds as green.dart-definesproduced an invalid--dart-definesflag. Flutter only accepts a repeated--dart-define=KEY=VALUE, so any configuration using dart defines failed with exit code 64. Values are now expanded into one flag per pair.- GitHub release assets were uploaded as
multipart/form-data. The GitHub API expects the raw bytes, so every uploaded APK/AAB/IPA was corrupted. Assets are now streamed as the raw request body with a correct content type. - A publish job no longer runs after its build job failed, which used to upload a stale artifact from a previous build.
- Credentials (Apple password, GitHub token, Firebase CI token, Google service
account JSON data) are no longer written to
distribution.logor the terminal. --obfuscatenow auto-supplies the--split-debug-infodirectory that Flutter requires, instead of failing the build with a usage error.- GitHub releases are matched by name and tag, and are no longer created as drafts by default. The previous "fall back to the latest release" behaviour could attach a build to an unrelated release and has been removed.
- A missing
variables:section no longer crashes with atype 'Null' is not a subtype of type 'Map'error; the section is optional. - Fastlane no longer appends
debug_symbols.zipto the caller'smapping-pathslist on repeated reads. - Debug symbols are copied into the output directory even when it does not exist yet.
- Errors are written to stderr, and colors are disabled automatically when the
output is piped or
NO_COLORis set.
Added #
distribute validate- parses the configuration, reports unresolved${{VAR}}placeholders and missing credential files. Use--strictto fail on warnings.distribute run --dry-run- prints every resolved command without executing it.distribute run --list- lists the available task and job keys.distribute run --fail-fast- stops at the first failing task.- Per-job
continue-on-error: trueandretry: <n>indistribution.yaml. - A run summary with per-job status, duration and attempt count.
- Global
--version,--log-fileand--no-colorflags. obfuscateandsplit-debug-infoare now supported for iOS builds too.- GitHub publisher:
binary-type,draft,prereleaseandtarget-commitish. distribute initappends the generated artifacts to.gitignore.- Test suite covering config validation, argument building, variables and logging.
Changed #
- The package no longer depends on the Flutter SDK. It never used a Flutter
API, and the dependency made the documented
dart pub global activate distribute_clifail. Installing is now a plain Dart install. - Configuration errors name the offending key and its position, for example
tasks[0].jobs[1] ('Publish') in 'distribution.yaml' is invalid: .... - Duplicate task/job keys and workflow entries that reference a missing job are rejected at parse time rather than mid-run.
- When several artifacts match, the most recently modified one is published.
- Removed the unusable
Job.fromJsonfactory, which always threw because it never constructed a builder or publisher.
2.3.5 #
- Fix dart-define-from-file
- Upgrade packages
- Flavor app supports
2.3.4 #
- Fix typo
- Read applicationId from gradleKts
2.3.3 #
- Distribute debug symbols and path separator fixes
2.3.2 #
- Windows powershall validate fastlane fixes
2.3.1 #
- Enhance documentations
2.3.0+1 #
- Windows powershell support
- Enhance logger to provide more detailed information
- Enhance code and functionality for better performance
2.2.0 #
- Add
wizardcommand to create a newdistribution.yamlfile interactively - Enhance logger to provide more detailed information
- Enhance code and functionality for better performance
2.1.2 #
- Add command substitution for
distribution.yamlvariables%{{COMMAND}} - Optimize the code
2.1.1 #
- Fix
distribute run -onot working on specific jobs - Fix wrong output for ios built
2.1.0 #
- Add output on build and publish
- Output on job's arguments
- Once build finished or publish started, binary will be copyed to the output provided
- Add
Builder.generate-debug-symbolsandPublisher.fastlane.upload-debug-symbolsfor Android - Change
distribution.yamlpatterns - Add
workflowsonTaskto sort the jobs - Add Github as a publisher
- Change publisher subcommand to directly use the publisher name
- Add
createcommand to create a new task or job on distirbution.yaml - Update documentation
- Update examples
2.0.1 #
- Add
exportOptionsPlistandexportMethodto iOS build - Solving pub.dev scores
2.0.0 #
- Refactor the code
- Refactor how to use the package
- Distribute.yaml is now used to configure the package
- Added support for
${{KEY}}to reference environment variables or custom variables fromdistribution.yaml - Updated
README.md:- Added detailed explanation for using
${{KEY}}indistribution.yaml - Enhanced examples for
distribution.yamlwith variables and tasks - Improved documentation for commands (
init,build,publish,run) - Added a section for variable substitution in
distribution.yaml - Clarified usage of environment variables and custom variables
- Added detailed explanation for using
1.0.4+4 #
- Fix Android and iOS build error logs
1.0.4+3 #
- Update code docs
1.0.4+2 #
- Update changelogs
- Update readme.md
1.0.4+1 #
- Enhance distribution.log and terminal logs
- Run firebase and fastlane same time for quick process
- Publish git logs on fastlane
- Update readme.md and add example.md
1.0.3+1 #
- Create distribution.log to record the logs
- Start distribute once task finished
1.0.2+1 #
- Builder and Publisher will be more tolerant for tools that doesn't exists
- Firebase changelogs included
- Optimize code and error appearance
1.0.1+1 #
- Add
fastlane_trackandfastlane_promote_track_to - Solve
distribute build -p - More environment on init
1.0.0+2 #
- Add documentation on the code
- Solve pub.dev scores
1.0.0+1 #
- Add distribute executable
- Fix args not working on subCommands
- Enhance command to be more clearer
1.0.0 #
- First release, look readme.md for details