@_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
@@ -1,843 +0,0 @@
1
- import 'dart:convert';
2
-
3
- import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
4
- import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart';
5
- import 'package:test/test.dart';
6
-
7
- // ---------------------------------------------------------------------------
8
- // Helpers
9
- // ---------------------------------------------------------------------------
10
-
11
- const _defaultBundleFields = <String, Object?>{
12
- 'fileHash': 'hash',
13
- 'gitCommitHash': null,
14
- 'message': null,
15
- 'enabled': true,
16
- 'shouldForceUpdate': false,
17
- 'storageUri':
18
- 'storage://my-app/00000000-0000-0000-0000-000000000000/bundle.zip',
19
- 'fingerprintHash': null,
20
- };
21
-
22
- Bundle createBundle(
23
- String channel,
24
- String platform,
25
- String targetAppVersion,
26
- String id,
27
- ) {
28
- return Bundle.fromJson({
29
- ..._defaultBundleFields,
30
- 'channel': channel,
31
- 'platform': platform,
32
- 'targetAppVersion': targetAppVersion,
33
- 'id': id,
34
- });
35
- }
36
-
37
- // ---------------------------------------------------------------------------
38
- // Fake blob store
39
- // ---------------------------------------------------------------------------
40
-
41
- class _FakeBlobStore {
42
- final Map<String, String> data = {};
43
- final List<String> listObjectCalls = [];
44
- final List<String> loadObjectCalls = [];
45
- final List<String> invalidationCalls = [];
46
-
47
- void clear() {
48
- data.clear();
49
- listObjectCalls.clear();
50
- loadObjectCalls.clear();
51
- invalidationCalls.clear();
52
- }
53
-
54
- Future<List<String>> listObjects(String prefix) async {
55
- listObjectCalls.add(prefix);
56
- return data.keys.where((k) => k.startsWith(prefix)).toList();
57
- }
58
-
59
- Future<T?> loadObject<T>(String key) async {
60
- loadObjectCalls.add(key);
61
- final raw = data[key];
62
- if (raw == null) return null;
63
- return jsonDecode(raw) as T;
64
- }
65
-
66
- Future<void> uploadObject<T>(String key, T value) async {
67
- data[key] = jsonEncode(value);
68
- }
69
-
70
- Future<void> deleteObject(String key) async {
71
- data.remove(key);
72
- }
73
-
74
- Future<void> invalidatePaths(List<String> paths) async {
75
- invalidationCalls.addAll(paths);
76
- }
77
-
78
- bool shouldSkipLoadObjectError(Object error, String key) => false;
79
- void validateChannel(String channel) {}
80
- }
81
-
82
- _BlobOps _createOps(_FakeBlobStore store) => _BlobOps(store);
83
-
84
- class _BlobOps implements BlobOperations {
85
- _BlobOps(this._store);
86
- final _FakeBlobStore _store;
87
-
88
- @override
89
- Future<List<String>> listObjects(String prefix) => _store.listObjects(prefix);
90
- @override
91
- Future<T?> loadObject<T>(String key) => _store.loadObject<T>(key);
92
- @override
93
- Future<void> uploadObject<T>(String key, T data) =>
94
- _store.uploadObject<T>(key, data);
95
- @override
96
- Future<void> deleteObject(String key) => _store.deleteObject(key);
97
- @override
98
- bool shouldSkipLoadObjectError(Object error, String key) =>
99
- _store.shouldSkipLoadObjectError(error, key);
100
- @override
101
- void validateChannel(String channel) => _store.validateChannel(channel);
102
- @override
103
- Future<void> invalidatePaths(List<String> paths) =>
104
- _store.invalidatePaths(paths);
105
- @override
106
- String get apiBasePath => '/api/check-update';
107
- }
108
-
109
- void _seedUpdateManifests(_FakeBlobStore store, List<Bundle> bundles) {
110
- final bundlesByKey = <String, List<Bundle>>{};
111
- final targetVersionsByKey = <String, Set<String>>{};
112
-
113
- for (final bundle in bundles) {
114
- final target = bundle.targetAppVersion ?? bundle.fingerprintHash;
115
- if (target == null) continue;
116
-
117
- final key =
118
- '${bundle.channel}/${bundle.platform.value}/$target/update.json';
119
- bundlesByKey.putIfAbsent(key, () => <Bundle>[]).add(bundle);
120
-
121
- if (bundle.targetAppVersion != null) {
122
- final tvKey =
123
- '${bundle.channel}/${bundle.platform.value}/target-app-versions.json';
124
- targetVersionsByKey
125
- .putIfAbsent(tvKey, () => <String>{})
126
- .add(bundle.targetAppVersion!);
127
- }
128
- }
129
-
130
- for (final entry in bundlesByKey.entries) {
131
- final sorted = entry.value..sort((a, b) => b.id.compareTo(a.id));
132
- store.data[entry.key] = jsonEncode(sorted.map((b) => b.toJson()).toList());
133
- }
134
-
135
- for (final entry in targetVersionsByKey.entries) {
136
- store.data[entry.key] = jsonEncode(entry.value.toList());
137
- }
138
- }
139
-
140
- // ---------------------------------------------------------------------------
141
- // Tests
142
- // ---------------------------------------------------------------------------
143
-
144
- void main() {
145
- late _FakeBlobStore store;
146
- late DatabasePlugin plugin;
147
-
148
- DatabasePlugin createPlugin() {
149
- return createBlobDatabasePlugin(
150
- name: 'blobDatabase',
151
- blobFactory: (_) => _createOps(store),
152
- )(null as dynamic)();
153
- }
154
-
155
- setUp(() {
156
- store = _FakeBlobStore();
157
- plugin = createPlugin();
158
- });
159
-
160
- group('update checks', () {
161
- test('uses direct app-version manifests for update checks', () async {
162
- final latest = createBundle(
163
- 'production',
164
- 'ios',
165
- '*',
166
- '00000000-0000-0000-0000-000000000002',
167
- );
168
- final previous = createBundle(
169
- 'production',
170
- 'ios',
171
- '*',
172
- '00000000-0000-0000-0000-000000000001',
173
- );
174
-
175
- _seedUpdateManifests(store, [previous, latest]);
176
-
177
- final updateInfo = await plugin.getUpdateInfo(
178
- AppVersionGetBundlesArgs(
179
- appVersion: '1.0.0',
180
- bundleId: '00000000-0000-0000-0000-000000000000',
181
- platform: Platform.ios,
182
- ),
183
- );
184
-
185
- expect(updateInfo, isNotNull);
186
- expect(updateInfo!.id, equals(latest.id));
187
- expect(updateInfo.status, equals(UpdateStatus.update));
188
-
189
- expect(store.listObjectCalls, isEmpty);
190
- expect(store.loadObjectCalls, [
191
- 'production/ios/target-app-versions.json',
192
- 'production/ios/*/update.json',
193
- ]);
194
- });
195
-
196
- test('uses fingerprint manifests directly for update checks', () async {
197
- final fingerprintBundle = Bundle.fromJson({
198
- ..._defaultBundleFields,
199
- 'channel': 'production',
200
- 'platform': 'ios',
201
- 'targetAppVersion': null,
202
- 'fingerprintHash': 'fingerprint-1',
203
- 'id': '00000000-0000-0000-0000-000000000010',
204
- });
205
-
206
- _seedUpdateManifests(store, [fingerprintBundle]);
207
-
208
- final updateInfo = await plugin.getUpdateInfo(
209
- FingerprintGetBundlesArgs(
210
- bundleId: '00000000-0000-0000-0000-000000000000',
211
- fingerprintHash: 'fingerprint-1',
212
- platform: Platform.ios,
213
- ),
214
- );
215
-
216
- expect(updateInfo, isNotNull);
217
- expect(updateInfo!.id, equals(fingerprintBundle.id));
218
- expect(updateInfo.status, equals(UpdateStatus.update));
219
-
220
- expect(store.listObjectCalls, isEmpty);
221
- expect(store.loadObjectCalls, [
222
- 'production/ios/fingerprint-1/update.json',
223
- ]);
224
- });
225
-
226
- test('respects cohort eligibility', () async {
227
- final gatedBundle = Bundle.fromJson({
228
- ...createBundle(
229
- 'production',
230
- 'ios',
231
- '*',
232
- '00000000-0000-0000-0000-000000000021',
233
- ).toJson(),
234
- 'targetCohorts': ['beta'],
235
- });
236
- final fallback = createBundle(
237
- 'production',
238
- 'ios',
239
- '*',
240
- '00000000-0000-0000-0000-000000000020',
241
- );
242
- final fallbackWithCohort = Bundle.fromJson({
243
- ...fallback.toJson(),
244
- 'targetCohorts': ['stable'],
245
- });
246
-
247
- _seedUpdateManifests(store, [fallbackWithCohort, gatedBundle]);
248
-
249
- final updateInfo = await plugin.getUpdateInfo(
250
- AppVersionGetBundlesArgs(
251
- appVersion: '1.0.0',
252
- bundleId: '00000000-0000-0000-0000-000000000000',
253
- cohort: 'stable',
254
- platform: Platform.ios,
255
- ),
256
- );
257
-
258
- expect(updateInfo, isNotNull);
259
- expect(updateInfo!.id, equals(fallbackWithCohort.id));
260
- expect(updateInfo.status, equals(UpdateStatus.update));
261
- });
262
-
263
- test('returns null when no candidates and bundleId = nil', () async {
264
- final updateInfo = await plugin.getUpdateInfo(
265
- AppVersionGetBundlesArgs(
266
- appVersion: '1.0.0',
267
- bundleId: '00000000-0000-0000-0000-000000000040',
268
- minBundleId: '00000000-0000-0000-0000-000000000040',
269
- platform: Platform.ios,
270
- ),
271
- );
272
- expect(updateInfo, isNull);
273
- });
274
- });
275
-
276
- group('append and commit', () {
277
- test('appends a new bundle and commits to blob store', () async {
278
- final bundle = createBundle(
279
- 'production',
280
- 'ios',
281
- '1.0.0',
282
- '00000000-0000-0000-0000-000000000001',
283
- );
284
-
285
- await plugin.appendBundle(bundle);
286
- await plugin.commitBundle();
287
-
288
- final bundleKey = 'production/ios/1.0.0/update.json';
289
- expect(store.data.containsKey(bundleKey), isTrue);
290
-
291
- final stored = jsonDecode(store.data[bundleKey]!) as List;
292
- expect(stored.length, equals(1));
293
-
294
- final fetched = await plugin.getBundleById(
295
- '00000000-0000-0000-0000-000000000001',
296
- );
297
- expect(fetched, isNotNull);
298
- expect(fetched!.id, equals(bundle.id));
299
- });
300
-
301
- test('does not write to store until commitBundle', () async {
302
- final bundle = createBundle(
303
- 'production',
304
- 'ios',
305
- '1.0.0',
306
- '00000000-0000-0000-0000-000000000010',
307
- );
308
-
309
- expect(store.data, isEmpty);
310
-
311
- await plugin.appendBundle(bundle);
312
- expect(store.data, isEmpty);
313
-
314
- await plugin.commitBundle();
315
- expect(
316
- store.data.containsKey('production/ios/1.0.0/update.json'),
317
- isTrue,
318
- );
319
- });
320
-
321
- test('appends multiple bundles to correct keys', () async {
322
- final b1 = createBundle('production', 'ios', '1.0.0', 'multi-1');
323
- final b2 = createBundle('production', 'android', '2.0.0', 'multi-2');
324
-
325
- await plugin.appendBundle(b1);
326
- await plugin.appendBundle(b2);
327
- await plugin.commitBundle();
328
-
329
- expect(
330
- store.data.containsKey('production/ios/1.0.0/update.json'),
331
- isTrue,
332
- );
333
- expect(
334
- store.data.containsKey('production/android/2.0.0/update.json'),
335
- isTrue,
336
- );
337
- });
338
- });
339
-
340
- group('update and delete', () {
341
- test('updates an existing bundle', () async {
342
- final bundle = createBundle(
343
- 'production',
344
- 'android',
345
- '2.0.0',
346
- '00000000-0000-0000-0000-000000000002',
347
- );
348
- final bundleKey = 'production/android/2.0.0/update.json';
349
- store.data[bundleKey] = jsonEncode([bundle.toJson()]);
350
-
351
- await plugin.getBundles(const DatabaseBundleQueryOptions(limit: 20));
352
- await plugin.updateBundle('00000000-0000-0000-0000-000000000002', {
353
- 'enabled': false,
354
- });
355
- await plugin.commitBundle();
356
-
357
- final updated = jsonDecode(store.data[bundleKey]!) as List;
358
- expect(updated.length, equals(1));
359
- expect(updated[0]['enabled'], isFalse);
360
- });
361
-
362
- test('throws when updating non-existent bundle', () async {
363
- expect(
364
- () => plugin.updateBundle('nonexistent', {'enabled': true}),
365
- throwsA(isA<StateError>()),
366
- );
367
- });
368
-
369
- test('deletes a bundle successfully', () async {
370
- final b1 = createBundle('production', 'ios', '1.1.1', 'bundleX');
371
- final b2 = createBundle('production', 'android', '1.1.1', 'bundleY');
372
-
373
- await plugin.appendBundle(b1);
374
- await plugin.appendBundle(b2);
375
- await plugin.commitBundle();
376
-
377
- expect(await plugin.getBundleById('bundleX'), isNotNull);
378
-
379
- await plugin.deleteBundle(b1);
380
- await plugin.commitBundle();
381
-
382
- expect(await plugin.getBundleById('bundleX'), isNull);
383
- expect(await plugin.getBundleById('bundleY'), isNotNull);
384
- });
385
-
386
- test('deletes entire update.json when no bundles remain', () async {
387
- final b1 = createBundle('production', 'ios', '1.1.1', 'bundleX');
388
-
389
- await plugin.appendBundle(b1);
390
- await plugin.commitBundle();
391
-
392
- final updateJsonKeys = store.data.keys
393
- .where((k) => k.contains('update.json'))
394
- .toList();
395
- expect(updateJsonKeys, isNotEmpty);
396
-
397
- await plugin.deleteBundle(b1);
398
- await plugin.commitBundle();
399
-
400
- expect(store.data.containsKey(updateJsonKeys.first), isFalse);
401
- });
402
-
403
- test('keeps update.json when other bundles remain', () async {
404
- final b1 = {
405
- ..._defaultBundleFields,
406
- 'id': 'bundleA',
407
- 'channel': 'production',
408
- 'platform': 'ios',
409
- 'targetAppVersion': '1.1.1',
410
- };
411
- final b2 = {
412
- ..._defaultBundleFields,
413
- 'id': 'bundleB',
414
- 'channel': 'production',
415
- 'platform': 'ios',
416
- 'targetAppVersion': '1.1.1',
417
- };
418
-
419
- final bundle1 = Bundle.fromJson(b1);
420
- final bundle2 = Bundle.fromJson(b2);
421
-
422
- await plugin.appendBundle(bundle1);
423
- await plugin.appendBundle(bundle2);
424
- await plugin.commitBundle();
425
-
426
- await plugin.deleteBundle(bundle1);
427
- await plugin.commitBundle();
428
-
429
- final remaining = await plugin.getBundleById('bundleB');
430
- expect(remaining, isNotNull);
431
- });
432
- });
433
-
434
- group('targetAppVersion move', () {
435
- test('moves bundle between version paths', () async {
436
- final keyOld = 'production/ios/1.x.x/update.json';
437
- final keyNew = 'production/ios/1.0.2/update.json';
438
-
439
- final oldBundles = [
440
- createBundle(
441
- 'production',
442
- 'ios',
443
- '1.x.x',
444
- '00000000-0000-0000-0000-000000000003',
445
- ),
446
- createBundle(
447
- 'production',
448
- 'ios',
449
- '1.x.x',
450
- '00000000-0000-0000-0000-000000000002',
451
- ),
452
- createBundle(
453
- 'production',
454
- 'ios',
455
- '1.x.x',
456
- '00000000-0000-0000-0000-000000000001',
457
- ),
458
- ];
459
-
460
- final newBundles = [
461
- createBundle(
462
- 'production',
463
- 'ios',
464
- '1.0.2',
465
- '00000000-0000-0000-0000-000000000005',
466
- ),
467
- createBundle(
468
- 'production',
469
- 'ios',
470
- '1.0.2',
471
- '00000000-0000-0000-0000-000000000004',
472
- ),
473
- ];
474
-
475
- store.data[keyOld] = jsonEncode(
476
- oldBundles.map((b) => b.toJson()).toList(),
477
- );
478
- store.data[keyNew] = jsonEncode(
479
- newBundles.map((b) => b.toJson()).toList(),
480
- );
481
- store.data['production/ios/target-app-versions.json'] = jsonEncode([
482
- '1.x.x',
483
- '1.0.2',
484
- ]);
485
-
486
- await plugin.getBundles(const DatabaseBundleQueryOptions(limit: 20));
487
-
488
- await plugin.updateBundle('00000000-0000-0000-0000-000000000003', {
489
- 'targetAppVersion': '1.0.2',
490
- });
491
- await plugin.commitBundle();
492
-
493
- final newFileBundles = jsonDecode(store.data[keyNew]!) as List;
494
- expect(newFileBundles.length, equals(3));
495
-
496
- final oldFileBundles = jsonDecode(store.data[keyOld]!) as List;
497
- expect(oldFileBundles.length, equals(2));
498
- });
499
- });
500
-
501
- group('getChannels', () {
502
- test('returns sorted unique channels', () async {
503
- final p1 = createBundle('production', 'ios', '1.0.0', 'ch-prod');
504
- final p2 = createBundle('beta', 'ios', '1.0.0', 'ch-beta');
505
-
506
- await plugin.appendBundle(p1);
507
- await plugin.appendBundle(p2);
508
- await plugin.commitBundle();
509
-
510
- final channels = await plugin.getChannels();
511
- expect(channels, ['beta', 'production']);
512
- });
513
- });
514
-
515
- group('getBundles', () {
516
- test('returns empty for no data', () async {
517
- final result = await plugin.getBundles(
518
- const DatabaseBundleQueryOptions(limit: 20),
519
- );
520
- expect(result.data, isEmpty);
521
- expect(result.pagination.total, equals(0));
522
- });
523
-
524
- test('sorts bundles descending by id', () async {
525
- final bA = createBundle('production', 'ios', '1.0.0', 'A');
526
- final bB = createBundle('production', 'ios', '1.0.0', 'B');
527
- final bC = createBundle('production', 'ios', '2.0.0', 'C');
528
-
529
- store.data['production/ios/1.0.0/update.json'] = jsonEncode([
530
- bB.toJson(),
531
- bA.toJson(),
532
- ]);
533
- store.data['production/ios/2.0.0/update.json'] = jsonEncode([
534
- bC.toJson(),
535
- ]);
536
-
537
- final result = await plugin.getBundles(
538
- const DatabaseBundleQueryOptions(limit: 20),
539
- );
540
- expect(result.data.length, equals(3));
541
- expect(result.data[0].id, 'C');
542
- expect(result.data[1].id, 'B');
543
- expect(result.data[2].id, 'A');
544
- });
545
-
546
- test('filters by channel and platform', () async {
547
- final ios = createBundle('production', 'ios', '1.0.0', 'ios-1');
548
- final android = createBundle(
549
- 'production',
550
- 'android',
551
- '1.0.0',
552
- 'android-1',
553
- );
554
-
555
- store.data['production/ios/1.0.0/update.json'] = jsonEncode([
556
- ios.toJson(),
557
- ]);
558
- store.data['production/android/1.0.0/update.json'] = jsonEncode([
559
- android.toJson(),
560
- ]);
561
-
562
- final result = await plugin.getBundles(
563
- DatabaseBundleQueryOptions(
564
- limit: 20,
565
- where: const DatabaseBundleQueryWhere(
566
- channel: 'production',
567
- platform: Platform.ios,
568
- ),
569
- ),
570
- );
571
- expect(result.data.length, equals(1));
572
- expect(result.data[0].id, 'ios-1');
573
- });
574
- });
575
-
576
- group('CloudFront invalidation', () {
577
- test('invalidates correct paths on new bundle', () async {
578
- final bundle = createBundle(
579
- 'production',
580
- 'ios',
581
- '1.0.0',
582
- 'cloudfront-new-test',
583
- );
584
-
585
- await plugin.appendBundle(bundle);
586
- await plugin.commitBundle();
587
-
588
- expect(store.invalidationCalls, isNotEmpty);
589
- expect(
590
- store.invalidationCalls.any(
591
- (p) => p.contains('app-version') && p.contains('1.0.0'),
592
- ),
593
- isTrue,
594
- );
595
- expect(
596
- store.invalidationCalls.any((p) => p.contains('update.json')),
597
- isFalse,
598
- );
599
- });
600
-
601
- test('invalidates semver pattern path', () async {
602
- final bundle = createBundle(
603
- 'production',
604
- 'ios',
605
- '3.0.x',
606
- 'cloudfront-semver',
607
- );
608
-
609
- store.invalidationCalls.clear();
610
- await plugin.appendBundle(bundle);
611
- await plugin.commitBundle();
612
-
613
- expect(
614
- store.invalidationCalls.any(
615
- (p) => p == '/api/check-update/app-version/ios/*',
616
- ),
617
- isTrue,
618
- );
619
- });
620
-
621
- test('no invalidation when commitBundle has no changes', () async {
622
- store.invalidationCalls.clear();
623
- await plugin.commitBundle();
624
- expect(store.invalidationCalls, isEmpty);
625
- });
626
- });
627
-
628
- group('targetAppVersion with spaces', () {
629
- test('normalizes space-containing targetAppVersion', () async {
630
- final bundle = createBundle(
631
- 'production',
632
- 'ios',
633
- '>= 10.7.0',
634
- 'space-normalization-test',
635
- );
636
-
637
- await plugin.appendBundle(bundle);
638
- await plugin.commitBundle();
639
-
640
- expect(
641
- store.data.containsKey('production/ios/>=10.7.0/update.json'),
642
- isTrue,
643
- );
644
- });
645
-
646
- test('normalizes multiple spaces', () async {
647
- final bundle = createBundle(
648
- 'production',
649
- 'android',
650
- '> 1.0.0 < 2.0.0',
651
- 'multi-space-test',
652
- );
653
-
654
- await plugin.appendBundle(bundle);
655
- await plugin.commitBundle();
656
-
657
- expect(
658
- store.data.containsKey('production/android/>1.0.0 <2.0.0/update.json'),
659
- isTrue,
660
- );
661
- });
662
-
663
- test('handles various semver range formats', () async {
664
- final cases = [
665
- ('> 1.0.0', '>1.0.0'),
666
- ('< 2.0.0', '<2.0.0'),
667
- ('>= 1.0.0', '>=1.0.0'),
668
- ('<= 2.0.0', '<=2.0.0'),
669
- ('~ 1.0.0', '~1.0.0'),
670
- ('^ 2.0.0', '^2.0.0'),
671
- ];
672
-
673
- for (var i = 0; i < cases.length; i++) {
674
- final (version, normalized) = cases[i];
675
- final bundle = createBundle('production', 'ios', version, 'format-$i');
676
- await plugin.appendBundle(bundle);
677
- }
678
-
679
- await plugin.commitBundle();
680
-
681
- for (final (_, normalized) in cases) {
682
- expect(
683
- store.data.containsKey('production/ios/$normalized/update.json'),
684
- isTrue,
685
- reason: 'Missing key for $normalized',
686
- );
687
- }
688
- });
689
-
690
- test('normalizes target-app-versions.json', () async {
691
- final bundle = createBundle(
692
- 'production',
693
- 'ios',
694
- '>= 10.7.0',
695
- 'target-versions-test',
696
- );
697
-
698
- await plugin.appendBundle(bundle);
699
- await plugin.commitBundle();
700
-
701
- final versions = jsonDecode(
702
- store.data['production/ios/target-app-versions.json']!,
703
- ) as List;
704
- expect(versions, contains('>=10.7.0'));
705
- });
706
- });
707
-
708
- group('semver normalization', () {
709
- test('invalidates all paths for 1.0.0', () async {
710
- final bundle = createBundle(
711
- 'production',
712
- 'ios',
713
- '1.0.0',
714
- 'semver-normalization-test',
715
- );
716
-
717
- store.invalidationCalls.clear();
718
- await plugin.appendBundle(bundle);
719
- await plugin.commitBundle();
720
-
721
- expect(
722
- store.invalidationCalls.any((p) => p.contains('app-version/ios/1.0.0')),
723
- isTrue,
724
- );
725
- expect(
726
- store.invalidationCalls.any((p) => p.contains('app-version/ios/1.0')),
727
- isTrue,
728
- );
729
- expect(
730
- store.invalidationCalls.any((p) => p.contains('app-version/ios/1/')),
731
- isTrue,
732
- );
733
- });
734
-
735
- test('only normalizes 2.x when patch=0', () async {
736
- final bundle = createBundle(
737
- 'production',
738
- 'android',
739
- '2.1.0',
740
- 'semver-norm-test-2',
741
- );
742
-
743
- store.invalidationCalls.clear();
744
- await plugin.appendBundle(bundle);
745
- await plugin.commitBundle();
746
-
747
- expect(
748
- store.invalidationCalls.any(
749
- (p) => p.contains('app-version/android/2.1.0'),
750
- ),
751
- isTrue,
752
- );
753
- expect(
754
- store.invalidationCalls.any(
755
- (p) => p.contains('app-version/android/2.1'),
756
- ),
757
- isTrue,
758
- );
759
- expect(
760
- store.invalidationCalls.any(
761
- (p) => RegExp(r'app-version/android/2/').hasMatch(p),
762
- ),
763
- isFalse,
764
- );
765
- });
766
-
767
- test('no extra normalization for 1.2.3', () async {
768
- final bundle = createBundle(
769
- 'production',
770
- 'ios',
771
- '1.2.3',
772
- 'no-normalization-test',
773
- );
774
-
775
- store.invalidationCalls.clear();
776
- await plugin.appendBundle(bundle);
777
- await plugin.commitBundle();
778
-
779
- expect(
780
- store.invalidationCalls.any(
781
- (p) => p == '/api/check-update/app-version/ios/1.2.3/production/*',
782
- ),
783
- isTrue,
784
- );
785
- expect(
786
- store.invalidationCalls.any(
787
- (p) => p == '/api/check-update/app-version/ios/1.2/production/*',
788
- ),
789
- isFalse,
790
- );
791
- expect(
792
- store.invalidationCalls.any(
793
- (p) => p == '/api/check-update/app-version/ios/1/production/*',
794
- ),
795
- isFalse,
796
- );
797
- });
798
- });
799
-
800
- group('issue #745 promotion scenario', () {
801
- test('updates target-app-versions when promoting channel', () async {
802
- final bundle = createBundle(
803
- 'test',
804
- 'android',
805
- '8.1.3',
806
- 'issue-745-promote-bundle',
807
- );
808
-
809
- await plugin.appendBundle(bundle);
810
- await plugin.commitBundle();
811
-
812
- store.invalidationCalls.clear();
813
-
814
- await plugin.updateBundle('issue-745-promote-bundle', {
815
- 'channel': 'prod',
816
- });
817
- await plugin.commitBundle();
818
-
819
- expect(
820
- store.invalidationCalls.any(
821
- (p) => p.contains('app-version/android/8.1.3/test'),
822
- ),
823
- isTrue,
824
- );
825
- expect(
826
- store.invalidationCalls.any(
827
- (p) => p.contains('app-version/android/8.1.3/prod'),
828
- ),
829
- isTrue,
830
- );
831
-
832
- final prodVersions = jsonDecode(
833
- store.data['prod/android/target-app-versions.json'] ?? '[]',
834
- ) as List;
835
- expect(prodVersions, contains('8.1.3'));
836
-
837
- final testVersions = jsonDecode(
838
- store.data['test/android/target-app-versions.json'] ?? '[]',
839
- ) as List;
840
- expect(testVersions, isNot(contains('8.1.3')));
841
- });
842
- });
843
- }