npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rollbird/cli

v0.2.0

Published

Command-line tools for shipping React Native OTA updates with Rollbird.

Readme

@rollbird/cli

The rollbird command: detect a React Native project, wire it up to a Rollbird project, and ship signed OTA releases from your machine or CI. Bundled to a single ESM file with tsup, requires Node ≥ 20.

Install

npm i -D @rollbird/cli

A local install puts rollbird in node_modules/.bin, so npx rollbird <command> picks it up without any global install. (A global install — npm i -g @rollbird/cli — works the same way, for use outside any one project.)

Quickstart

npx rollbird login              # confirm the device code in your browser (or set ROLLBIRD_TOKEN in CI, see below)
npx rollbird init                # detect the project, link/create it, wire native config
npx rollbird release --production

In a terminal each command renders a timeline: every step spins while it runs and settles with its result and elapsed time.

┌  rollbird release · fieldkit → production
│
◇  Bundled iOS          966 KB · 12.4s
│
◇  Bundled Android      968 KB · 11.8s
│
◇  Signed               2 bundles
│
◇  Uploaded             1.9 MB · 2.1s
│
◇  Published            Release #4 · 100% rollout
│
└  Live in production
   https://app.rollbird.dev/p/fieldkit/r/4

In CI, when output is piped, or with --json, the same steps print as plain lines with no animation or timings (✓ Bundled iOS 966 KB), and errors as ✗ message followed by the hint.

init prints a snippet to wrap your app entry point:

export default Rollbird.wrap({ projectKey: "pk_..." })(App);

See @rollbird/react-native for what Rollbird.wrap does.

Commands

rollbird login

Authenticates the CLI via the device-authorization flow. In a terminal it prints the one-time code (and copies it to your clipboard when pbcopy, clip, wl-copy, xclip or xsel is available), then offers Press Enter to open https://app.rollbird.dev/device?code=WXYZ-1234 — the device page arrives with the code filled in, and you confirm it after checking it matches your terminal. The CLI polls the whole time, so confirming from any browser or device works without pressing Enter. Writes ~/.config/rollbird/credentials.json (0600). Each org you log in to is kept: logging in to a second org adds it rather than replacing the first, and the latest login becomes the default.

Without a TTY (CI, piped output, --json) it never reads stdin, copies, or opens anything: it prints Open https://app.rollbird.dev/device and enter the code: WXYZ-1234 and polls.

| Flag | Description | | --- | --- | | --token <token> | API token — CI escape hatch, skips the device flow entirely | | --org <orgId> | Organization id (optional, paired with --token; the API resolves org from the token itself otherwise) |

You will not normally need this in CI — see Authentication below.

rollbird init

Detects the React Native project in the current directory (ios/, android/, Hermes, bundle/application ids), links it to a Rollbird project (or creates one), generates a signing key pair for it server-side, writes the channel and public key into your native config, adds @rollbird/react-native

  • the pinned @hot-updater/react-native to package.json (installing them, unless --no-install), wires the native entry points (MainApplication's getJSBundleFile, AppDelegate's bundleURL) with Hot Updater's own transformers, embeds each platform's native fingerprint (HOT_UPDATER_FINGERPRINT_HASH / hot_updater_fingerprint_hash), and writes rollbird.json. Safe to re-run — every write is idempotent — and re-run it whenever native code changes, then rebuild: devices only receive releases whose fingerprint matches the one their build carries. release warns when the fingerprint it computes differs from the embedded one.

| Flag | Default | Description | | --- | --- | --- | | --name <name> | package.json name | Project name, only used when creating a new Rollbird project | | --channel <channel> | production | Channel written into native config | | --yes | off | Accept defaults instead of prompting | | --no-install | (installs) | Skip installing SDK dependencies | | --ios-bundle-id <id> | auto-detected | iOS bundle identifier, when it can't be read from Info.plist | | --android-app-id <id> | auto-detected | Android application id, when it can't be read from build.gradle |

Expo projects — expo installed plus an app.json with an expo key or an app.config.{js,mjs,cjs,ts,mts,cts} — take a different path, because expo prebuild regenerates ios/ and android/ and throws away anything written there. init registers the config plugin instead:

{ "expo": { "plugins": [["@rollbird/react-native", { "channel": "production" }]] } }

