@tiledev/tile-push-cli
v0.1.0
Published
Tile Push CLI — OTA deploys for React Native, built on top of hot-updater.
Readme
Tile Updater
Over-the-air (OTA) code-push updates for React Native — ship JS/asset changes to installed apps without an app-store release. Multi-tenant, fingerprint-safe, and self-contained: the SDK and CLI are plain npm packages.
@tiledev/tile-updater— the runtime SDK (wrap your app; devices auto-update on launch).@tiledev/tile-push-cli— thetile-pushCLI (init, fingerprint, deploy, manage bundles).
Built on hot-updater.
Install
npm install @tiledev/tile-updater @hot-updater/react-native @hot-updater/core
npm install --save-dev @tiledev/tile-push-cli @hot-updater/expo@hot-updater/react-native and @hot-updater/core are peer dependencies of the
SDK — install them explicitly. Use @hot-updater/metro instead of @hot-updater/expo
for a bare React Native (non-Expo) project.
Using the Tile platform? Skip all of this —
tile init --blueprint defaultinstalls and wires the SDK, the config plugin,tile-push.config.ts, the app wrap, and a reconciled lockfile automatically. Jump to Ship an update.
Wire it up (3 steps)
1. Wrap your root component
// App.tsx
import { TileUpdater } from '@tiledev/tile-updater';
function App() {
return <YourAppRoot />;
}
export default TileUpdater.wrap({
appId: 'your-app-id', // your Tile Push tenant id
apiUrl: 'https://ota.tile.dev', // the OTA backend
updateStrategy: 'fingerprint', // or 'appVersion'
})(App);2. Register the Expo config plugin
app.json → expo.plugins. This injects the native fingerprint into the build
so devices only receive bundles built from a matching native tree:
{
"expo": {
"plugins": ["@tiledev/tile-updater"]
}
}3. Create tile-push.config.ts
Run npx tile-push init --app-id <id> --token <deploy-token>, or write it by hand:
import 'dotenv/config';
import { defineConfig } from 'hot-updater';
import { expo } from '@hot-updater/expo';
import { tilePushDatabase, tilePushStorage } from '@tiledev/tile-push-cli';
const appId = process.env.TILE_PUSH_APP_ID;
if (!appId) throw new Error('TILE_PUSH_APP_ID is not set (see .env).');
export default defineConfig({
build: expo({ enableHermes: true }),
storage: tilePushStorage({ appId }),
database: tilePushDatabase({ appId }),
updateStrategy: 'fingerprint',
});Ship an update
The fingerprint is a hash of your native inputs (deps, native config). A bundle
is only served to devices whose installed build has the same fingerprint — so a JS
push can never land on an incompatible native app. Keep fingerprint.json committed
as the single source of truth.
1. Fingerprint (once per native change)
npx tile-push fingerprint create # writes fingerprint.jsonFor Expo prebuild projects the order is prebuild → fingerprint create → prebuild (injects the hash) → build. The value is stable across re-injection.
2. Build the native app
Any normal build works — the SDK is just a package, and the config plugin injects
the fingerprint at expo prebuild. No OTA/deploy step happens during the build.
3. Deploy
npx tile-push deploy --platform android --rollout 10 # ship to 10% of devices
npx tile-push deploy --platform android # ship to everyonedeploy bundles your JS (Hermes), verifies its fingerprint matches fingerprint.json,
tags the bundle, and uploads it. Devices whose native build matches pick it up on
next launch; others are correctly skipped.
Commands
| Command | Description |
| --- | --- |
| tile-push init | Write tile-push.config.ts + ~/.tile-push/credentials.json |
| tile-push fingerprint create | Compute/snapshot the native fingerprint |
| tile-push deploy | Build and ship a new bundle (supports staged --rollout) |
| tile-push bundle list/show/enable/disable/update/promote/delete | Manage bundles |
| tile-push rollback <channel> | Disable the most recent enabled bundle |
| tile-push channel [set] | Read/write the channel baked into the native app |
| tile-push whoami | Show the active tenant + token |
| tile-push doctor | Diagnose config / credentials / server / project |
| tile-push console | Open the web console for this tenant |
Credentials
The CLI reads credentials in this order:
TILE_PUSH_APP_ID+TILE_PUSH_TOKENenv vars (preferred for CI)~/.tile-push/credentials.json(written bytile-push init, chmod 600)
Never put tokens in tile-push.config.ts — it's committed to your repo. Override
the API base URL with TILE_PUSH_API_URL.
On the Tile platform
Apptile customers manage the same thing through the unified CLI — tile ota
forwards to this binary with the app id + session token injected automatically:
tile ota deploy --platform android --rollout 10
tile ota bundle list
tile ota rollback productiontile init --blueprint default installs and wires everything above, so a fresh app
is code-push-ready after npm install.
Roadmap
- Cloud Push — deploy an OTA bundle remotely from a
tile saveor git ref, the same way native builds already run in the cloud. The bundle is built in the same container as the APK, so the fingerprint matches by construction — no local toolchain, no fingerprint drift. Planned surface:tile ota deploy --source save|git, with a reach-check that refuses a push no installed build can receive. - iOS build/deploy parity docs.
- First-class staged-rollout cohort tooling in the CLI.
tile-push login— browser device-code flow that mints and stores a token.
Acknowledgements
Built on hot-updater (MIT). Tile Push wraps it with hosted storage, auth, multi-tenancy, and a unified deploy CLI. The bundle pipeline, fingerprinting, and bundle metadata model are hot-updater's work.
License
MIT. See LICENSE.
