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

@webority/mobile-tools

v0.16.6

Published

Dev-time CLI for Webority React Native apps: one prebuild, a dependency guard, a device-aware run, per-environment launcher icons and over-the-air deploys, the same on Windows and macOS.

Readme

@webority/mobile-tools

The dev-time CLI for Webority React Native apps. It covers five jobs: compiling the environment config into the native projects, catching a stale node_modules before a 20-minute build, building and launching on one device, rendering per-environment launcher icons, and publishing over-the-air updates. It behaves the same on Windows (Git Bash, PowerShell, cmd) and macOS (zsh).

It is a devDependency. App code never imports it, and it adds nothing to the app bundle.

npm install --save-dev --save-exact @webority/mobile-tools

Node 24. Run every command from the app's root folder, where appsettings.json and package.json live.

Commands

prebuild

npx mobile-tools prebuild --env <Development|Staging|Production> [--platform android|ios]
npx mobile-tools prebuild --same-env [--platform android|ios]
  • Merges appsettings.json with appsettings.<Env>.json into appsettings.Compiled.json.
  • Stamps COMMIT (the short git sha, with -dirty when the app folder has changes other than the files prebuild writes) and BUILD_TIME (UTC, ISO 8601).
  • Fails, naming the key, when the merged settings lack APP_NAME, APP_ID, ENVIRONMENT, BASE_URL, VERSION_DISPLAY or VERSION_INTERNAL.
  • Fails when ENVIRONMENT differs from the --env given, when VERSION_INTERNAL is not a positive whole number, or when an APP_ID_SUFFIX disagrees with the environment.
  • Android (android/gradle.properties): sets APP_NAME, APP_ID, VERSION_DISPLAY and VERSION_INTERNAL. Every other line, comment and line ending stays as it was.
    • With flavors, APP_ID is the base id. Each flavor's applicationIdSuffix adds the suffix.
    • Without flavors, APP_ID is the suffixed id.
  • iOS:
    • Sets CFBundleDisplayName in ios/<name>/Info.plist.
    • In project.pbxproj, sets PRODUCT_BUNDLE_IDENTIFIER (suffixed), MARKETING_VERSION and CURRENT_PROJECT_VERSION. It also sets ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-<Env> when that icon set exists.
    • When another environment's icon set exists but this one's does not, prebuild fails, so a build never ships the wrong icon.
    • Copies <firebasePlistDir>/GoogleService-Info.<Env>.plist to ios/<name>/GoogleService-Info.plist. Without that file for the environment it fails when the app depends on @react-native-firebase/app (the Crashlytics build phase needs it), and otherwise removes the copy.
  • Firebase on Android: an app that depends on @react-native-firebase/app fails without android/app/google-services.json, whose absence would otherwise stop the Gradle build later with a less direct error.
  • Over-the-air updates, only when the settings carry OTA_UPDATE_BASE_URL and the platform's component id (OTA_ANDROID_COMPONENT_ID, OTA_IOS_COMPONENT_ID). An app without them is untouched.
    • Writes the expo-updates settings into android/app/src/main/AndroidManifest.xml (expo.modules.updates.* meta-data) and ios/<name>/Supporting/Expo.plist (EXUpdates* keys): enabled, the manifest URL <OTA_UPDATE_BASE_URL>/ota/manifest/<OTA_PROJECT_ID>/<component id>/<environment>, the runtime version VERSION_INTERNAL, check on launch ALWAYS, launch wait 0, and the code-signing certificate with metadata keyid main, alg rsa-v1_5-sha256.
    • OTA_CODE_SIGNING_CERT names the update server's PEM certificate, relative to the app folder. It is required, because the server signs every manifest and an app without the certificate cannot verify one. A missing OTA_PROJECT_ID also fails.
    • A missing AndroidManifest.xml or Expo.plist fails rather than being created: Xcode bundles only a plist the project lists.
  • Without --platform, it stamps every platform folder the app has.
  • --same-env recompiles the environment last compiled, which refreshes the build stamp.
  • Running it twice gives the same native files as running it once. A file whose content did not change is not rewritten.