and records the channel, the public key, "runtime": "expo" and one fingerprints.{ios,android} value per platform in rollbird.json — which is what the plugin reads at prebuild time, locally and on an EAS worker. It also adds @expo/fingerprint to devDependencies: init and release both read the app's own copy, so it has to be a declared dependency rather than whichever one the SDK happens to hoist (a range the project already declares is left alone). Re-running init updates that one plugin entry rather than adding a second. When the project is configured by an app.config.js/.ts (code, which Rollbird won't rewrite), init prints the snippet to paste and says so. Then run npx expo prebuild (or let EAS Build run it) and rebuild the app before releasing.

The Expo fingerprint is computed here and only here. init stores it, the config plugin stamps that stored string into the binary, and release tags the release with it — one value, so a build and its updates always agree. Recomputing on both sides does not work: @expo/fingerprint adds a dir:android (and dir:ios) source as soon as those directories exist, so any machine that has prebuilt locally computes a value none of its own binaries carry. Re-run rollbird init after adding or changing native dependencies — it refreshes the stored value in place — then npx expo prebuild and rebuild.

init refuses (exit 2) when expo-updates is installed: it and Rollbird both control the update process, so an app carrying both updates unpredictably or crashes. Remove expo-updates and stop using EAS Update first.

If creating the project would go over your plan's app limit, init exits with code 4 and says what to upgrade to. In a terminal (without --yes) it offers to open checkout instead, waits for the upgrade, and then carries on with setup — see Plan limits.

rollbird release

Builds, signs, and publishes a new OTA release. For each detected platform: computes the native fingerprint, builds the bundle, zips it, fetches this project's signing key and signs both the archive hash and its immutable release targeting metadata. It refuses to publish if the project has no signing key. It uploads to the presigned URL the API returns, then publishes. Attaches git metadata (commit, branch, author) and a detected CI provider (GITHUB_*, CI_* → GitLab, BITRISE_*, CIRCLE_*) when available.

| Flag | Default | Description | | --- | --- | --- | | --production | off | Publish to the production channel (shorthand for --channel production) | | --channel <name> | preview | Release channel | | --rollout <percent> | 100 | Staged rollout percentage, 1–100 | | --message <text> | none | Release message | | --force | off | Force the update on-device (shouldForceUpdate) | | --platform <platform> | both detected | Build only ios or android | | --target-app-version <version> | none (fingerprint strategy) | Target app version, for the appVersion update strategy | | --yes | off | Skip the confirmation prompt when publishing to production |

On an Expo project ("runtime": "expo" in rollbird.json) the bundle is built with npx expo export:embed — the same command Expo's own build phase runs — with the entry file taken from package.json's main, and both platforms are built when no native directory exists yet. export:embed emits plain JavaScript even for a Hermes app, so the bundle is then compiled to bytecode with the app's own hermesc (from node_modules/react-native/sdks/hermesc/) and shipped in its place, matching what a bare release carries; a Hermes app with no compiler installed fails the release rather than quietly shipping source. The release is tagged with the fingerprint stored in rollbird.json — the same string the plugin stamped into the binary — and release refuses (exit 2) if there isn't one, naming rollbird init.

It also re-checks that stored value, but only where the check means something: with no ios//android/ on disk, the state init measured in. A difference there means native-affecting changes landed since, and release says to re-run rollbird init, prebuild and rebuild. On a prebuilt checkout a recompute measures something else (@expo/fingerprint hashes the generated directories too), so release says the fingerprint wasn't re-checked here instead of crying drift on every single release.

When rollbird.json records a channel and you publish to a different one, release says so — the channel is baked into the build, so those installs never ask for that release. It's a note, not a refusal.

After a production publish on a plan with a monthly production-release limit, release warns when you're down to your last one (1 production release left this month (resets Oct 1). Upgrade for unlimited: …) and when you've just used it (That was your last production release this month …). With --json there's no warning; the success object gains a quota field ({ used, limit, resetsAt, upgradeUrl }, where used includes this publish) instead. If a limit stops the release, see Plan limits.

rollbird rollback [number]

Rolls a channel back to the previous active release — instant, free, and always allowed regardless of quota or plan status. Omit [number] to roll back the channel's current active release; pass a release number to target a specific one.

| Flag | Default | Description | | --- | --- | --- | | --yes | off | Skip the confirmation prompt | | --channel <name> | production | Channel to roll back |

rollbird status

Prints the current project, each channel's latest release (number, status, rollout, published), a production-release quota bar, and storage usage. Reads rollbird.json in the current directory; no flags beyond the global ones.

Global flags

| Flag | Description | | --- | --- | | --json | Emit one JSON object on stdout; human-readable output moves to stderr (see Scripting) | | --api-url <url> | Override the Rollbird API URL for this invocation (takes precedence over ROLLBIRD_API_URL and the credentials file) |

