quickpatch_cli 1.6.137
quickpatch_cli: ^1.6.137 copied to clipboard
QuickPatch — self-hosted OTA code-push for Flutter apps.
1.6.137 #
quickpatch doctorand console links now use QuickPatch's real branded domains. The doctor network check previously probedconsole.quickpatch.dev(never provisioned) andcdn.quickpatch.cloud(never provisioned), plus two legacy Google Cloud endpoints (oauth2.googleapis.com/storage.googleapis.com) inherited from the old GCS setup that QuickPatch no longer uses - so two checks always failed and two were misleadingly green. It now checks the three hosts QuickPatch actually talks to:api.quickpatch.dev(backend),console.quickpatch.dev(web dashboard, now live), andcdn.quickpatch.dev(R2 artifact/engine CDN). The "manage this release" link printed on an active-release conflict now points atconsole.quickpatch.devas well.
1.6.136 #
quickpatch flutter versions listnow lists the versions QuickPatch actually supports. It previously listed the pinned Flutter fork's git tags (3.41.x etc.), most of which have no QuickPatch engine and cannotrelease/patch- misleading. The command now reads the server's/api/v1/engine-versionsregistry (the authoritative list of versions with a built + hosted engine) and marks the current version;--jsonadds asupported_versionsarray with flutter/engine revisions. If the registry is unreachable (offline / self-hosted without it), it falls back to the old tag list with a clear warning.
1.6.135 #
- iOS staged rollouts now gate per device. The interpreter bootstrapper's patch-check request now carries the persistent per-install
client_id(the same id used for download/install telemetry). The server buckets staged rollouts per device —sha256(client_id:release:patch) → 0..99compared against the rollout percent — so without a client id every iOS device hashed to the same bucket and a percentage rollout was effectively all-or-nothing. Android already sent it (native updater); no server change needed (the check endpoint already acceptedclient_id). Parity-safe: no bootstrapper import changes (the frozen-import regression test still passes). Rebuild the iOS release with ≥1.6.135 for per-device staged rollouts.
1.6.134 #
- iOS interpreter releases report download/install telemetry — parity-safe this time. Re-lands the 1.6.132 feature with the root cause of its breakage designed out: the telemetry helpers use ONLY libraries the bootstrapper already imports (
dart:io,dart:convert,package:crypto— the client id is sha256 over process/time entropy instead ofdart:mathRandom), so the base image's library graph is byte-identical to 1.6.131/1.6.133 and patch-module loading is unaffected by construction. A regression test now freezes the bootstrapper's exact import list so any future import change fails CI with a pointer to the patcherextraImportsmirror. Events:__patch_download__when a patch is staged (background OTA and user-drivenupdate()paths),__patch_install__when a patched module reaches its first frame; persistent per-installclient_id; once-per-patch dedupe with the marker written only after a server 200 (failed sends retry next launch); fire-and-forget so reporting can never block or crash the app. Server-side, a partial unique index on(appId, clientId, type, patchNumber, releaseVersion)for the two countable types makes the dashboard numbers per-device facts even against client retries. Rebuild the iOS release with ≥1.6.134 for counts to appear; already-installed builds don't backfill.
1.6.133 #
- Revert the 1.6.132 iOS download/install telemetry change — it broke patch application. Adding an
import 'dart:math'(+ telemetry helpers) to the release's server-mode bootstrapper changed the base image's library set without a matching update to the patcher'sextraImportsmirror. A patch built against the un-mirrored import-dill then bundles its own copy of a library the base already holds and fails to load on device ("library ... is already loaded") — the staged patch never applies. Reverted the bootstrapper to the byte-identical 1.6.131 generator so releases + patches are consistent again. iOS download/install telemetry is deferred until it can be added without perturbing the base/patch library parity (and verified with a real on-device build). The server-side fix (not counting iOS binary-diff__patch_update_failure__as a failure) is unaffected and stays.
1.6.132 (yanked — breaks iOS patch application; use 1.6.133) #
- iOS interpreter releases report download/install telemetry from the bootstrapper. Superseded/yanked: the
dart:mathimport it added to the base broke patch-module loading (see 1.6.133). Do not use for iOS releases.
1.6.131 #
quickpatch initnow always writesbase_urlinto quickpatch.yaml (previously only when QUICKPATCH_HOSTED_URL was set). Without it, Android builds resolved the Flutter engine from Google's CDN and got the vanilla engine with no on-device updater — silently breaking OTA for newly initialized apps.QUICKPATCH_HOSTED_URLstill overrides for self-hosted servers.
1.6.130 #
- Zero-config patch signing. On the first
quickpatch releasefor an app, the CLI now auto-generates an RSA-2048 key pair into a per-app key store (<config dir>/keys/<app_id>/, next to the CLI credentials) and embeds the public key automatically;quickpatch patchsigns with the stored key automatically. No openssl knowledge or key flags needed. Explicit--public-key-path/--private-key-path/--public-key-cmd/--sign-cmdalways override, so CI and externally-managed keys keep working unchanged. A patch for a release built without a key stays unsigned (patching is never the key-creation moment).
1.6.129 #
- quickpatch_code_push works on the iOS interpreter path (prompt-driven updates).
update()on an interpreter release failed ("Downloaded patch file does not have valid zstd magic bytes") because it invoked the native binary-diff updater, which cannot process bytecode-module patches. The generated bootstrapper now installsQuickPatchInterpreterOverrideshooks (package 1.1.0) socheckForUpdate/update/readCurrentPatch/readNextPatchdrive the interpreter's staged full-module flow. Device-proven end-to-end: check → consent dialog → download+stage → restart → patched. - Interpreter bootstrapper honors
auto_update: false— the background download+stage is skipped; updates become user-driven via quickpatch_code_push (same semantics as the native updater). - iOS interpreter patch no longer collides at load. The patcher's import-dill now covers the packages the release baked into the base (quickpatch_code_push, asn1lib/crypto/pointycastle — gated on the app's package_config), so the patch module references them instead of bundling copies ("library ... is already loaded" → boot-load skipped → blank screen).
- A bad staged patch can no longer blank the app forever: if the staged module fails to LOAD at boot, the stage is cleared and the bundled base is booted (previously nothing was loaded, and since the empty frame reset the crash counter the bad stage was never dropped).
- Engine ensure re-asserts the interpreter platform overlay:
flutter precache/artifact re-materialization can revertcommon/flutter_patched_sdk*to stock while the engine dir stays overlaid, breaking --interpreter builds (Method not found: 'loadDynamicModulePatch'). Now detected and re-overlaid from the engine's cached platform dill. - iOS interpreter release hard-fails if the built archive is missing the app bytecode module (previously such a release published and booted to a blank screen).
1.6.128 #
- Android code push on stable Flutter 3.44.0. Stable Flutter ships a vanilla Android engine with no on-device updater, so patches couldn't apply. The build now maps the vanilla engine to the QuickPatch Android engine (which carries the updater) for the duration of the build, so
libfluttersupports OTA and the snapshot stays consistent. Device-proven: an OTA code patch applies over-the-air, and a patch signed with an untrusted key is rejected (no brick). QUICKPATCH_PUBLIC_KEYSenvironment variable for signing-key rotation (the previousSHOREBIRD_PUBLIC_KEYSname is still accepted as a fallback, so existing setups keep working).- Fix: iOS
--interpreterapps that usequickpatch_code_pushcrashed at module load (Unable to find function ... in dart:isolate / dart:ffi). The generated dynamic-interface (which marks the AOT framework/SDK surface the interpreted app module may call) omitteddart:isolateanddart:ffi, soIsolate.runand the FFI symbols (nullptr/Pointer) thatquickpatch_code_pushuses were tree-shaken out of the base AOT image and the interpreter FATAL'd at module load. The release build now addsdart:isolateanddart:ffito the interface'scallable+can-be-used-as-typesections, so they are retained and resolvable. Rebuild the release with this version. - Fix: iOS engine overlay could silently revert to the stock Flutter engine (white screen).
ensureQuickPatchIosEnginetreated its.quickpatch-engine-revstamp as sufficient proof the overlay was intact. But a partial overlay (interrupted mid-copy) or aflutter precache/ artifact re-materialization can leave the stamp in place whilegen_snapshot_arm64reverts to the stock Flutter build. An app AOT-compiled by a stock gen_snapshot produces a snapshot whose version hash the QuickPatch runtime engine refuses to load — the app boots to a white screen.ensurenow also verifies the on-diskgen_snapshot_arm64actually embeds the engine revision, and re-installs the engine if it doesn't. This most commonly affected a second Flutter version's cache dir (e.g. stable 3.44.0), whose engine had been partially overlaid.
1.6.127 #
- Support for Flutter 3.44.0 stable:
quickpatch release/patchnow work on apps built with stable Flutter 3.44.0 (revision559ffa3f75…), in addition to the 3.44.0-rc3 pin. Stable 3.44.0 pins the identical Dart SDK revision as the rc3 fork, so it reuses the same on-device engine revision (snapshot hash unchanged) — no new engine download is required. Target it by revision:quickpatch release ios --interpreter --flutter-version 559ffa3f75e7402d65a8def9c28389a9b2e6fe42. (getRevisionForVersionalso gained an upstream-tag fallback so a plain--flutter-version 3.44.0resolves to stable on a vanilla-Flutter clone; on the legacy Shorebird-fork clone the fork'sflutter_release/3.44.0branch still points at the rc3 pin, so use the revision there.)
1.6.126 #
- Reliable large-artifact uploads (direct-to-R2): release & patch artifacts now upload directly to object storage via a presigned URL instead of being proxied through the API server. Large release artifacts (the iOS interpreter base is ~40MB+) previously failed with a connection reset when the API server ran behind a memory/edge-limited host (e.g. Railway); they now upload reliably regardless of artifact size or host. Requires the matching server update that returns presigned upload URLs.
1.6.125 #
- Publish-time smoke-test gate (Android): before an Android patch is uploaded,
quickpatch patchnow builds the patched app as an APK, installs and launches it on a connected device/emulator, and verifies it reaches its first frame. If the patched app crashes or hangs on startup, the patch is not published (the command exits non-zero) — so a startup-crashing patch never reaches your users. Detection uses the OS first-frame signal (ActivityManager: Displayed) for success, and logcat crash markers / a render timeout for failure. Runs automatically when a device is connected; skipped (with a warning for thestabletrack) when none is. Control it with--no-smoke-testand--smoke-test-timeout=<seconds>(default 45). Device-proven: a startup-crash patch is blocked; a healthy patch passes and publishes.
1.6.124 #
- iOS
--interpreterpatches now support ANY code change, including new classes/screens (device-proven: a patch that adds a whole new screen opens on a physical iPhone via OTA, no reinstall). The patch is now built as a full app module (the whole changed app behind adyn-module:entry-pointwrapper, like the release's base module) and the on-device bootstrapper loads + runs it vialoadModuleFromByteson the next launch (whole-program replacement) instead of the previous function-merge loader, which could only swap existing functions and crashed when a patch added a new top-level class. - Self-heal against a bad staged patch: a boot-failure counter (bumped before applying a staged patch, reset once the first frame renders) drops a staged patch that crashes repeatedly at load, falling back to the base — so a bad patch can no longer permanently brick the app.
- Note: the bootstrapper is baked into the release, so rebuild your release with this version (
quickpatch release ios --interpreter) before publishing full-module patches to it.
1.6.123 #
- iOS
--interpreterrelease & patch now build end-to-end (device-proven: an arbitrary Dart code change applied over-the-air on a physical iPhone with no reinstall). Completes thedynamic_modulesresolution work from 1.6.122 by fixing three follow-on issues:- The generated
package_config.jsonis written to the build dir, so its relativerootUris are now rebased to absolute — previously the app's ownpackage:<app>/...imports resolved against the wrong base. - The interpreter release now declares
dynamic_modulesas apath:dependency so theflutter build ipastep (which compiles the bootstrapper via the project's own package_config) can resolve it. - The interpreter patch now runs a
flutter pub getwith the vended Flutter before compiling, sopackage:flutterresolves to the engine-matched framework instead of a different system Flutter that fails to compile against the vended platform.
- The generated
1.6.122 #
- Fix iOS
--interpreterpatch/release builds:quickpatch patch ios --interpreter(and the interpreter release path) failed atgen_kernelwithCouldn't resolve the package 'dynamic_modules'. The bootstrapper importspackage:dynamic_modules, whose functions wrapdart:_internaldynamic-module natives that are already present in the engine's platform dill — but the wrapper package itself wasn't resolvable. The CLI now generates that wrapper and an augmentedpackage_config.jsonautomatically before compiling, so no engine change or extra setup is needed. Your project's.dart_tool/package_config.jsonis left untouched.
1.6.121 #
quickpatch init --channel: choose the update channel a build listens to (defaults tostable). Ship a beta build with--channel=beta, publish to it withquickpatch patch --track=beta, and your store build (onstable) stays untouched. The flag writeschannel:intoquickpatch.yaml; the on-device updater already honors it. Stable builds keep a clean config (no channel line written for the default).
1.6.120 #
- Branded CLI experience:
quickpatch initnow opens with a welcome banner — an emerald ANSI "QUICKPATCH" wordmark, tagline, and a short wizard intro — andquickpatch --helpshows the same logo header. Color and art are automatically stripped when output isn't a terminal (pipes, CI, files) and are never emitted in--jsonmode, so scripting output stays clean.
1.6.119 #
- Default endpoints now use the production custom domains: the default hosted server is
https://api.quickpatch.dev(was the Railway origin URL) and the login/console hint points tohttps://quickpatch.dev. These are only defaults —QUICKPATCH_HOSTED_URLand thebase_urlinquickpatch.yamlstill override them, so self-hosting and existing projects are unaffected.
1.6.118 #
- Multi-version foundation: the CLI now resolves the engine revision for a Flutter version from the server's
/api/v1/engine-versionsregistry, falling back to the built-in map if the server is unreachable. This means a newly-built Flutter version becomes usable without shipping a new CLI - the engine-build pipeline just publishes the version to the registry. Behavior is unchanged for the currently-shipped version (it resolves identically), and offline/no-server builds keep working via the fallback.
1.6.117 #
- Signing-key rotation (end to end): a build can now trust more than one patch-signing public key (a primary key plus rotation keys), so a patch signed by a newly-rotated key is accepted alongside one signed by the prior key — letting the signing key be rotated without invalidating installs. The additional keys, supplied as a comma-separated list, are embedded into
quickpatch.yamlfor the Android/data-patch path and baked into the iOS interpreter bootstrapper's trusted-key set; in both cases a patch verifies if its signature matches any trusted key (a valid-but-wrong key is skipped, never accepted). Device-verified on a physical Android device and a physical iPhone: a patch signed with a rotated key is downloaded, verified against the multi-key set, and applied.
1.6.116 #
- Patch signing fix (security): ensure the patch public key is embedded into the app before every Android and iOS build, so on-device patch signature verification is actually enforced. Previously the key could be omitted for QuickPatch projects, leaving the on-device updater unable to verify patches (effectively unsigned). Device-verified on a physical Android device: a patch signed with an untrusted key is now rejected at boot ("Patch signature is invalid"), while one signed with the trusted key applies normally. Applied automatically and idempotently, so existing installs are fixed on the next build.
- Forward additional trusted public keys (for signing-key rotation) to the build environment, so a rotated key can be trusted before the old one is retired.
1.6.115 #
- Self-host without an env var: the storage/mirror base URL now falls back to
quickpatch.yaml'sbase_url(after theQUICKPATCH_HOSTED_URLenv override, before the hosted default), so abase_urlin your config is enough to point both the build-time engine downloads and the on-device updater at your own server — no manualQUICKPATCH_HOSTED_URLexport. With neither set, behaviour is unchanged.
1.6.114 #
- Fix
--versionreporting:version.dartwas not bumped in the previous two releases, soquickpatch --version(and thex-cli-versionrequest header) reported a stale version. Now synced to the package version.
1.6.113 #
- Cleanup: removed the legacy config-file fallback (projects use
quickpatch.yamlexclusively) and tidied source comments. No functional change for existing projects.
1.6.112 #
- Fix onboarding crash:
quickpatch --versionandquickpatch doctorno longer fail with a cache-corrupted error on a fresh install before the pinned Flutter/engine is downloaded. The engine revision now falls back to the install-root pin, and both commands degrade to a readable "not installed (downloaded on first release/patch)" instead of throwing.
1.6.111 #
- iOS arbitrary code push (
--interpreter): release/patch iOS apps with arbitrary Dart changes (new widgets, screens, control-flow) over the air via an on-device Dart interpreter. - Staged OTA: interpreter patches are signature-verified, staged to disk on download, and applied at the next launch's first frame — no flash of the old UI, no live reassemble.
quickpatch upgrade: now performs a real git fast-forward + rebuild instead of printing an upgrade hint.- Fix: declare
asn1libas a direct dependency (used for patch-signature key parsing).
1.6.109 #
flutter versions list: Show newest versions first. Remove.reversedsince server already returns newest-first order.
1.6.108 #
- Fix
flutter versions list: Always fetch from production server using a fixed URL — previous version used the project-levelhostedUriwhich caused silent fallback to local git branches.
1.6.107 #
flutter versions list: Now fetches supported versions directly from your QuickPatch server instead of reading all engine fork branches. Only versions mirrored in R2 are shown — the ones you actually support. Falls back to local git branch listing if server is unreachable.
1.6.106 #
- Fix: When
QUICKPATCH_HOSTED_URLis not set, the CLI now shows a clear error with the exact export command (platform-aware:exporton macOS/Linux,$env:on Windows) instead of a crypticSocketException.
1.6.105 #
- Login: Replaced browser OAuth flow with API key authentication. Run
quickpatch loginand paste your key from the dashboard. - Upgrade:
quickpatch upgradenow shows the correctdart pub global activate quickpatch_cliinstruction instead of crashing with a git error. - Published to pub.dev — install and upgrade via
dart pub global activate quickpatch_cli.