| Environment | App id suffix | Android flavor | |---|---|---| | Development | .development | development | | Staging | .staging | staging | | Production | none | production |

sync

npx mobile-tools sync --check
npx mobile-tools sync
npx mobile-tools sync --stamp
npx mobile-tools hooks install
  • sync --check hashes the lockfile (package-lock.json or pnpm-lock.yaml), the dependency fields of package.json and every file under patches/. It compares that hash with the stamp in node_modules/.webority-install.json.
    • On macOS it also compares ios/Podfile.lock with ios/Pods/Manifest.lock when both exist.
    • On a mismatch it prints the fix command and exits 1, well inside a second.
  • sync installs from the lockfile and writes the stamp: npm ci, or pnpm install --frozen-lockfile when the app has pnpm-lock.yaml.
    • When a package with an android/ folder was added, removed or changed version, and the app has been built before, it runs gradlew generateCodegenArtifactsFromSchema. It then deletes android/app/.cxx and android/app/build/generated/autolinking.
    • On macOS it runs pod install (through bundle exec when the app has a Gemfile) when the Pods are missing or stale.
  • sync --stamp only records that node_modules matches the lockfile now. Run it from the app's postinstall, so a plain npm ci or pnpm install also leaves an accurate stamp.
  • hooks install writes git post-merge and post-checkout hooks that run sync --check after every pull and branch switch.
    • It honours core.hooksPath, and an app in a subfolder of the repo gets its own block.
    • A hook it did not write is kept as <hook>.chained and still runs first, with its exit status passed through.

run

npx mobile-tools run --env <Environment> [--platform android|ios] [--device <serial|name>]

Android (the default):

  1. Runs sync --check and stops on a stale tree.
  2. Lists adb devices. A device that answers on several serials (MuMu shows one instance as 127.0.0.1:16416, 127.0.0.1:7555 and emulator-5556) is shown once.
  3. Picks the device:
    • the one named by --device (any of its serials, or its model name);
    • otherwise the only device;
    • otherwise asks, on a terminal. Without a terminal it fails with the list.
  4. Reads the device's ABI and runs prebuild.
  5. Builds :app:assemble<Flavor>Debug (or :app:assembleDebug without flavors) with -PreactNativeArchitectures=<abi>, so only one ABI compiles.
  6. Installs with adb -s <serial> install -r on that device only.
  7. Reuses a Metro already serving this app. Otherwise it starts one detached and prints the path of its log; when another project holds 8081, the new Metro takes the next free port.
  8. Sets adb reverse tcp:8081 (and, for Development, the local API port). It then launches <applicationId>/<namespace>.MainActivity.

iOS (macOS only):

  1. Picks a booted simulator or a connected iPhone (xcrun simctl, xcrun devicectl).
  2. Runs prebuild.
  3. Builds with xcodebuild -workspace ios/<name>.xcworkspace -scheme <name> -configuration Debug -destination id=<udid> -derivedDataPath ios/build ONLY_ACTIVE_ARCH=YES.
  4. Installs and launches the app.
  5. Metro must be on 8081, where the iOS app looks.

The build log goes to node_modules/.cache/mobile-tools/. On a failure only the root error is printed, with the log path. That root error is Metro's Unable to resolve module when present, otherwise Gradle's first "What went wrong" block or the compiler's error: lines.

Every program is started by absolute path with an argument list, never through a shell string:

  • android/gradlew on macOS. android\gradlew.bat on Windows, through cmd.exe named by path, because Node will not start a .bat file any other way.
  • adb from ANDROID_HOME/platform-tools, falling back to PATH.
  • /usr/bin/xcrun and /usr/bin/xcodebuild.

NoDefaultCurrentDirectoryInExePath is removed from every child's environment. On any other OS an iOS command stops with one line.

icons

npx mobile-tools icons [--source <png|svg>]

