flutter_distribute_manager 2.1.1
flutter_distribute_manager: ^2.1.1 copied to clipboard
Ship Flutter apps to QA in one command: build the Android APK and iOS IPA, then upload to Google Drive (via rclone) and Diawi with shareable install links.
Changelog #
2.1.0 #
Added #
-
resetcommand — a clean way to remove saved configuration and start over.flutter_distribute_manager resetdeletes the machine config (rclone remote, Drive folder, Diawi token, notify webhook) and the current project's.flutter_distribute_manager.json, while leaving Google Drive connected (the rclone remote and its OAuth token stay in place, so no re-sign-in).reset --alladditionally deletes the rclone remote to fully disconnect Google Drive. The command prints exactly what it will remove and, in an interactive terminal, asks you to typeresetto confirm; pass--yes(-y) to skip the prompt in CI, and--app-dir(-d) to target a project other than the current directory.Previously the only reset was the "Reset all configuration" option inside
config, which cleared the machine config only — it never removed the per-project file or offered to disconnect Drive. That narrower option is still available insideconfig.
Changed #
- Tightened the pub.dev
descriptionfor clarity and length.
2.0.0 #
A full audit of the build, distribution and runtime layers. Several defects here meant the tool shipped the wrong artifact, or hung, rather than failing — the worst kind of bug for a release tool. Test suite grew from 100 to 361.
Why this is a major release. The behavioural changes are not backwards compatible, even though almost all of them are corrections:
- The tool now refuses to ship artifacts it previously shipped. Stale IPAs
left by a failed export, and debug APKs sitting in
flutter-apk/, were being uploaded to QA; both are now rejected. A pipeline that (unknowingly) depended on one of those files being produced will now fail instead. --export-method release-testingwas the default and was never a value Flutter accepts, so every iOS build without a Team ID failed at the usage check. The default now works, which means iOS builds that used to fail immediately will now actually run.- Exit codes are more specific: a missing binary or bad
--flutter-pathis now 78 rather than 255, "built but no artifact found" is 70 rather than 1, and--versionin a non-global position no longer exits 0. - A filesystem error while taking the build lock no longer reports "another build is already running" (exit 69); it warns and builds unlocked.
rcloneis now invoked with--retries 1, since its own retries were multiplying with the tool's retry policy for up to nine full uploads.ProcessRunner.capture/streamandDriveUploadergained members; anything implementing those interfaces must be updated.
Shipped the wrong artifact #
- iOS: a stale IPA could be uploaded to QA.
flutter build ipaexits 0 when the archive succeeds butxcodebuild -exportArchivefails, and it never cleansbuild/ios/ipa/. After a signing failure the tool picked up a previous IPA, stamped it with the current timestamp and shipped it. Builds now record a start time and reject anything older; export failure is detected in Flutter's output. - Android: debug APKs were shipped alongside release ones. The Flutter
Gradle plugin copies every variant into one flat
flutter-apk/directory and never deletes anything, so any developer who had runflutter runhad anapp-debug.apksitting there. Artifact discovery is now variant- and flavor-aware, and rejects stale files. --flavorwas passed to the build but ignored when locating artifacts, so a multi-flavor project could ship the wrong flavor.- Release builds silently signed with the debug keystore — the Flutter template's default — now warned about before the build starts.
iOS builds that could never succeed #
--export-method release-testingis not a value Flutter accepts (app-store,ad-hoc,development,enterprise), and it was the default. Every iOS build without a Team ID failed with a usage error.- Hardcoded
signingStyle: automaticbroke every manual-signing project. - ExportOptions.plist: XML is now escaped,
destinationis set, App Store exports setuploadSymbolsandmanageAppVersionAndBuildNumber: false, and the obsoletecompileBitcodekey is gone.enterprisewas missing from the export-method model entirely. - Xcode presence was checked with
which xcodebuild, which succeeds with Command Line Tools only; the build then died minutes later.
Hangs #
- The child process's stdin was never closed, so anything that reads it —
sudo, CocoaPods, a Gradle prompt, an SDK licence question — hung forever with no timeout. - A daemon inheriting the pipe blocked the run after a successful build. Gradle and adb keep stdout open, so waiting for end-of-stream meant waiting for the daemon to exit. Now a bounded post-exit drain.
- A single non-UTF-8 byte killed the CLI mid-build with an unhandled
FormatExceptionand exit 255 — acaféin a path was enough. Decoding is now lenient.
Distribution #
- The Diawi API token leaked into logs and stack traces (the poll URI
carries it, and
ClientException.toString()includes the URI). Redacted. The notification webhook URL leaked the same way. - Discord notifications always failed — the payload used Slack's
textfield; Discord requirescontent. Slack, Discord and Teams are now detected and formatted per dialect. - A 401 or 413 re-uploaded the whole IPA three times. 4xx is no longer
retried (except 408/425/429), and
Retry-Afteris honoured. rclone's own--retries 3multiplied with the retry policy for up to 9 full uploads; now pinned to 1. - The Drive progress bar never worked and destroyed error messages. It
required a
Transferred:prefix that--stats-one-linedoes not emit, so every progress line fell into the 20-line error tail and evicted the real error within ten seconds. - No HTTP timeouts on Diawi — a stalled connection hung the CLI after the build. Poll bound raised from 3 minutes, and a timeout now names the job id instead of stranding an uploaded build.
- A failed
rclone linkwas silent; QA got a summary with no link and no reason. AddedDriveUploader.preflight()to validate the remote and its token before a build is paid for.
Runtime and config #
- A filesystem error was reported as "another build is already running",
which no amount of waiting fixes. Lock paths are now canonicalized, so
-d appand-d app/no longer take separate locks on one project. - One unknown config field discarded the entire config, silently losing the saved Diawi token and rclone remote.
ProjectConfig.save()crashed a finished run on a read-only checkout, after the uploads, so the user never saw their links.- Trailing YAML comments were parsed into the app name and version, landing in artifact filenames and Drive paths.
- The project is now found by walking up from a subdirectory.
--versionanywhere in the arguments exited 0, so a CI step with a mistyped flag passed silently.- The saved Diawi token was echoed to the terminal by
configandinit; the config file is nowchmod 600. - FVM detection understood only the
.fvm/flutter_sdklayout.
1.1.1 #
Bug fixes from an adversarial review of 1.1.0. No API or flag changes.
- Build lock moved out of
build/— it lived inbuild/.fdm.lock, whichflutter cleandeletes, allowing a second concurrent build to slip in. The lock now lives in a stable OS-temp path keyed by the project, survives a clean, and no longer unlinks on release (closing an unlink race) or NUL-pads its pid stamp. flutter cleanfailures are now surfaced instead of being swallowed (the auto-retry path no longer silently rebuilds against a dirty cache).- More accurate exit codes — "no artifacts" and artifact-staging I/O
failures report build (70); invalid
--retryreports usage (64). - fvm version reporting now resolves from the project directory, and a warning is shown when an fvm-pinned project falls back to a global Flutter.
gradleDeclaresAbiFiltersno longer false-positives onabiFiltersinside block/inline comments.- Build-failure signing hint no longer fires on benign debug-keystore log lines.
- Bundled global flags (e.g.
-vq doctor) are now routed correctly instead of being treated as a positional tobuild.
1.1.0 #
Real-world hardening from testing on a large, fvm-pinned production app. All existing flags remain backward-compatible.
Added #
- fvm support — the tool now detects fvm-pinned projects (
.fvmrc,.fvm/,.fvm/fvm_config.json) and invokes the project's pinned Flutter viafvm flutter …, so version-pinned apps build correctly with no manualPATHexport. New--flutter-pathoverrides the executable explicitly. --clean— runflutter cleanbefore building. The tool also auto-cleans and retries once when it detects a corrupted Kotlin cache.- Concurrent-build lock — a per-project OS lock (
build/.fdm.lock) refuses a second simultaneous build, preventing Gradle/Kotlin cache corruption. --flavor— passed through toflutter build.--quiet/-q— silence non-essential output (build chatter, detail lines); errors, warnings and the summary still show.- Distinct exit codes for CI: build failure (
70), upload failure (69), missing configuration (78).
Improved #
--split-per-abi↔ndk.abiFiltersconflict — when an app declaresabiFilters, the tool builds a single APK by default (with a notice) instead of failing after a multi-minute build. An explicit--split-per-abi/--no-split-per-abistill wins.- Actionable failure hints — known Gradle/Kotlin/signing/SDK/v1-embedding failures now print a one-line fix instead of just raw output.
doctor— fvm-aware Flutter check (green when resolvable via fvm), verifies the configured rclone remote exists, and reports resolved Flutter/Java versions.init— empty Diawi-token / webhook answers now skip with a clear notice.- Pre-build summary shows the resolved Flutter toolchain (and fvm), plus a
reminder that
--environmentonly labels artifacts and sets--dart-define=ENV=….
1.0.1 #
Documentation and metadata polish (no functional changes).
- Reworked README and example walkthrough for clarity.
- Tightened the package description.
- Updated LICENSE copyright holder.
1.0.0 #
Initial release.
Build & distribute #
build(default command): build release Android APK (--split-per-abi) and iOS IPA for one or both platforms.- Environment tagging (DEV / UAT / PROD) with timestamped artifact names, also
passed to the build as
--dart-define=ENV=…. - Google Drive uploads via rclone with an automatic
App/Year/Month/Environmentfolder structure and case-insensitive folder matching to avoid duplicates. - Diawi integration for iOS one-tap install links.
Professional touches #
- Resilient uploads — Drive and Diawi transfers retry with exponential
backoff (
--retry). - Live progress bar — rclone transfers render as a branded
[████░░░] 64% 42 MiB / 66 MiB 4.2 MiB/s ETA 6smeter. - Per-step timing — build/upload durations reported in the summary.
--json— machine-readable result on stdout for CI pipelines.--dry-run— preview the full plan without building or uploading.- Team notifications — post a build summary to a Slack/Discord/generic
webhook (
--notify-url). - Box-drawing UI and a summary table for a polished terminal experience.
Setup & diagnostics #
init: one-time setup wizard (install rclone, authenticate Drive, save Diawi token and notification webhook).doctor: environment & connectivity health checks, including a macOS Gatekeeper section that lists quarantined binaries and the exactxattrcommand to clear them.config: interactive configuration editor with full reset.
Everything else #
- Interactive prompts plus a fully non-interactive mode (
--yes) for CI/CD. - Per-machine and per-project configuration files.
- A reusable library API (
package:flutter_distribute_manager/…).