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):
- Command-Line Options: Explicit CLI flags (
--server,--app-id, etc.). - Environment Variables:
$OTA_SERVER_URL,$OTA_API_KEY, etc. - Active User Session: Saved session file
~/.tk_ota_push/credentials.json. - Configuration File:
shorebird.yamlat the root of the Flutter project.
Parameter Resolution Matrix
| Parameter | 1. CLI Flag | 2. Environment Variable | 3. Active Session | 4. shorebird.yaml | 5. Native Detection / Default |
|---|---|---|---|---|---|
app_id | --app-id | OTA_APP_ID, APP_ID | — | environments.<env>, flavors.<flavor>, app_id | build.gradle (applicationId) or project.pbxproj (PRODUCT_BUNDLE_IDENTIFIER) |
server | --server | OTA_SERVER_URL, SHOREBIRD_BASE_URL | — | base_url | http://localhost:8080 |
tenant_id | --tenant-id | OTA_TENANT_ID | session.tenantId | tenant_id | default |
token / API Key | — | OTA_API_KEY | session.accessToken (with auto-refresh) | — | None (unauthenticated request) |
channel | -c, --channel | — | — | channel | stable |
web_console_url | --web-url | OTA_WEB_CONSOLE_URL | session.webConsoleUrl | web_console_url | http://localhost:3000 |
countries | --countries | — | — | geo.default_countries or rollout.countries | null (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
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:
- The
applicationIdornamespaceinsidedefaultConfig. - When a
--flavoror--envis specified (e.g.,dev), it matches the correspondingproductFlavorsorbuildTypesblock and extracts variant-specificapplicationIdor concatenatesapplicationIdSuffix(e.g.,com.enterprise.app.dev). - As a final fallback, it parses
android/app/src/main/AndroidManifest.xmlfor thepackageattribute.
On iOS (project.pbxproj or Info.plist)
The _detectIosBundleId helper inspects:
ios/Runner.xcodeproj/project.pbxprojlooking forPRODUCT_BUNDLE_IDENTIFIER(filtering out test targets likeRunnerTests).- If a flavor is passed, it matches the identifier containing that flavor name.
- As a final fallback, it reads the
CFBundleIdentifierkey inios/Runner/Info.plist.
5. Practical Resolution Scenarios
Scenario A: Standard Monolithic App
pubspec.yamlpresentbuild.gradledefinesapplicationId "com.example.myapp"- No
shorebird.yamlexists
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.yamlcontains anenvironmentsblock- 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.