CLI
sankofa patch
Ship a code-only OTA update on top of a base release — no store resubmission. Covers the Flutter auto-diff workflow (edit real Dart, ship only changed functions), the React Native JS-bundle path, and every flag.
sankofa patch ships a code-only update against an existing base release. No native rebuild, no App Store / Play Store resubmission — devices in the rollout fetch and apply the patch on next launch. Both iOS and Android are App Store + Play Store compliant.
sankofa patch [platform] [options]platform is ios or android. Omit it and the CLI prompts. As with release, the stack is auto-detected and the matching pipeline runs.
Flutter: the auto-diff workflow (default)
The Flutter path is the headline feature: you edit your real Dart code, and sankofa patch ships only the functions that changed. There is no patch file to write and no special entry point to maintain.
Edit your real code
Change a function body in your
lib/— fix a bug, tweak a string, adjust logic. Just edit the app the way you normally would.Run sankofa patch
The CLI rebuilds your edited app, diffs it against the baseline captured by
sankofa release, and lifts exactly the changed functions into a tiny patch module.Publish
The module is signed (if you have a signing key), uploaded, and labelled
<base>-patch.N. Devices in the rollout apply it on next launch via the Sankofa updater.
# 1. …edit your Dart code…
# 2. Ship it — Sankofa diffs your edits and ships only what changed
sankofa patch android
sankofa patch ios # App Store compliant
# Non-interactive with a staged rollout + note
sankofa patch android --publish --rollout 25 --description "fix crash on Pixel 6"If nothing changed vs. the baseline, the CLI tells you there is nothing to ship and exits without publishing.
What you can and cannot patch
A patch carries Dart code changes only. The safety check refuses anything that changes AndroidManifest.xml, native assets, or adds new native bindings — those require a full sankofa release and a store submission. Practically:
- Yes: changed logic and function bodies in your Dart code, bug fixes, copy tweaks, behavior changes reachable through your existing code.
- No: new native plugins, changed
Info.plist/AndroidManifest.xml, new bundled assets, engine upgrades, or a different app version. Cut a new release for those.
Engine must match the baseline
A patch built against engine X can only be applied to a baseline built against engine X, or the device cannot load it. The CLI cross-checks this before building and stops with an explicit engine mismatch message if your local engine differs from the baseline's. If you intentionally upgraded, cut a new release with sankofa engine upgrade first.
Selecting the baseline
With one baseline release the CLI picks it automatically. With several it shows an interactive picker. For headless / CI runs, name the baseline explicitly:
# Pick the base release by label (or id) — required for CI / non-interactive runs
sankofa patch android --release v1.4.0 --publish
# Scope to the flavor the base release was built with
sankofa patch android --release v1.4.0 --flavor production --publishPatches inherit the baseline's target_binary_version (so device targeting lines up) and are labelled <base>-patch.N, where N auto-increments.
Flavored apps
sankofa patch android --flavor production -t lib/main_production.dart
sankofa patch ios --flavor staging -t lib/main_staging.dart--flavor scopes which base release the patch targets (mirroring sankofa release --flavor); -t/--target is the entry point the rebuild + diff uses.
Legacy patch-file model
The default auto-diff path replaces the older model where you hand-wrote a single entry-point file. You can still opt into it:
# Compile a single hand-written @pragma('dyn-module:entry-point') file
sankofa patch android --legacy-patch-file # default lib/sankofa_patch.dart
sankofa patch android --legacy-patch-file lib/my_patch.dartThis is limited compared to auto-diffing your real code — prefer the default unless you have a specific reason.
React Native: JS-bundle patch
React Native patches bundle your JavaScript + assets and ship them against a chosen base release. The CLI shows a picker of base releases, asks whether the change includes native code (which can't go OTA), then bundles and uploads.
sankofa patch ios --publish --mandatory
sankofa patch android --rollout 50
sankofa patch ios --release v1.2.0 --publish # skip the picker in CIFull flag reference
Shared
--publishflag--env <environment>live | test--rollout <percent>0–100default 100--mandatoryflag--description <desc>string--release <labelOrId>string--project <path>path--dry-runflagFlutter
-t, --target <file>pathdefault lib/main.dart--entry-file <file>path--legacy-patch-file [file]pathdefault lib/sankofa_patch.dart--flavor <name>string--no-rebuildflag--output-dir <dir>pathdefault ./build--dart-define <KEY=VALUE>repeatable--engine-version <version>string--target-binary-version <semver>string--label <label>string--dynamic-interface <yaml>pathdefault ./sankofa/dynamic_interface.yaml--engine-commit <sha>string--dart-version <semver>stringSigning
If a project signing key exists on disk (created by sankofa keys generate), every patch is signed with it automatically and the device verifies the signature before applying. Without a key, patches are unsigned but still fully functional — generate a key when you want end-to-end signature verification.
After publishing
The command prints the patch ID, label (<base>-patch.N), runtime, engine, target version, rollout %, and whether it is mandatory. Devices in the rollout fetch and apply it on next launch through the Sankofa updater's pre-flight step at startup. Track adoption with sankofa status.
Related commands
sankofa release— cut the base release a patch attaches to.sankofa engine— inspect / match / upgrade the engine a patch is built against.- Troubleshooting — engine mismatch,
no_matching_release, and theSANKOFA_SKIP_ENGINE_CHECKescape hatch.