bext-lite mobile CLI

bext-lite mobile is the packaging and DX family for shipping a PRISM app to iOS and Android. It has four subcommands:

bext-lite mobile init      # scaffold the native shell project(s)
bext-lite mobile run       # build + launch on a simulator/device
bext-lite mobile build     # produce a store artifact (.ipa / .aab)
bext-lite mobile dev       # live-reload loop

run, build, and dev orchestrate two tools that already exist rather than reimplementing a build: the AOT exporter (bext-lite-build) produces the bundle, then cargo tauri drives the platform toolchain (Xcode or Gradle). For the mobile architecture see /docs/mobile; for the device API see /docs/device-sdk.

Warning

Device builds need a device toolchain: macOS plus Xcode for iOS, the Android SDK plus NDK for Android, and the matching Rust targets. Where that toolchain is absent, every device-touching command prints exactly what is missing and exits with a clean non-zero status. It never panics and never assumes an in-process export.

Export first: --dist#

bext-lite itself does not AOT-export a PRISM app. That is the separate bext-lite-build binary. So run, build, and dev all require an already-exported dist-lite/ passed as --dist:

# 1. export the PRISM app to a self-contained dist-lite/
bext-lite-build ./my-site -o ./dist-lite

# 2. hand it to a mobile command
bext-lite mobile run ios --dist ./dist-lite

The CLI validates that --dist points at a directory containing a manifest.json. If --dist is missing, or the directory has no manifest, it prints an error that points you back at bext-lite-build and exits 2 (a usage error) rather than starting a doomed build.

init#

bext-lite mobile init [--platform ios|android|both] [--name <app>] \
                      [--id <bundle.id>] [--out <dir>] [--force]

Scaffolds the native shell project(s) once. Defaults: --platform both, --name BextApp, --id dev.bext.app, --out mobile.

It is non-destructive: it only ever writes under --out, and it refuses to write into an existing non-empty shell directory unless you pass --force. Each platform scaffold is oriented around this intended layout:

ios/App.xcodeproj/             Xcode project (name + bundle id parameterized)
ios/App/Info.plist             deep-link + capability declarations
ios/App/BextBridge.swift       NativeBridge callback -> Swift device APIs
ios/App/Resources/dist-lite/   the AOT-exported bundle (copied at build)

android/app/build.gradle                     Gradle module (applicationId parameterized)
android/app/src/main/AndroidManifest.xml     deep-link + permission declarations
android/app/src/main/java/.../BextBridge.kt  NativeBridge callback -> Kotlin device APIs
android/app/src/main/assets/dist-lite/       the AOT-exported bundle (copied at build)

The scaffold is a starting point; the real, checked-in shell lives at crates/bext-lite/templates/mobile-shell/ and is parameterized by app name and bundle id.

run#

bext-lite mobile run <ios|android> --dist <dist-lite-dir> [--device <id>]

Builds and launches the app on a simulator, emulator, or attached device. It validates --dist, checks the platform toolchain, and if the toolchain is present proceeds to build and launch. --device targets a specific simulator or device id.

build#

bext-lite mobile build <ios|android> --dist <dist-lite-dir> [--release]

Produces a store artifact: .ipa for iOS, .aab for Android. --release switches from a debug to a release configuration. Same --dist requirement and toolchain guard as run.

dev#

bext-lite mobile dev <ios|android> --dist <dist-lite-dir>

The live-reload loop: watch the site and re-export on change, then hot-swap the refreshed bundle into the running app via Tier-1 content OTA so the WebView reloads without a native rebuild. Native or runtime changes still need a full bext-lite mobile run.

Toolchain requirements and the guard#

Before any device build the CLI checks the platform toolchain and, when something is missing, prints the setup runbook and exits 3.

iOS requires:

  • a macOS host (Xcode is macOS-only)
  • xcodebuild and xcrun (the Xcode command-line tools)
rustup target add aarch64-apple-ios aarch64-apple-ios-sim
xcode-select --install        # or install full Xcode from the App Store
cargo install tauri-cli       # Tauri Mobile  (or: cargo install cargo-mobile2)

Android requires:

  • $ANDROID_HOME or $ANDROID_SDK_ROOT (the SDK location)
  • $ANDROID_NDK_HOME (the NDK location)
  • gradle
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
# install Android Studio (SDK + NDK) + a JDK, then export:
export ANDROID_HOME=<sdk> ANDROID_NDK_HOME=<ndk>
cargo install tauri-cli       # Tauri Mobile  (or: cargo install cargo-mobile2)
Note

Because the guard exits cleanly instead of failing opaquely, the same command is safe to run on a machine without the toolchain (a CI runner, a Linux dev box): it validates the export, reports what is absent, and stops. It does not leave a half-finished build behind.

How packaging works: the dist-lite resource#

The bundle-to-app copy is not a manual cp in the CLI. The shell template declares dist-lite as a bundled resource in tauri.conf.json:

"bundle": {
  "resources": ["dist-lite"]
}

So cargo tauri bundles dist-lite/ into the app package itself, and the shell reads it back at launch via resource_dir(), with a first-launch copy into the writable app-data dir so OTA swaps and OS app updates survive. The CLI exports, then delegates the packaging and the first-launch copy to the Tauri template. It does not shuffle files by hand.

The canonical recipe: scripts/mobile-shell-build.sh#

The full export-then-cargo tauri handoff that run and build wrap is codified as a runnable script:

scripts/mobile-shell-build.sh <site_dir> <ios|android>

It adds the Rust device targets, installs tauri-cli (^2) if absent, runs bext-lite-build <site> -o <shell>/dist-lite, then cargo tauri <platform> {init,build,dev} from the shell directory. It uses the same degrade style as the CLI: a missing tool prints exactly what is absent plus the install step and exits non-zero, so the script is safe to invoke on a box without the toolchain (it stops cleanly at the guard). Optional env: ACTION (build, dev, or init; default build), DIST_DIR, DEBUG=1, DEVICE, SKIP_EXPORT=1, SKIP_INSTALL=1.