Skip to main content

Cascade Resolution and shorebird.yaml

To seamlessly integrate into single-flavor Flutter applications as well as enterprise multi-environment architectures (dev, staging, uat, prod), the tk-ota-push CLI implements a deterministic cascade resolution engine (lib/src/cli/project.dart).

This mechanism allows developers to run tk-ota-push release or tk-ota-push patch without redundant configuration, while ensuring that application identifiers, API URLs, and cryptographic keys are resolved with absolute priority given to explicit parameters.


1. Global Parameter Resolution Hierarchy​

When executing any command, configuration parameters are resolved following a strict precedence hierarchy (from highest to lowest priority):

Configuration Precedence Hierarchy
  1. Command-Line Options: Explicit CLI flags (--server, --app-id, etc.).
  2. Environment Variables: $OTA_SERVER_URL, $OTA_API_KEY, etc.
  3. Active User Session: Saved session file ~/.tk_ota_push/credentials.json.
  4. Configuration File: shorebird.yaml at the root of the Flutter project.

Parameter Resolution Matrix​

Parameter1. CLI Flag2. Environment Variable3. Active Session4. shorebird.yaml5. Native Detection / Default
app_id--app-idOTA_APP_ID, APP_ID—environments.<env>, flavors.<flavor>, app_idbuild.gradle (applicationId) or project.pbxproj (PRODUCT_BUNDLE_IDENTIFIER)
server--serverOTA_SERVER_URL, SHOREBIRD_BASE_URL—base_urlhttp://localhost:8080
tenant_id--tenant-idOTA_TENANT_IDsession.tenantIdtenant_iddefault
token / API Key—OTA_API_KEYsession.accessToken (with auto-refresh)—None (unauthenticated request)
channel-c, --channel——channelstable
web_console_url--web-urlOTA_WEB_CONSOLE_URLsession.webConsoleUrlweb_console_urlhttp://localhost:3000
countries--countries——geo.default_countries or rollout.countriesnull (Worldwide / All countries)

2. The 9-Level app_id Cascade Resolution (resolveEffectiveAppId)​

The application identifier (app_id) is the primary key linking your Flutter project to records in the database OTA database. The FlutterProject.resolveEffectiveAppId method inspects 9 successive authority levels:

1. Explicit CLI Flag: --app-id <id>
└── Absolute override; takes precedence over everything.

2. Direct injection via --dart-define:
└── Inspects APP_ID, SHOREBIRD_APP_ID, APPLICATION_ID, or OTA_APP_ID.

3. File injection via --dart-define-from-file:
└── Parses referenced JSON, YAML, or .env files looking for the same keys.

4. Active Environment Name Deduction (deduceEnvironmentName):
├── --env <name> or --flavor <name>
├── --dart-define ENV=... or ENVIRONMENT=... or FLAVOR=...
├── --target lib/main_<env>.dart (e.g., lib/main_dev.dart -> "dev")
└── --dart-define-from-file env/<env>.json (e.g., env/staging.json -> "staging")

5. Environment Mapping in shorebird.yaml:
├── environments.<env> section
└── flavors.<env> section

6. Project Local Environment Files:
└── Searches for env/<env>.json, config/<env>.json, .env.<env>, env/.env.<env>, environments/<env>.json.

7. Root Field in shorebird.yaml:
└── app_id: "..."

8. System Environment Variables:
└── $OTA_APP_ID or $APP_ID

9. Automatic Deterministic Native Detection:
├── Android: android/app/build.gradle[.kts] (applicationId, namespace, productFlavors, buildTypes)
└── iOS: ios/Runner.xcodeproj/project.pbxproj (PRODUCT_BUNDLE_IDENTIFIER) or ios/Runner/Info.plist

3. Comprehensive shorebird.yaml Specification​

The shorebird.yaml file must reside at the root of your Flutter project (adjacent to pubspec.yaml). Here is a full enterprise configuration example:

# ==============================================================================
# TkushPatch / Shorebird Sovereign OTA Configuration
# ==============================================================================

# Default application identifier (universal fallback)
app_id: "com.enterprise.superapp"

# Multi-tenant organization identifier
tenant_id: "atlantic-banking"

# On-Premise TkushPatch Server Control Plane URL
base_url: "https://ota.cloud.enterprise.internal"

# Web Administration Console URL (used by tk-ota-push login)
web_console_url: "https://console-ota.cloud.enterprise.internal"

# Default distribution channel
channel: "stable"

# Public signing key (Base64) synced from the server
patch_public_key: |
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyN6K7...

# ------------------------------------------------------------------------------
# Multi-Environment / Multi-Flavor Mapping
# ------------------------------------------------------------------------------
environments:
dev: "com.enterprise.superapp.dev"
staging: "com.enterprise.superapp.staging"
uat: "com.enterprise.superapp.uat"
prod: "com.enterprise.superapp"

# Shorebird-compatible flavor aliases
flavors:
dev: "com.enterprise.superapp.dev"
staging: "com.enterprise.superapp.staging"
prod: "com.enterprise.superapp"

# ------------------------------------------------------------------------------
# Default Geo-Fencing (Geo-Rollout)
# ------------------------------------------------------------------------------
geo:
default_countries:
- CI
- SN
- CM
- BF
Automatic shorebird.yaml Injection

When a Flutter project lacks an explicit app_id in its shorebird.yaml, the ensureShorebirdYamlAppId() method automatically inserts the resolved app_id at the top of the file before building. This guarantees that the C++/Rust Shorebird runtime engine bakes the ID into the compiled app assets.


4. Zero-Config Native Detection​

If you have not created or configured shorebird.yaml yet, tk-ota-push automatically detects the native application ID:

On Android (build.gradle.kts or build.gradle)​

The _detectAndroidApplicationId helper inspects:

  1. The applicationId or namespace inside defaultConfig.
  2. When a --flavor or --env is specified (e.g., dev), it matches the corresponding productFlavors or buildTypes block and extracts variant-specific applicationId or concatenates applicationIdSuffix (e.g., com.enterprise.app.dev).
  3. As a final fallback, it parses android/app/src/main/AndroidManifest.xml for the package attribute.

On iOS (project.pbxproj or Info.plist)​

The _detectIosBundleId helper inspects:

  1. ios/Runner.xcodeproj/project.pbxproj looking for PRODUCT_BUNDLE_IDENTIFIER (filtering out test targets like RunnerTests).
  2. If a flavor is passed, it matches the identifier containing that flavor name.
  3. As a final fallback, it reads the CFBundleIdentifier key in ios/Runner/Info.plist.

5. Practical Resolution Scenarios​

Scenario A: Standard Monolithic App​

  • pubspec.yaml present
  • build.gradle defines applicationId "com.example.myapp"
  • No shorebird.yaml exists
tk-ota-push release 1.0.0+1 -p android

Outcome: The app_id is automatically detected as com.example.myapp. A shorebird.yaml file is created with app_id: "com.example.myapp".

Scenario B: Multi-Flavor with JSON Configuration File​

  • shorebird.yaml contains an environments block
  • Command run with --dart-define-from-file=env/staging.json
tk-ota-push patch 1.0.0+1 -p android --dart-define-from-file=env/staging.json

Outcome: deduceEnvironmentName() detects staging from the filename env/staging.json, then retrieves the associated app_id configured in environments.staging.