@_nazmiforreal/flutter-ota 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/README.md +112 -18
  2. package/bin/flutter-ota-linux-x64 +0 -0
  3. package/bin/flutter-ota.js +12 -11
  4. package/dart-src/packages/cli-tools/README.md +146 -2
  5. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
  6. package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
  9. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
  10. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +36 -8
  11. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
  12. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
  13. package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
  14. package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
  15. package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
  16. package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
  17. package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
  18. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
  19. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
  20. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
  21. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
  22. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
  23. package/dart-src/root/README.md +334 -0
  24. package/dart-src/root/analysis_options.yaml +21 -0
  25. package/dart-src/root/android/build.gradle +116 -0
  26. package/dart-src/root/android/consumer-rules.pro +23 -0
  27. package/dart-src/root/android/settings.gradle +1 -0
  28. package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
  29. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
  30. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
  31. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
  32. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
  33. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
  34. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
  35. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
  36. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
  37. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
  38. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
  39. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
  40. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
  41. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
  42. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
  43. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
  44. package/dart-src/root/bin/pack.dart +182 -0
  45. package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
  46. package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
  47. package/dart-src/root/lib/src/aws_update_source.dart +97 -0
  48. package/dart-src/root/lib/src/blacklist.dart +54 -0
  49. package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
  50. package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
  51. package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
  52. package/dart-src/root/lib/src/io.dart +48 -0
  53. package/dart-src/root/lib/src/io_stub.dart +19 -0
  54. package/dart-src/root/lib/src/ota_progress_overlay.dart +1186 -0
  55. package/dart-src/root/lib/src/pack/pack.dart +434 -0
  56. package/dart-src/root/lib/src/patch_info.dart +12 -0
  57. package/dart-src/root/lib/src/patcher_channel.dart +160 -0
  58. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
  59. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
  60. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
  61. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
  62. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
  63. package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
  64. package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
  65. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
  66. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
  67. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
  68. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
  69. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
  70. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
  71. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
  72. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
  73. package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
  74. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
  75. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
  76. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
  77. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
  78. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
  79. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
  80. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
  81. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
  82. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
  83. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
  84. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
  85. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
  86. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
  87. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
  88. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
  89. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
  90. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
  91. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
  92. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
  93. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
  94. package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
  95. package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
  96. package/dart-src/root/lib/src/shared_update_check.dart +131 -0
  97. package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
  98. package/dart-src/root/pubspec.yaml +54 -0
  99. package/package.json +1 -1
  100. package/scripts/postinstall.js +17 -13
  101. package/scripts/vendor.sh +22 -7
  102. package/dart-src/packages/core/CHANGELOG.md +0 -21
  103. package/dart-src/packages/core/README.md +0 -23
  104. package/dart-src/packages/core/example/lib/main.dart +0 -69
  105. package/dart-src/packages/core/pubspec.yaml +0 -13
  106. package/dart-src/packages/core/test/rollout_test.dart +0 -135
  107. package/dart-src/packages/core/test/semver_test.dart +0 -55
  108. package/dart-src/packages/core/test/semver_vectors.json +0 -1
  109. package/dart-src/packages/core/test/types_test.dart +0 -131
  110. package/dart-src/packages/core/test/uuid_test.dart +0 -40
  111. package/dart-src/packages/core/tool/dbg.dart +0 -7
  112. package/dart-src/plugins/aws/.gitkeep +0 -0
  113. package/dart-src/plugins/aws/CHANGELOG.md +0 -23
  114. package/dart-src/plugins/aws/LICENSE +0 -21
  115. package/dart-src/plugins/aws/README.md +0 -28
  116. package/dart-src/plugins/aws/example/lib/main.dart +0 -60
  117. package/dart-src/plugins/aws/pubspec.yaml +0 -30
  118. package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
  119. package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
  120. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
  121. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  122. package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
  123. package/dart-src/plugins/cloudflare/LICENSE +0 -21
  124. package/dart-src/plugins/cloudflare/README.md +0 -25
  125. package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
  126. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
  127. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
  128. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
  129. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
  130. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
  131. package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
  132. package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
  133. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
  134. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
  135. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
  136. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
  137. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
  138. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
  139. package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
  140. package/dart-src/plugins/plugin-core/LICENSE +0 -21
  141. package/dart-src/plugins/plugin-core/README.md +0 -21
  142. package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
  143. package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
  144. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
  145. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
  146. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
  147. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
  148. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
  149. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
  150. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
  151. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
  152. package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
  153. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
  154. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
  155. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
  156. package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
  157. package/dart-src/plugins/pocketbase/LICENSE +0 -21
  158. package/dart-src/plugins/pocketbase/README.md +0 -54
  159. package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
  160. package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
  161. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
  162. package/dart-src/plugins/postgres/.gitkeep +0 -0
  163. package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
  164. package/dart-src/plugins/postgres/LICENSE +0 -21
  165. package/dart-src/plugins/postgres/README.md +0 -29
  166. package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
  167. package/dart-src/plugins/postgres/pubspec.yaml +0 -26
  168. package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
  169. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
  170. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
  171. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
  172. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
  173. package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
  174. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
  175. package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
  176. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
  177. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
  178. package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
  179. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
  180. package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
  181. package/dart-src/plugins/supabase/LICENSE +0 -21
  182. package/dart-src/plugins/supabase/README.md +0 -53
  183. package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
  184. package/dart-src/plugins/supabase/pubspec.yaml +0 -27
  185. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
  186. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
  187. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
  188. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
  189. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
  190. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
  191. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
  192. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
  193. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
  194. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
  195. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
  196. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
  197. package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
  198. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
  199. /package/dart-src/{packages/core → root}/LICENSE +0 -0
  200. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
  201. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
  202. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
  203. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
  204. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
  205. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
  206. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
  207. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
  208. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
  209. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
  210. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
  211. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
  212. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
  213. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
  214. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
  215. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
  216. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
  217. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
  218. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
  219. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
  220. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
  221. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
  222. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
  223. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
  224. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
  225. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
  226. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
  227. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
  228. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
  229. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
  230. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
  231. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
  232. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
  233. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
  234. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
  235. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
  236. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
  237. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
  238. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
  239. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
  240. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
  241. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
  242. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
  243. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
  244. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
  245. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
  246. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
  247. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
  248. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
  249. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
  250. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
  251. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
  252. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
  253. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
  254. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
  255. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
  256. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
  257. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
  258. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
  259. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/types.dart +0 -0