Renders every environment's launcher icons from one square source: an SVG, or a PNG of at least 1024 px.

  • Android, per flavor, into android/app/src/<flavor>/res:
    • ic_launcher and ic_launcher_round in mipmap-mdpi through mipmap-xxxhdpi;
    • the adaptive ic_launcher_foreground, with mipmap-anydpi-v26 XML;
    • ic_launcher_background, taken from the source's corner colour.
    • Launcher icons of the same name in another format (.webp) are removed, because Android rejects the duplicate.
    • Skipped when flavors is false.
  • iOS: ios/<name>/Images.xcassets/AppIcon-<Env>.appiconset, a single 1024 px universal icon with no alpha channel. Xcode derives the other sizes. Prebuild selects the set.
  • Production is clean. Staging carries an orange STAGING ribbon across the bottom-right corner, and Development a green DEV one. The ribbon is SVG text in a generic sans-serif, with no font file.
  • The same source gives the same bytes on the same machine.

ota deploy

npx mobile-tools ota deploy --env <Staging|Production> [--platform android|ios] [--rollout <0-100>] [--notes <text>] [--critical]
npx mobile-tools ota deploy --complete

Publishes a JavaScript-only change over the air to the phones already on the current store build. A native change, a permission or an SDK bump goes through the stores instead.

  1. Reads every id from the app's settings for the environment, never from the command line:
    • OTA_ANDROID_COMPONENT_ID and OTA_IOS_COMPONENT_ID, the update server's component per platform;
    • APP_ID, the identity the server and the app check the update against;
    • VERSION_INTERNAL, the runtime version. An update reaches only phones whose build is exactly this number.
  2. Production only: asks the stores for the live build, through webority-play release tracks get and webority-appstore status. A platform whose live build is not VERSION_INTERNAL is skipped, because the update would reach nobody (for example, while the new build is still in review). --force publishes anyway. A lookup that fails is reported and the platform kept. --no-store-check skips the lookup, and --store-profile <name> picks the store CLIs' credential profile.
  3. Per platform: runs prebuild (which also writes the expo-updates settings, above), then expo export --platform <platform> into node_modules/.cache/mobile-tools/ota/, then weborityos ota deploy with --app-identifier and --yes.
  4. The first deploy uploads nothing. It raises an approval card per platform and the ids are recorded in node_modules/.cache/mobile-tools/ota/approvals.json. Once a person approves, ota deploy --complete uploads every recorded bundle. --approval-id <id> with --platform does the same for one platform in one run.
  5. Prebuilds the environment that was compiled before, so a Development checkout is left as it was.

--dry-run prints the commands after the store check, and changes nothing. weborityos must be on PATH and signed in (weborityos login). Development is refused: it runs from Metro.

mobile-tools.config.json

Optional, at the app root. Every key is optional. An unknown key or a wrong type fails, naming the key.

{
  "iosProjectName": "FieldApp",
  "flavors": true,
  "localApiPort": 62430,
  "iconSource": "assets/app-icon.svg",
  "firebasePlistDir": "ios/firebase"
}

| Key | Default | Meaning | |---|---|---| | iosProjectName | the single ios/*.xcodeproj | Xcode project, scheme and workspace name | | flavors | true | Android product flavors development, staging, production in dimension environment | | localApiPort | the port of appsettings.Development.json's BASE_URL when it is localhost | port run reverses for a Development build | | iconSource | none | source image for icons when --source is not given | | firebasePlistDir | ios/firebase | folder holding GoogleService-Info.<Env>.plist |

Typical package.json scripts

{
  "scripts": {
    "android": "mobile-tools run --env Development",
    "android:staging": "mobile-tools run --env Staging",
    "ios": "mobile-tools run --env Development --platform ios",
    "prebuild": "mobile-tools prebuild --same-env",
    "sync": "mobile-tools sync",
    "postinstall": "mobile-tools sync --stamp"
  }
}

postinstall keeps the stamp accurate after any install. After a pull, npx mobile-tools sync is still the one command to run: it also resets native codegen when a native package moved, which a plain install does not.