NativeScript

Set up signed application-tree updates, runtime compatibility, assets, and file deltas.

Edit

Alpha integration

NativeScript support is experimental. Signed full-tree updates, images, and file deltas have been exercised on iOS and Android simulators/emulators. This is not a production certification: workers, power-loss recovery, native upgrades, and physical-device coverage remain release gates. Use a dedicated test environment.

Supported setup

Use @nitropush/nativescript, not @nitropush/react-native. This SDK shares NitroPush's native Swift/Kotlin engine but does not install React Native or Nitro Modules.

The validated Webpack templates are NativeScript CLI/core 9.1.1, Android 9.1.1, iOS 9.1.0, and @nativescript/webpack 5.0.38. The engine requires iOS 15+; the tested Android example uses minimum SDK 26. Vite, snapshots, and SwiftUI bootstrap are not supported by these hooks. Preparation stops if it cannot identify the supported startup location.

As checked on September 10, 2026, the public npm registry does not yet expose @nitropush/nativescript. Use the local workspace package or build a tarball:

# From the NitroPush monorepo, for local package development:
yarn workspace @nitropush/nativescript build
yarn workspace @nitropush/cli build
npm pack ./packages/nativescript

Then, from the consuming NativeScript app:

npm install /absolute/path/to/nitropush-nativescript-0.1.0-alpha.0.tgz

Replace that path with the tarball just created. The monorepo example already uses workspace:*. If you maintain a private registry or an alpha is published later, use the available version there. Verify SDK/CLI versions and nitropush release upload --help before proceeding; source support alone does not mean npm or the backend has been deployed.

1. Project, environment, and signing

Create a project with framework NativeScript, or reuse an existing NativeScript project. Frameworks cannot be switched after creation. Confirm its platform settings; publish platform-specific outputs separately.

nitropush login
nitropush whoami --json
 
# Only if a project/environment does not already exist:
nitropush app create --name "My NativeScript app" --framework nativescript
nitropush env create --app PROJECT_ID --name test --key-out ./nitropush-test-key.txt

The environment key is written once to a new 0600 file, never recovered from the server later. Store it in your private build configuration and ignore that file in Git. Do not log it.

Signing is required for NativeScript. Use an existing registered keypair where available. For a new trust root:

nitropush app signing-key generate \
  --app PROJECT_ID \
  --out ./nitropush-signing.pem \
  --public-out ./nitropush-signing.public.b64

The private PEM stays in your release pipeline and must be gitignored. The public file contains the complete base64 DER SPKI public key, not its SHA-256 pin. Set NITROPUSH_DEPLOYMENT_KEY and NITROPUSH_BUNDLE_PUBLIC_KEY in the native build environment using your secret/config manager. Never embed the private key. Do not regenerate a key for each release: changing the trust root requires native rebuilds.

2. Install the prepare hook

Add hooks/after-prepare/nitropush.js to the application:

module.exports = function ($projectData, hookArgs) {
  const platform = hookArgs.platform || hookArgs.prepareData?.platform;
  if (!platform) throw new Error('Missing NativeScript prepare platform');
  require('@nitropush/nativescript/scripts/prepare.cjs')
    .prepare($projectData.projectDir, platform.toLowerCase());
};

The hook injects client configuration, selects the app tree before native runtime startup, and writes platforms/<platform>/nitropush-runtime.json. Do not hand-edit generated native projects. A native release rebuild is required; a JS-only upload cannot add this bootstrap to an installed binary.

3. Configure once, confirm healthy rendering

import { configure, sync, InstallMode, SyncStatus } from '@nitropush/nativescript';
 
const client = configure(); // Module scope; no arguments or JS endpoint overrides.
 
// Invoke this from your first usable screen's loaded/after-render callback.
export async function onFirstScreenReady() {
  await client.notifyAppReady();
}
 
export async function checkForUpdates() {
  const status = await sync(
    client,
    { installMode: InstallMode.ON_NEXT_RESTART },
    (status, error) => {
      if (status === SyncStatus.UNKNOWN_ERROR) {
        // Show a retry message; do not log credentials or signed URLs.
      }
    },
  );
  return status;
}

Do not call notifyAppReady() during module initialization: it must represent a successfully rendered application. If confirmation is missing, the next launch may roll back.

Only ON_NEXT_RESTART is supported. UPDATE_INSTALLED means the signed tree is staged. Fully terminate the test process and reopen it to activate; navigating or backgrounding alone is not a cold restart. Debug builds keep the development loader.

NativeScript API (not the React Native API)

APIResult
configure()Singleton client from native build configuration
client.notifyAppReady()Confirm the active update is healthy
client.getCurrentPackage()Active package or null for the binary bundle
client.getPendingPackage()Staged package or null
client.clearPendingUpdate()Remove only the staged update
sync(client, options?, onStatus?)Coalesced update check/download/stage