@@ -0,0 +1,131 @@
1
+ import 'dart:async';
2
+
3
+ import 'package:flutter_ota_kit/src/pkg/core/flutter_ota_kit_core.dart'
4
+ show
5
+ AppUpdateStatus,
6
+ AppVersionGetBundlesArgs,
7
+ FingerprintGetBundlesArgs,
8
+ GetBundlesArgs,
9
+ nilUuid,
10
+ Platform,
11
+ UpdateInfo,
12
+ UpdateStrategy;
13
+ import 'package:flutter_ota_kit/src/pkg/client/flutter_ota_kit_client.dart'
14
+ show ServerUpdateResult;
15
+ import 'package:flutter_ota_kit/src/pkg/plugin_core/flutter_ota_kit_plugin_core.dart'
16
+ show DatabasePlugin, StoragePlugin;
17
+
18
+ import 'patch_info.dart' show PatchInfo;
19
+ import 'app_version_resolver.dart';
20
+
21
+ final _uuidRe = RegExp(
22
+ r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$',
23
+ );
24
+
25
+ /// Backend-agnostic update-check orchestration shared by every hosted source
26
+ /// (Supabase / Postgres / Cloudflare / AWS / PocketBase).
27
+ ///
28
+ /// The only backend-specific pieces are the already-constructed [DatabasePlugin]
29
+ /// and [StoragePlugin] plus the targeting fields pulled from each source config.
30
+ /// Everything else — bundle-id normalization, `appVersion`/fingerprint argument
31
+ /// building (with runtime `appVersion` auto-detection), the `getUpdateInfo` call,
32
+ /// download-URL resolution and the `ServerUpdateResult` mapping — is identical
33
+ /// across backends and lives here exactly once.
34
+ ///
35
+ /// [timeout] bounds the two HTTP round-trips (DB query + signed-URL mint).
36
+ /// When the timeout fires, the in-flight HTTP request is cancelled via
37
+ /// `Future.timeout`, which propagates a `TimeoutException` that callers can
38
+ /// catch and surface to the user (e.g. "no internet — try again later").
39
+ Future<ServerUpdateResult> performSharedUpdateCheck({
40
+ required DatabasePlugin db,
41
+ required StoragePlugin storage,
42
+ required String channel,
43
+ required Platform platform,
44
+ required UpdateStrategy updateStrategy,
45
+ required String? appVersion,
46
+ required String? fingerprintHash,
47
+ required String minBundleId,
48
+ String? currentBundleId,
49
+ Duration timeout = const Duration(seconds: 10),
50
+ }) async {
51
+ final bundleId =
52
+ (currentBundleId != null && _uuidRe.hasMatch(currentBundleId))
53
+ ? currentBundleId
54
+ : nilUuid;
55
+
56
+ final GetBundlesArgs args;
57
+ if (updateStrategy == UpdateStrategy.fingerprint) {
58
+ args = FingerprintGetBundlesArgs(
59
+ channel: channel,
60
+ platform: platform,
61
+ bundleId: bundleId,
62
+ minBundleId: minBundleId,
63
+ fingerprintHash: fingerprintHash ?? '',
64
+ );
65
+ } else {
66
+ final resolvedAppVersion = await resolveAppVersion(appVersion);
67
+ args = AppVersionGetBundlesArgs(
68
+ channel: channel,
69
+ platform: platform,
70
+ bundleId: bundleId,
71
+ minBundleId: minBundleId,
72
+ appVersion: resolvedAppVersion,
73
+ );
74
+ }
75
+
76
+ final UpdateInfo? info = await db
77
+ .getUpdateInfo(args)
78
+ .timeout(
79
+ timeout,
80
+ onTimeout: () => throw TimeoutException(
81
+ 'update check timed out after ${timeout.inSeconds}s while querying the backend',
82
+ ),
83
+ );
84
+ if (info == null) return ServerUpdateResult.upToDate();
85
+
86
+ final storageUri = info.storageUri;
87
+ // Server signals rollback with ROLLBACK status and null/empty storageUri
88
+ if (info.status == 'ROLLBACK') {
89
+ return ServerUpdateResult(
90
+ isUpToDate: false,
91
+ patch: null,
92
+ status: AppUpdateStatus.rollback,
93
+ shouldForceUpdate: info.shouldForceUpdate,
94
+ id: info.id,
95
+ message: info.message,
96
+ );
97
+ }
98
+ if (storageUri == null || storageUri.isEmpty) {
99
+ return ServerUpdateResult.upToDate();
100
+ }
101
+
102
+ final runtime = storage.profiles.runtime;
103
+ if (runtime == null) return ServerUpdateResult.upToDate();
104
+ final dl = await runtime
105
+ .getDownloadUrl(storageUri)
106
+ .timeout(
107
+ timeout,
108
+ onTimeout: () => throw TimeoutException(
109
+ 'update check timed out after ${timeout.inSeconds}s while minting the download URL',
110
+ ),
111
+ );
112
+ final fileUrl = dl['fileUrl'];
113
+ if (fileUrl == null || fileUrl.isEmpty) {
114
+ return ServerUpdateResult.upToDate();
115
+ }
116
+
117
+ final patch = PatchInfo(
118
+ version: info.id,
119
+ patchUrl: fileUrl,
120
+ md5: info.fileHash ?? '',
121
+ );
122
+ return ServerUpdateResult(
123
+ isUpToDate: false,
124
+ patch: patch,
125
+ status: AppUpdateStatus.update,
126
+ shouldForceUpdate: info.shouldForceUpdate,
127
+ id: info.id,
128
+ message: info.message,
129
+ gitCommitHash: info.gitCommitHash,
130
+ );
131
+ }
@@ -0,0 +1,93 @@
1
+ import 'package:flutter_ota_kit/src/pkg/core/flutter_ota_kit_core.dart'
2
+ show nilUuid, Platform, UpdateStrategy;
3
+ import 'package:flutter_ota_kit/src/pkg/client/flutter_ota_kit_client.dart'
4
+ show ServerUpdateResult;
5
+ import 'package:flutter_ota_kit/src/pkg/supabase/flutter_ota_kit_supabase.dart'
6
+ show
7
+ supabaseDatabase,
8
+ supabaseStorage,
9
+ SupabaseServiceRoleConfig,
10
+ SupabaseStorageConfig;
11
+
12
+ import 'shared_update_check.dart';
13
+
14
+ /// Configuration for a hosted **Supabase** update source.
15
+ ///
16
+ /// Talks to the Supabase project directly over its REST API
17
+ /// (PostgREST `bundles` table + Storage signed URLs). No separate server is
18
+ /// required — Supabase *is* the backend.
19
+ class SupabaseUpdateConfig {
20
+ const SupabaseUpdateConfig({
21
+ required this.supabaseUrl,
22
+ this.anonKey,
23
+ this.serviceRoleKey,
24
+ required this.bucket,
25
+ required this.channel,
26
+ required this.platform,
27
+ required this.updateStrategy,
28
+ this.appVersion,
29
+ this.fingerprintHash,
30
+ this.sdkVersion = '1.0.0',
31
+ this.cohort,
32
+ this.minBundleId = nilUuid,
33
+ });
34
+
35
+ final String supabaseUrl;
36
+ final String? anonKey;
37
+ final String? serviceRoleKey;
38
+ final String bucket;
39
+ final String channel;
40
+ final Platform platform;
41
+ final UpdateStrategy updateStrategy;
42
+ final String? appVersion;
43
+ final String? fingerprintHash;
44
+ final String sdkVersion;
45
+ final String? cohort;
46
+
47
+ /// Build-time scan floor (mirrors hot-updater's `getMinBundleId()`). Bundles
48
+ /// with an id older than this are never served.
49
+ final String minBundleId;
50
+ }
51
+
52
+ /// Device-side update source backed by a hosted Supabase project. Performs the
53
+ /// update check directly against PostgREST and resolves the patch download URL
54
+ /// from Supabase Storage (signed URL). Mirrors hot-updater's `createSupabaseSource`.
55
+ class SupabaseUpdateSource {
56
+ const SupabaseUpdateSource();
57
+
58
+ /// Reads the latest enabled bundle for the configured channel/platform/target
59
+ /// and resolves its download URL.
60
+ Future<ServerUpdateResult> check(
61
+ SupabaseUpdateConfig config, {
62
+ String? currentBundleId,
63
+ Duration timeout = const Duration(seconds: 10),
64
+ }) async {
65
+ final dbConfig = SupabaseServiceRoleConfig(
66
+ supabaseUrl: config.supabaseUrl,
67
+ supabaseServiceRoleKey: config.serviceRoleKey,
68
+ supabaseAnonKey: config.anonKey,
69
+ );
70
+ final storageConfig = SupabaseStorageConfig(
71
+ supabaseUrl: config.supabaseUrl,
72
+ supabaseServiceRoleKey: config.serviceRoleKey,
73
+ supabaseAnonKey: config.anonKey,
74
+ bucketName: config.bucket,
75
+ );
76
+
77
+ final db = supabaseDatabase(dbConfig)();
78
+ final storage = supabaseStorage(storageConfig);
79
+
80
+ return performSharedUpdateCheck(
81
+ db: db,
82
+ storage: storage,
83
+ channel: config.channel,
84
+ platform: config.platform,
85
+ updateStrategy: config.updateStrategy,
86
+ appVersion: config.appVersion,
87
+ fingerprintHash: config.fingerprintHash,
88
+ minBundleId: config.minBundleId,
89
+ currentBundleId: currentBundleId,
90
+ timeout: timeout,
91
+ );
92
+ }
93
+ }
@@ -0,0 +1,54 @@
1
+ name: flutter_ota_kit
2
+ description: Open-source, self-hosted code push (OTA updates) for Flutter Android. Patches Dart AOT libapp.so and assets on cold start with integrity checks and crash rollback.
3
+ version: 0.2.1
4
+ homepage: https://github.com/HYPER12755/flutter-ota-kit
5
+ repository: https://github.com/HYPER12755/flutter-ota-kit
6
+ issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
7
+ documentation: https://github.com/HYPER12755/flutter-ota-kit#readme
8
+ license: MIT
9
+
10
+ topics:
11
+ - flutter
12
+ - android
13
+ - code-push
14
+ - hot-update
15
+ - ota-update
16
+
17
+ environment:
18
+ sdk: '>=3.13.2 <4.0.0'
19
+ flutter: '>=3.47.2'
20
+
21
+ dependencies:
22
+ flutter:
23
+ sdk: flutter
24
+ plugin_platform_interface: '>=2.0.2 <3.0.0'
25
+ package_info_plus: ^10.0.0
26
+ restart_app: ^1.9.1
27
+ # The npm `flutter-ota` CLI (`flutter_ota_kit_cli`) is intentionally NOT a pub
28
+ # dependency; `dart run flutter_ota_kit:pack` is implemented in `lib/src/pack`.
29
+ archive: ^4.0.0
30
+ args: '>=2.4.0 <3.0.0'
31
+ crypto: '>=3.0.3 <4.0.0'
32
+ # Merged from the former sub-packages (core, plugin-core, client, and the
33
+ # supabase/postgres/cloudflare/aws/pocketbase backends), now bundled directly
34
+ # under lib/src/pkg/. Their external dependencies are unioned here.
35
+ http: ^1.2.0
36
+ path: ^1.9.0
37
+ mime: ^2.0.0
38
+ supabase: ^2.14.0
39
+ postgres: ^3.4.0
40
+ pointycastle: ^4.0.0
41
+ asn1lib: ^1.5.4
42
+
43
+ dev_dependencies:
44
+ flutter_test:
45
+ sdk: flutter
46
+ flutter_lints: ^6.0.0
47
+ test: ^1.25.0
48
+
49
+ flutter:
50
+ plugin:
51
+ platforms:
52
+ android:
53
+ package: com.flutter_patcher.flutter_patcher
54
+ pluginClass: FlutterPatcherPlugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_nazmiforreal/flutter-ota",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "Command-line interface for flutter_ota_kit — an OTA code-push tool for Flutter, hot-updater compatible.",
5
5
  "bin": {
6
6
  "flutter-ota": "./bin/flutter-ota.js"
@@ -4,13 +4,15 @@
4
4
  // Post-install: ensure a runnable flutter-ota binary exists next to the
5
5
  // launcher for the current platform+arch. If a prebuilt
6
6
  // `flutter-ota-<os>-<arch>` binary ships in `bin/`, use it. Otherwise, when
7
- // the Dart SDK is present, compile one from the bundled Dart source so the
7
+ // the Flutter SDK is present, compile one from the bundled Dart source so the
8
8
  // package works on architectures we did not prebuild (e.g. arm64).
9
9
  //
10
- // We explicitly run `dart pub get` first so dependency resolution is reliable
11
- // (a stale/locked tree would otherwise fail to fetch packages). If compilation
12
- // cannot be performed we warn (and exit 0) rather than failing the whole npm
13
- // install — the launcher prints a clear "how to build" message at runtime.
10
+ // NOTE (v0.2.0+): the CLI now depends on the merged `flutter_ota_kit` package,
11
+ // which is a Flutter plugin. Dependency resolution therefore needs the Flutter
12
+ // SDK (`flutter pub get`), not just the Dart SDK. The compiled executable
13
+ // itself is still a plain Dart binary (the CLI only touches the pure-Dart
14
+ // backend code, not Flutter widgets). If Flutter is unavailable we warn and
15
+ // exit 0 rather than failing the whole npm install.
14
16
 
15
17
  const { spawnSync } = require('child_process');
16
18
  const fs = require('fs');
@@ -50,11 +52,13 @@ if (fs.existsSync(target)) {
50
52
  }
51
53
 
52
54
  const dartCheck = spawnSync('dart', ['--version'], { stdio: 'ignore' });
53
- if (dartCheck.status !== 0) {
55
+ const flutterCheck = spawnSync('flutter', ['--version'], { stdio: 'ignore' });
56
+ if (dartCheck.status !== 0 || flutterCheck.status !== 0) {
54
57
  console.warn(
55
- `flutter-ota: Dart SDK not found; skipping build. Provide a prebuilt ` +
58
+ `flutter-ota: Flutter SDK not found; skipping build. Provide a prebuilt ` +
56
59
  `binary (bin/flutter-ota-${platformName}-${archName}${ext}) or install ` +
57
- 'the Dart SDK (https://dart.dev).',
60
+ 'the Flutter SDK (https://flutter.dev). The CLI depends on the ' +
61
+ '`flutter_ota_kit` Flutter package, so `flutter pub get` is required.',
58
62
  );
59
63
  process.exit(0);
60
64
  }
@@ -64,13 +68,13 @@ if (!fs.existsSync(path.join(cliDir, 'pubspec.yaml'))) {
64
68
  process.exit(0);
65
69
  }
66
70
 
67
- console.log('flutter-ota: resolving Dart dependencies (dart pub get)...');
68
- const getRes = spawnSync('dart', ['pub', 'get'], { stdio: 'inherit', cwd: cliDir });
71
+ console.log('flutter-ota: resolving dependencies (flutter pub get)...');
72
+ const getRes = spawnSync('flutter', ['pub', 'get'], { stdio: 'inherit', cwd: cliDir });
69
73
  if (getRes.status !== 0) {
70
74
  console.error(
71
- 'flutter-ota: `dart pub get` failed (check your network / Dart version). ' +
72
- `To build manually:\n cd ${cliDir} && dart compile exe bin/flutter_ota_kit.dart ` +
73
- `-o ${target}`,
75
+ 'flutter-ota: `flutter pub get` failed (check your network / Flutter version). ' +
76
+ `To build manually:\n cd ${cliDir} && flutter pub get && dart compile exe ` +
77
+ `bin/flutter_ota_kit.dart -o ${target}`,
74
78
  );
75
79
  process.exit(0);
76
80
  }
package/scripts/vendor.sh CHANGED
@@ -10,14 +10,28 @@ SRC="$ROOT"
10
10
  echo "Vendoring Dart source from $SRC into $DST"
11
11
 
12
12
  rm -rf "$DST"
13
- mkdir -p "$DST/packages" "$DST/plugins"
13
+ mkdir -p "$DST/packages"
14
14
 
15
- for p in packages/core packages/cli-tools; do
16
- cp -R "$SRC/$p" "$DST/$p"
17
- done
18
- for p in plugins/cloudflare plugins/plugin-core plugins/postgres plugins/aws plugins/supabase plugins/pocketbase; do
19
- cp -R "$SRC/$p" "$DST/$p"
15
+ # Single-package layout (v0.2.0+): the former sub-packages (core, plugin-core,
16
+ # client, and the supabase/postgres/cloudflare/aws/pocketbase backends) now live
17
+ # INSIDE the root `flutter_ota_kit` package under lib/src/pkg/. So we only vendor
18
+ # the root package + the CLI. The CLI depends on the root via a path override.
19
+ #
20
+ # We copy the root package's publishable pieces (lib, android, pubspec, etc.)
21
+ # and the CLI. `example/` and build artifacts are dropped.
22
+ mkdir -p "$DST/root"
23
+ for item in lib android bin pubspec.yaml analysis_options.yaml LICENSE README.md; do
24
+ [ -e "$SRC/$item" ] && cp -R "$SRC/$item" "$DST/root/$item"
20
25
  done
26
+ cp -R "$SRC/packages/cli-tools" "$DST/packages/cli-tools"
27
+
28
+ # Repoint the CLI's `flutter_ota_kit` path override at the vendored root copy.
29
+ CLI_PUBSPEC="$DST/packages/cli-tools/pubspec.yaml"
30
+ if [ -f "$CLI_PUBSPEC" ]; then
31
+ # dependency_overrides -> flutter_ota_kit -> path: ../.. becomes ../../root
32
+ perl -0pi -e 's{(flutter_ota_kit:\s*\n\s*path:\s*)\.\.\/\.\.}{${1}../../root}g' "$CLI_PUBSPEC" 2>/dev/null || \
33
+ sed -i 's|path: \.\./\.\.|path: ../../root|' "$CLI_PUBSPEC"
34
+ fi
21
35
 
22
36
  # Drop tooling/lock artifacts so `dart pub get` resolves fresh on install.
23
37
  find "$DST" -name pubspec.lock -delete
@@ -26,7 +40,8 @@ find "$DST" -name build -type d -prune -exec rm -rf {} +
26
40
  find "$DST" -name .git -type d -prune -exec rm -rf {} +
27
41
  find "$DST" -name coverage -type d -prune -exec rm -rf {} +
28
42
 
29
- # Sync migration SQL files so the binary can find them at ../migrations/<backend>/
43
+ # Sync migration SQL files so the binary can find them at ../migrations/<backend>/.
44
+ # The SQL/DDL data still lives in the (non-Dart) plugins/* data dirs in the repo.
30
45
  MIG_DST="$(cd "$(dirname "$0")/.." && pwd)/migrations"
31
46
  rm -rf "$MIG_DST"
32
47
 
@@ -1,21 +0,0 @@
1
- ## 0.1.8
2
-
3
- - Release 2026-09-10
4
-
5
- ## 0.1.8
6
-
7
- - Automated release 2026-09-10
8
-
9
- ## 0.1.7
10
-
11
- - Added comprehensive documentation for all public APIs.
12
- - Improved `Bundle` equality and hashCode implementation.
13
- - Fixed `toSqlJson()` nullable field handling.
14
- - Added `semver` exports to barrel file.
15
-
16
- ## 0.1.6
17
-
18
- - Added `gitCommitHash` field to `UpdateInfo` for commit tracking.
19
- - General maintenance.
20
-
21
- ## 0.1.5
@@ -1,23 +0,0 @@
1
- # flutter_ota_kit_core
2
-
3
- Pure-Dart core of the [flutter_ota_kit](https://github.com/HYPER12755/flutter-ota-kit) OTA toolkit. It has **no Flutter dependency** and is shared by every other package (client, plugin-core, and all four backends).
4
-
5
- ## What's inside
6
-
7
- - **Data model** — `Bundle`, `PatchInfo`, `AppUpdateInfo`, and the `Platform` / `UpdateStrategy` / status enums that describe an over-the-air update.
8
- - **Rollout math** — deterministic staged-rollout cohort helpers (`rollout.dart`).
9
- - **Semver** — `semverSatisfies(target, range)` for matching a device's app version against a bundle's rollout rule.
10
- - **Identifiers** — `uuidv7` generation plus a `NIL` sentinel (`uuid.dart`).
11
- - **Bundle artifacts** — helpers to describe and address content-addressed patch artifacts.
12
-
13
- ## Usage
14
-
15
- ```dart
16
- import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
17
-
18
- final ok = semverSatisfies('1.0.0', '>=1.0.0 <2.0.0'); // true
19
- ```
20
-
21
- ## License
22
-
23
- MIT — see the [project LICENSE](https://github.com/HYPER12755/flutter-ota-kit/blob/main/LICENSE).
@@ -1,69 +0,0 @@
1
- // flutter_ota_kit_core example
2
- //
3
- // `flutter_ota_kit_core` is the framework-agnostic data model shared by every
4
- // backend plugin in the `flutter_ota_kit` family. It defines the canonical
5
- // shapes for bundles, patches, and rollouts.
6
- //
7
- // Most apps don't import this package directly — they use `flutter_ota_kit`
8
- // (the umbrella package) which re-exports it. This file shows the types you
9
- // might encounter in API responses, in `flutter_ota_kit_cli` tool output, or
10
- // while writing a custom backend.
11
- //
12
- // Run: `dart run example/main.dart`
13
-
14
- import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
15
-
16
- void main() {
17
- // ── 1. Bundle: the central database row / manifest object. ─────────
18
- // Every backend stores this shape (Postgres, Supabase, Cloudflare D1,
19
- // AWS DynamoDB, PocketBase). The wire format is camelCase, the SQL
20
- // format is snake_case — `fromJson` tolerates both.
21
- final bundle = Bundle(
22
- id: '01a059a6-7d3c-4f3e-8a2c-9c2e3a4b5d6e',
23
- platform: Platform.android,
24
- shouldForceUpdate: false,
25
- enabled: true,
26
- fileHash: '7c4a8d09ca3762af61e59520943dc26494f8941b',
27
- storageUri: 'supabase-storage://flutter-ota-bundles/01a059a6/patch.zip',
28
- channel: 'production',
29
- targetAppVersion: '1.0.0',
30
- metadata: const BundleMetadata(
31
- appVersion: '1.0.1',
32
- signature: 'ed25519:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
33
- ),
34
- rolloutCohortCount: 100, // 10% of devices (per-mille 0..1000).
35
- );
36
-
37
- print('Bundle ${bundle.id} on ${bundle.platform.value}:');
38
- print(' storageUri = ${bundle.storageUri}');
39
- print(' targetAppVersion = ${bundle.targetAppVersion}');
40
- print(' metadata.appVersion = ${bundle.metadata?.appVersion}');
41
-
42
- // ── 2. Wire format conversion. ─────────────────────────────────────
43
- // `toJson()` produces the camelCase wire payload sent over the
44
- // hot-updater-compatible update-check HTTP contract.
45
- final wire = bundle.toJson();
46
- print('Wire payload keys: ${wire.keys.toList()}');
47
-
48
- // `toSqlJson()` produces the snake_case row format used by SQL
49
- // backends (Postgres / Supabase / D1 / DynamoDB).
50
- final sql = bundle.toSqlJson();
51
- print('SQL row keys: ${sql.keys.toList()}');
52
-
53
- // ── 3. Rollout: per-mille 0..1000 cohort bucketing. ────────────────
54
- // The same device fingerprint always lands in the same bucket across
55
- // cold starts, so a 10% rollout stays at 10% even as users churn.
56
- const bundleId = '01a059a6-7d3c-4f3e-8a2c-9c2e3a4b5d6e';
57
- const rolloutCohortCount = 100; // 10% (per-mille).
58
-
59
- for (final userId in const ['alice', 'bob', 'carol', 'dave', 'eve']) {
60
- final cohort = getDefaultNumericCohort(userId);
61
- final eligible = isCohortEligibleForUpdate(
62
- bundleId,
63
- cohort,
64
- rolloutCohortCount,
65
- null, // targetCohorts (null = percentage rollout)
66
- );
67
- print('user=$userId cohort=$cohort in 10% rollout? $eligible');
68
- }
69
- }
@@ -1,13 +0,0 @@
1
- name: flutter_ota_kit_core
2
- description: Core data model, rollout cohorts, and shared utilities for flutter_ota_kit — Dart translation of hot-updater's packages/core.
3
- version: 0.1.8
4
- homepage: https://github.com/HYPER12755/flutter-ota-kit
5
- repository: https://github.com/HYPER12755/flutter-ota-kit
6
- issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
7
- documentation: https://github.com/HYPER12755/flutter-ota-kit#readme
8
- license: MIT
9
-
10
- environment:
11
- sdk: '>=3.13.2 <4.0.0'
12
- dev_dependencies:
13
- test: ^1.31.2
@@ -1,135 +0,0 @@
1
- import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
2
- import 'package:test/test.dart';
3
-
4
- /// Ground-truth values generated by executing hot-updater's actual
5
- /// rollout.ts logic under node (see scripts/verify_rollout.mjs).
6
- void main() {
7
- group('ground-truth vectors (TS-verified)', () {
8
- test('getDefaultNumericCohort matches TS', () {
9
- expect(getDefaultNumericCohort('device-abc-123'), '809');
10
- expect(getDefaultNumericCohort('ANDROID_ID_SAMPLE_42'), '64');
11
- expect(getDefaultNumericCohort('8f2a1b6c'), '996');
12
- });
13
-
14
- test('shuffle parameters match TS', () {
15
- expect(getNumericCohortRolloutPosition('bundle-a', 1), 309);
16
- expect(getNumericCohortRolloutPosition('bundle-a', 500), 228);
17
- expect(getNumericCohortRolloutPosition('bundle-b', 999), 816);
18
- });
19
-
20
- test('rolled-out cohort sets match TS', () {
21
- final a137 = getRolledOutNumericCohorts('bundle-a', 137);
22
- expect(a137.take(3), [4, 11, 16]);
23
- expect(a137.skip(a137.length - 3), [980, 985, 992]);
24
- expect(a137.length, 137);
25
-
26
- final b100 = getRolledOutNumericCohorts('bundle-b', 100);
27
- expect(b100.take(3), [1, 14, 22]);
28
- });
29
- });
30
-
31
- group('getRolledOutNumericCohorts (spec cases)', () {
32
- test('ascending unique set matching eligibility', () {
33
- final cohorts = getRolledOutNumericCohorts('x-bundle', 137);
34
- expect(cohorts.length, 137);
35
- expect(cohorts, [...cohorts]..sort());
36
- expect(cohorts.toSet().length, 137);
37
- for (final c in cohorts) {
38
- expect(isCohortEligibleForUpdate('x-bundle', '$c', 137, null), isTrue);
39
- }
40
- final excluded = List.generate(
41
- 1000,
42
- (i) => i + 1,
43
- ).firstWhere((c) => !cohorts.contains(c));
44
- expect(
45
- isCohortEligibleForUpdate('x-bundle', '$excluded', 137, null),
46
- isFalse,
47
- );
48
- });
49
-
50
- test('expanding rollout keeps existing cohorts', () {
51
- final small = getRolledOutNumericCohorts('y-bundle', 100).toSet();
52
- final large = getRolledOutNumericCohorts('y-bundle', 400);
53
- expect(large.length, 400);
54
- expect(large.toSet().containsAll(small), isTrue);
55
- });
56
-
57
- test('boundaries', () {
58
- expect(getRolledOutNumericCohorts('z-bundle', 0), isEmpty);
59
- expect(getRolledOutNumericCohorts('z-bundle', 1000).length, 1000);
60
- expect(getRolledOutNumericCohorts('z-bundle', -5), isEmpty);
61
- });
62
- });
63
-
64
- group('isCohortEligibleForUpdate (spec cases)', () {
65
- test('numeric rollout stays active with target cohorts configured', () {
66
- const bundleId = 'd-bundle';
67
- final cohorts = getRolledOutNumericCohorts(bundleId, 137);
68
- expect(
69
- isCohortEligibleForUpdate(bundleId, '${cohorts.first}', 137, [
70
- 'qa-group',
71
- ]),
72
- isTrue,
73
- );
74
- });
75
-
76
- test('targeted custom cohort included beyond percentage', () {
77
- expect(
78
- isCohortEligibleForUpdate('e-bundle', 'qa-group', 137, ['qa-group']),
79
- isTrue,
80
- );
81
- });
82
-
83
- test('targeted numeric cohort wins even when excluded by rollout', () {
84
- const bundleId = 'f-bundle';
85
- final rolledOut = getRolledOutNumericCohorts(bundleId, 10).toSet();
86
- final excluded = List.generate(
87
- 1000,
88
- (i) => i + 1,
89
- ).firstWhere((c) => !rolledOut.contains(c));
90
- expect(
91
- isCohortEligibleForUpdate(bundleId, '$excluded', 10, ['$excluded']),
92
- isTrue,
93
- );
94
- });
95
-
96
- test('null cohort: eligible only at full rollout', () {
97
- expect(isCohortEligibleForUpdate('g-bundle', null, 500, null), isFalse);
98
- expect(isCohortEligibleForUpdate('g-bundle', null, null, null), isTrue);
99
- expect(isCohortEligibleForUpdate('g-bundle', null, 0, null), isFalse);
100
- });
101
-
102
- test('custom cohorts never match percentage rollouts', () {
103
- expect(
104
- isCohortEligibleForUpdate('h-bundle', 'qa-team', 999, null),
105
- isFalse,
106
- );
107
- expect(
108
- isCohortEligibleForUpdate('h-bundle', 'qa-team', 999, ['qa-team']),
109
- isTrue,
110
- );
111
- });
112
- });
113
-
114
- group('cohort validation (spec cases)', () {
115
- test('rejects empty and out-of-range numeric strings', () {
116
- expect(isValidCohort(''), isFalse);
117
- expect(isValidCohort('1001'), isFalse);
118
- expect(isCustomCohort('1001'), isFalse);
119
- });
120
-
121
- test('enforces max custom length', () {
122
- expect(isValidCohort('a' * maxCohortLength), isTrue);
123
- expect(isValidCohort('a' * (maxCohortLength + 1)), isFalse);
124
- });
125
-
126
- test('message mentions limit', () {
127
- expect(invalidCohortErrorMessage.contains('$maxCohortLength'), isTrue);
128
- });
129
-
130
- test('normalization trims and lowercases', () {
131
- expect(normalizeCohortValue(' QA-Group '), 'qa-group');
132
- expect(normalizeCohortValue('007'), '7');
133
- });
134
- });
135
- }