CLI
Tutorial: first release + first patch
An end-to-end walkthrough — install the CLI, log in, set up a Flutter project, cut a base release, submit the store binary, then ship a live code-only patch with the auto-diff workflow.
This tutorial takes you from a clean machine to a live over-the-air patch. It uses a Flutter project because that's the richest path; the React Native flow is called out where it differs. By the end you'll have cut a base release, submitted a store binary, edited real code, and shipped that change to devices without a store resubmission.
Prerequisites
- Node.js 18+ and the platform toolchain for your target (Xcode for iOS, Android SDK + JDK 17 for Android). See Install the CLI.
- A Sankofa account with a project. Create one in the dashboard first.
Step 1 — Install and log in
npm install -g sankofa-cli
sankofa --version
sankofa login # opens your browser, mints a Deploy Token for the project you pickFor CI, skip the browser and pass a pre-minted Deploy Token instead:
sankofa login --deploy-token sk_deploy_xxxxxxxx --project-id proj_xxxxxxxxStep 2 — Set up the project
From your Flutter project root:
sankofa init --deployThis adds the Sankofa Flutter SDK, creates sankofa.yaml, wires lib/main.dart (the updater pre-flight call), patches the Android + iOS native files, and installs the Sankofa engine. Because you already logged in, sankofa.yaml's app_id and api_key are filled from the project you selected — nothing to paste.
Confirm everything is wired:
sankofa doctor --deployStep 3 — Cut the base release
A base release builds the signed store binary and registers the OTA baseline patches attach to. Pick your platform:
# Android — produces a Play Store AAB
sankofa release android
# iOS — produces a signed .ipa for App Store Connect / TestFlight
sankofa release iosThe CLI detects the app version, verifies the embedded engine is a genuine Sankofa build, captures the auto-diff base for future patches, and (after you confirm) publishes the baseline. It prints the release label (e.g. v1.0.0), the rollout %, and the on-disk path of the store binary.
Step 4 — Submit the store binary
Upload the artifact the CLI printed:
- iOS: drag the
.ipainto Transporter, or use Xcode Organizer /xcrun altool. - Android: upload the
.aabto the Play Console (Production or a testing track).
Submit the exact binary sankofa release produced — it contains the Sankofa engine and OTA wiring. A binary from a raw flutter build is not registered for OTA and won't accept patches.
Step 5 — Ship your first patch
Now the payoff. Edit your real Dart code — fix a bug, change a string, adjust logic:
// lib/home_page.dart (example)
Text('Welcome back!') // was: Text('Welcome')Then ship it. Sankofa rebuilds your edited app, diffs it against the baseline, and ships only the changed functions — no patch file, no store resubmission:
sankofa patch android
# or: sankofa patch ios (App Store compliant)You'll see something like:
Patch module built — 1 changed function(s) (612 B).
Changed: _HomePageState.build
Publish patch v1.0.0-patch.1 targeting 1.0.0? (Y/n)Confirm, and the patch publishes as v1.0.0-patch.1. Devices in the rollout fetch and apply it on next launch.
Step 6 — Roll out gradually
Publish to a slice of devices first, then widen once it looks healthy:
sankofa patch android --publish --rollout 10 --description "welcome copy fix"
# …watch Catch + adoption, then re-publish wider by cutting the next patch at a higher rolloutWatch progress any time:
sankofa status
sankofa status --env live --platform androidReact Native differences
The shape is identical — release then patch — but a React Native patch ships a JavaScript bundle rather than diffed Dart:
sankofa release ios
# …edit your JS/TS…
sankofa patch ios --publish --rollout 25React Native's patch shows a picker of base releases and asks whether the change touches native code (which can't go OTA).
One-shot deploy
For CI scripts that shouldn't care whether a baseline exists yet, sankofa deploy auto-picks release (first run) or patch (subsequent runs):
sankofa deploy android --publish --rollout 50