Package metadata is { releaseId, label, appVersion, isPending }. Status values are CHECKING_FOR_UPDATE, UP_TO_DATE, UPDATE_INSTALLED, and UNKNOWN_ERROR. There is no public immediate restart, progress listener, configureWith, or resume/suspend installation API in this alpha.

4. Build and publish the complete app tree

Build a release-mode Webpack app using the same dependencies and native inputs as the installed binary. Use the emitted app directory with package.json, its main script, chunks, assets, CSS/XML, fonts, and workers—not the source app/ or the entire APK/IPA build directory.

Android's validated output is platforms/android/app/src/main/assets/app. For iOS, locate app/ inside the built .app for the exact release configuration (Xcode may use DerivedData). Do not assume an older platforms/ios/build directory is the installed build.

Read runtimeVersion from the matching platform's nitropush-runtime.json and pass it unchanged:

nitropush release upload \
  --project PROJECT_ID \
  --environment test \
  --platforms android \
  --runtime-version EXACT_RUNTIME_FINGERPRINT \
  --label "image-update" \
  --kind nativescript \
  --bundle-path ./platforms/android/app/src/main/assets/app \
  --signing-key ./nitropush-signing.pem

Repeat separately for iOS with its own runtime and built app/ directory. --runtime-version is the canonical flag; --app-version is a deprecated alias. NativeScript rejects *, unsigned releases, and incompatible project frameworks. --bundle runs RN/Expo bundling, not NativeScript.

Keep the emitted package.json and Android native class-registration file equal to the binary baseline. Changes to those, native plugins, permissions, or native configuration require a new binary. Use ~/assets/example.png for files included in the app tree; verify images after a cold OTA launch on both platforms.

5. Smaller file-delta updates

Add --delta to the same signed upload command after a compatible baseline release exists. The CLI compares the previous tree for the same project, environment, platform, runtime, and deployment-key generation:

  • Unchanged files are referenced by content hash without re-uploading their bytes.
  • Changed files use npdiff1 copy/insert patches when the patch is at least 20% smaller.
  • New/unrelated files are sent in full. Files deleted from the inventory disappear from the new tree.
  • The server reconstructs and verifies the complete signed target before publishing and retains full-file fallbacks.

An updated NativeScript native binary applies these patches automatically; it does not use the React Native enableDeltaUpdates flag or Hermes bsdiff4 decoder. Each base, patch, and reconstructed file is checked. Missing/corrupt bases or patches fall back to full files; unchanged cached files can be reused. The active tree is never patched in place.

A new native fingerprint starts with a full baseline. If the fingerprint changes unexpectedly, check lockfile/native inputs rather than forcing the previous runtime value.

Version, runtime fingerprint, and bundle hash

ValuePurpose
Version 1, 2, 3, …Server-assigned OTA sequence; starts at 1 per project/environment/runtime target. Upload reservations can leave gaps.
LabelHuman-readable release note/name; not a compatibility gate
Runtime fingerprintNative compatibility; includes platform, bootstrap ABI, packaged SDK native source, dependency versions, lockfile, config, and App_Resources
Bundle/file SHA-256Integrity and content-addressed caching for the actual OTA files

Keep both hashes. A JS or image change alters content hashes but normally remains compatible with the same native runtime. A native dependency change can break compatibility even when a JS bundle is unchanged. Removing runtime matching would let an incompatible signed update reach a device.

The fingerprint is deliberately conservative: even a JS-only dependency/lockfile change may require a new binary. Native edits outside the tracked inputs are unsupported.

Verify and troubleshoot

Before rollout: install a release binary → render/confirm readiness → upload a signed full tree → sync → cold restart → verify UI/assets → publish a signed delta → repeat on iOS and Android. Also verify offline launch, missing/corrupt asset rejection, rollback after failed first render, wrong-key rejection, native upgrade, lazy imports/workers, and interrupted installation.

SymptomCheck
Missing native bootstrapHook, build-time keys, supported template, then native rebuild
No eligible releaseProject/environment, exact runtime, platform, deployment generation, and rollout
Update staged but UI unchangedCold restart; inspect current vs pending package
Image missing only after OTAFile exists in signed inventory and ~/ resolves to the selected tree
Reverts next launchFirst-screen readiness confirmation and actual render failure
Delta savings absentSame runtime/base active, changed-file similarity, native decoder present
Bandwidth says not meteredSDK activity and manifest authorizations are not verified CDN egress

The dashboard's patch savings exclude unchanged cache hits, manifest/base64 overhead, and HTTP/TLS bytes. Installs include both full and delta paths. Native telemetry is already emitted by the SDK; do not add duplicate JavaScript analytics.