Authentication

Credentials resolve in this order:

  1. ROLLBIRD_TOKEN (+ optional ROLLBIRD_ORG, ROLLBIRD_API_URL) environment variables.
  2. ~/.config/rollbird/credentials.json (or $XDG_CONFIG_HOME/rollbird/), written by rollbird login.

Several orgs: release, status, rollback and a re-run init use the saved login whose org owns the project in rollbird.json, so repos in different orgs work side by side without logging in again. With one saved login the CLI uses it directly; with more it asks the API which one can see the project, latest login first. A new init links into the latest login's org.

CI: set ROLLBIRD_TOKEN as a secret in your CI provider — rollbird login is unnecessary there. The API resolves the org from the token itself, so ROLLBIRD_ORG is optional; set it too only if you specifically want the CLI to send x-rollbird-org (harmless as long as it matches the token's own org — the API 403s only on a genuine mismatch). For example, in GitHub Actions:

- run: npx rollbird release --production --yes
  env:
    ROLLBIRD_TOKEN: ${{ secrets.ROLLBIRD_TOKEN }}
    # ROLLBIRD_ORG: ${{ secrets.ROLLBIRD_ORG }}  # optional, see above

The CLI never prompts when stdout isn't a TTY or CI is set in the environment — a missing required input exits with code 2 and a one-line fix instead of hanging on a prompt. Pass --yes to skip confirmation prompts (release --production, rollback) explicitly rather than relying on non-interactive detection alone.

Project config

rollbird init writes rollbird.json to the project root — commit it:

{ "projectId": "proj_...", "projectKey": "pk_..." }

No secrets live in this file; it only identifies the project so release, rollback, and status know which one to talk to. Signing keys are generated and held server-side (see init's "Signing key" step). The CLI receives the encrypted-at-rest project private key over its authenticated session for in-memory signing. It never writes that key to project or credential files.

On an Expo project it carries three more fields, because expo prebuild regenerates the native config and this file is what survives it:

{
  "projectId": "proj_...",
  "projectKey": "pk_...",
  "channel": "production",
  "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n...",
  "runtime": "expo"
}

The public key is public by design (devices verify signatures with it); the private half never leaves the server. runtime is absent on bare projects.

A rollbird.json that exists but doesn't parse — bad JSON, an empty channel, an unknown runtime — is reported as invalid, naming the fields that failed, instead of being treated as missing.

Scripting (--json)

Every command accepts --json: exactly one JSON object is written to stdout, and the same human-readable lines you'd otherwise see on stdout are written to stderr instead — so you can pipe stdout straight into jq without the human transcript in the way, while still seeing progress if you also show stderr.

npx rollbird --json release --production --yes | jq -r '.number'

A failed command writes an { "error": { ... } } object instead, with exitCode, message, and, when present, hint, the API's error code, and its meta (for plan limits: resource, limit, used, plan, upgradePlan, upgradeUrl, and resetsAt for monthly releases).

Exit codes

| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Release failed (build/bundle error) | | 2 | Usage or configuration error (missing rollbird.json, bad flag, cancelled prompt) | | 3 | Not authenticated (run rollbird login, or set ROLLBIRD_TOKEN) | | 4 | Plan limit reached (message says which limit, and how to upgrade — see Plan limits) | | 5 | Network or upload failure |

Plan limits

When a release hits a plan limit (production releases per month, storage, apps, or seats), the CLI says what's at stake and how to unblock it, e.g.:

Release #142 is built and uploaded, but it can't go live yet.
Free includes 3 production releases a month — you've used 3. Resets Oct 1.
Publish it now: upgrade to Indie ($7/mo, unlimited production releases)
  → https://app.rollbird.dev/settings/billing?plan=indie&interval=year&reason=releases
Preview releases and rollbacks still work: npx rollbird release
  • Hit when publishing (after upload): the release stays uploaded. In a terminal, the CLI asks Open checkout and publish #142 when it's done?; on yes it opens checkout in your browser, checks every few seconds for the upgrade (up to 15 minutes; Ctrl+C stops waiting), then publishes that same release.
  • Hit when creating the release (before upload): nothing is uploaded yet. In a terminal you get the same checkout offer, and once you've upgraded the release is created, uploaded, and published. Otherwise, upgrade and re-run the same command.
  • No terminal (not a TTY, CI set, or --json): no prompt, just exit code 4, with the upgrade URL in the output (and the limit details in error.meta with --json).
  • If there's no bigger plan to upgrade to, the message says what the limit is and doesn't offer checkout.