@otaupdate/cli
v2.1.1
Published
CLI for publishing React Native over-the-air updates
Downloads
907
Readme
ota — CLI
Publish and manage React Native over-the-air updates.
npm install -g @otaupdate/cli # or prefix any command with: npx @otaupdate/cli …The whole workflow
ota login # once per machine
ota init # once per app
npm run ota:prod # every time you shipota init writes ota.config.js and the ota:* npm scripts, so no day-to-day
command takes an app name, a key, a platform or a version — they are read from
the config, your app version and your git state.
Authenticate
ota login # email + password, then the 6-digit code
ota login --api-key ota_xxx # CI / scripting — no 2FA prompt
ota whoami
ota logoutCredentials are written to ~/.ota/config.json (mode 0600). The CLI already
knows which server to talk to; --server is only for an internal deployment.
In CI, skip login entirely and set one secret:
export OTA_API_KEY=ota_xxxNamed profiles keep two accounts or organizations apart:
ota login --api-key ota_prod --profile prod
ota status --profile prodConnect an app
ota init # detects the stack, creates the project, prints the keys
ota init --project my-app # or attach to a project that already exists
ota init --org <orgId> --yes # non-interactive (an organization id, not a slug)It leaves behind:
ota.config.js— project slug, platforms, runtime-version policy. Committed; it holds no secrets.- npm scripts —
ota:staging,ota:prod,ota:prod:mandatory,ota:promote,ota:rollback,ota:doctor. An existing script of yours is never overwritten; ours is added as<name>:otainstead. - One SDK key per channel (
production,staging), printed once and readable again from the dashboard. That key goes into your native build — see the SDK README.
Re-running ota init repairs the config and scripts rather than creating a
second project.
Releasing
npm run ota:staging # ota release --channel staging
npm run ota:prod # ota release --channel production
ota release --channel production --rollout 20 -m "Fix checkout crash"It detects Expo vs bare React Native, runs the right bundler
(expo export:embed or react-native bundle) for each configured platform,
zips the output with its assets, and uploads it. The release note, commit,
branch, author and CI run URL are captured automatically.
| Flag | Default | Meaning |
|---|---|---|
| -c, --channel <name> | staging | Channel to publish to |
| -p, --platform <os> | both | Limit to android or ios |
| -m, --message <text> | last commit subject | Release note |
| -r, --rollout <pct> | 100 | Percentage of devices served, 1–100 |
| --mandatory | off | Devices install before continuing |
| --runtime-version <v> | your app version | Which native builds this bundle targets |
| --allow-dirty | off | Publish with uncommitted changes |
| --allow-duplicate | off | Publish even if byte-identical to the current release |
The runtime version is the native app version the bundle is compatible with. Devices on other binary versions never receive the release — the most common cause of "I published but nothing happened".
A dirty working tree is refused by default: a bundle built from uncommitted code exists on no branch, so nobody can reproduce what shipped.
Operating a release
ota status # the last ten releases and what each is doing
ota promote --from staging --to production # same bytes, no rebuild, no upload
ota rollback --channel production # both platforms
ota rollback --channel production --platform ios # one platform
ota rollback --channel production --to 11 # back to a release you name
ota doctor # why isn't my update arriving?rollback republishes an earlier bundle as a new release at 100% and stops
serving the bad one, so devices already running the bad build get the fix on
their next check. Without --to it goes back one release; --to <number>
restores the release you name, including one that was itself rolled back. The
dashboard has the same picker on the channel page.
A published release's rollout percentage is fixed. To widen it, publish again at the higher percentage or promote from another channel.
doctor checks the environment, the project and its bundle identifiers, the git
state and the scripts, and names the fix for each failure. It exits non-zero when
something is broken.
Keys and members
ota api-key create github-actions # shown once
ota api-key list
ota api-key revoke <id>
ota member list
ota member add [email protected] developer --initial-password 'temp-pass-123'
ota member role [email protected] admin
ota member rm [email protected]
ota org list
ota org add "Acme" # user session only, not an API keyAPI keys are scoped to one organization and cannot manage other keys or members — that needs a user session.
Scripting
Every command supports --format json:
ota status --format json | jq -r '.releases[0].release_number'Errors print as JSON in that mode too, with a stable code to branch on
(FREE_PLAN_RELEASE_LIMIT_REACHED, PLAN_APP_LIMIT_REACHED,
SUBSCRIPTION_PAST_DUE, …) and a details.upgradeUrl where relevant. The exit
code is non-zero.
GitHub Actions
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx @otaupdate/cli release --channel production --rollout 10 --format json
env:
OTA_API_KEY: ${{ secrets.OTA_API_KEY }}Nothing else is needed: ota.config.js is in the repo, and the commit, branch
and run URL are read from the CI environment (GitHub Actions, GitLab CI and
Bitrise are recognised).
Global flags
| Flag | Environment variable | Meaning |
|---|---|---|
| --api-key <key> | OTA_API_KEY | Authenticate one command with a key |
| --server <url> | OTA_SERVER_URL | Point at a non-default server |
| --profile <name> | — | Use a named credential profile |
| --format <human\|json> | — | Output format |
| — | OTA_DEBUG=1 | Print stack traces on failure |
Upgrading from 1.x
ota app …, ota deployment … and the old ota release <app> <bundle> were
removed in 2.0.0, along with the API they called. Run ota init once per app to
create the project, then use the commands above.
