@_nazmiforreal/flutter-ota 0.1.25 → 0.1.27

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 (256) hide show
  1. package/bin/flutter-ota-linux-x64 +0 -0
  2. package/bin/flutter-ota.js +12 -11
  3. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +46 -13
  4. package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -6
  5. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +2 -0
  6. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +7 -7
  7. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -1
  8. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +1 -1
  9. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +1 -1
  10. package/dart-src/packages/cli-tools/lib/src/config.dart +5 -5
  11. package/dart-src/packages/cli-tools/lib/src/operations.dart +2 -2
  12. package/dart-src/packages/cli-tools/pubspec.yaml +8 -21
  13. package/dart-src/packages/cli-tools/test/backends_test.dart +3 -3
  14. package/dart-src/packages/cli-tools/test/cli_test.dart +1 -1
  15. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
  16. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
  17. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +4 -1
  18. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +2 -2
  19. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +1 -1
  20. package/dart-src/root/README.md +394 -0
  21. package/dart-src/root/analysis_options.yaml +21 -0
  22. package/dart-src/root/android/build.gradle +116 -0
  23. package/dart-src/root/android/consumer-rules.pro +23 -0
  24. package/dart-src/root/android/settings.gradle +1 -0
  25. package/dart-src/root/android/src/main/AndroidManifest.xml +21 -0
  26. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/ApplyResult.kt +52 -0
  27. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BlacklistStore.kt +154 -0
  28. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/BootDiagnosticStore.kt +150 -0
  29. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/CrashGuard.kt +271 -0
  30. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherApplication.kt +201 -0
  31. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherAutoInitProvider.kt +93 -0
  32. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/FlutterPatcherPlugin.kt +245 -0
  33. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/LoaderHook.kt +298 -0
  34. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatchManager.kt +1579 -0
  35. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfig.kt +173 -0
  36. package/dart-src/root/android/src/main/kotlin/com/flutter_patcher/flutter_patcher/SignatureVerifier.kt +177 -0
  37. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/CrashGuardTest.kt +61 -0
  38. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/LoaderHookTest.kt +25 -0
  39. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatchManagerTest.kt +401 -0
  40. package/dart-src/root/android/src/test/kotlin/com/flutter_patcher/flutter_patcher/PatcherConfigTest.kt +30 -0
  41. package/dart-src/root/bin/pack.dart +182 -0
  42. package/dart-src/root/lib/flutter_ota_kit.dart +1170 -0
  43. package/dart-src/root/lib/src/app_version_resolver.dart +36 -0
  44. package/dart-src/root/lib/src/aws_update_source.dart +97 -0
  45. package/dart-src/root/lib/src/blacklist.dart +54 -0
  46. package/dart-src/root/lib/src/boot_diagnostic.dart +138 -0
  47. package/dart-src/root/lib/src/cloudflare_update_source.dart +97 -0
  48. package/dart-src/root/lib/src/flutter_ota_app.dart +69 -0
  49. package/dart-src/root/lib/src/io.dart +48 -0
  50. package/dart-src/root/lib/src/io_stub.dart +19 -0
  51. package/dart-src/root/lib/src/ota_progress_overlay.dart +987 -0
  52. package/dart-src/root/lib/src/pack/pack.dart +434 -0
  53. package/dart-src/root/lib/src/patch_info.dart +12 -0
  54. package/dart-src/root/lib/src/patcher_channel.dart +160 -0
  55. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_database.dart +1 -1
  56. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_s3_client.dart +1 -1
  57. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage.dart +1 -1
  58. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_storage_profile.dart +1 -1
  59. package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/with_cloudfront_signed_url.dart +1 -1
  60. package/dart-src/root/lib/src/pkg/client/flutter_ota_kit_client.dart +1 -0
  61. package/dart-src/root/lib/src/pkg/client/src/server_update_source.dart +46 -0
  62. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/cloudflare_worker_database.dart +1 -1
  63. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_build_where.dart +1 -1
  64. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_bundle_mapper.dart +3 -3
  65. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database.dart +1 -1
  66. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_database_plugin.dart +2 -2
  67. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_s3_client.dart +1 -1
  68. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage.dart +1 -1
  69. package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_storage_profile.dart +1 -1
  70. package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/status.dart +1 -1
  71. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/flutter_ota_kit_plugin_core.dart +1 -1
  72. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_blob_database_plugin.dart +1 -1
  73. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/filter_compatible_app_versions.dart +1 -1
  74. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/get_update_info.dart +1 -1
  75. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/request_update_bundle_state.dart +1 -1
  76. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/resolve_update_info_from_bundles.dart +1 -1
  77. package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/types.dart +2 -2
  78. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_bundle_mapper.dart +2 -2
  79. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_database.dart +26 -15
  80. package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_storage.dart +1 -1
  81. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_bundle_mapper.dart +2 -2
  82. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_database.dart +2 -2
  83. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_get_update_info.dart +2 -2
  84. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_storage.dart +1 -1
  85. package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_types.dart +1 -1
  86. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_bundle_mapper.dart +2 -2
  87. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_database.dart +2 -2
  88. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_database.dart +1 -1
  89. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_edge_function_storage.dart +1 -1
  90. package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_storage.dart +1 -1
  91. package/dart-src/root/lib/src/pocketbase_update_source.dart +107 -0
  92. package/dart-src/root/lib/src/postgres_update_source.dart +95 -0
  93. package/dart-src/root/lib/src/shared_update_check.dart +131 -0
  94. package/dart-src/root/lib/src/supabase_update_source.dart +93 -0
  95. package/dart-src/root/pubspec.yaml +54 -0
  96. package/package.json +2 -1
  97. package/scripts/postinstall.js +17 -13
  98. package/scripts/vendor.sh +22 -7
  99. package/dart-src/packages/core/CHANGELOG.md +0 -21
  100. package/dart-src/packages/core/README.md +0 -23
  101. package/dart-src/packages/core/example/lib/main.dart +0 -69
  102. package/dart-src/packages/core/pubspec.yaml +0 -13
  103. package/dart-src/packages/core/test/rollout_test.dart +0 -135
  104. package/dart-src/packages/core/test/semver_test.dart +0 -55
  105. package/dart-src/packages/core/test/semver_vectors.json +0 -1
  106. package/dart-src/packages/core/test/types_test.dart +0 -131
  107. package/dart-src/packages/core/test/uuid_test.dart +0 -40
  108. package/dart-src/packages/core/tool/dbg.dart +0 -7
  109. package/dart-src/plugins/aws/.gitkeep +0 -0
  110. package/dart-src/plugins/aws/CHANGELOG.md +0 -23
  111. package/dart-src/plugins/aws/LICENSE +0 -21
  112. package/dart-src/plugins/aws/README.md +0 -28
  113. package/dart-src/plugins/aws/example/lib/main.dart +0 -60
  114. package/dart-src/plugins/aws/pubspec.yaml +0 -30
  115. package/dart-src/plugins/aws/test/aws_database_test.dart +0 -161
  116. package/dart-src/plugins/aws/test/aws_storage_test.dart +0 -73
  117. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +0 -78
  118. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  119. package/dart-src/plugins/cloudflare/CHANGELOG.md +0 -23
  120. package/dart-src/plugins/cloudflare/LICENSE +0 -21
  121. package/dart-src/plugins/cloudflare/README.md +0 -25
  122. package/dart-src/plugins/cloudflare/example/lib/main.dart +0 -62
  123. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +0 -16
  124. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +0 -8
  125. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +0 -38
  126. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +0 -11
  127. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +0 -24
  128. package/dart-src/plugins/cloudflare/pubspec.yaml +0 -28
  129. package/dart-src/plugins/cloudflare/sql/bundles.sql +0 -42
  130. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +0 -126
  131. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +0 -106
  132. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +0 -200
  133. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +0 -261
  134. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +0 -78
  135. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +0 -74
  136. package/dart-src/plugins/plugin-core/CHANGELOG.md +0 -20
  137. package/dart-src/plugins/plugin-core/LICENSE +0 -21
  138. package/dart-src/plugins/plugin-core/README.md +0 -21
  139. package/dart-src/plugins/plugin-core/example/lib/main.dart +0 -61
  140. package/dart-src/plugins/plugin-core/pubspec.yaml +0 -23
  141. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +0 -170
  142. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +0 -74
  143. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +0 -345
  144. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +0 -843
  145. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +0 -298
  146. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +0 -195
  147. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +0 -56
  148. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +0 -1589
  149. package/dart-src/plugins/plugin-core/test/helpers_test.dart +0 -283
  150. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +0 -202
  151. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +0 -86
  152. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +0 -88
  153. package/dart-src/plugins/pocketbase/CHANGELOG.md +0 -23
  154. package/dart-src/plugins/pocketbase/LICENSE +0 -21
  155. package/dart-src/plugins/pocketbase/README.md +0 -54
  156. package/dart-src/plugins/pocketbase/example/lib/main.dart +0 -62
  157. package/dart-src/plugins/pocketbase/pubspec.yaml +0 -26
  158. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +0 -12
  159. package/dart-src/plugins/postgres/.gitkeep +0 -0
  160. package/dart-src/plugins/postgres/CHANGELOG.md +0 -23
  161. package/dart-src/plugins/postgres/LICENSE +0 -21
  162. package/dart-src/plugins/postgres/README.md +0 -29
  163. package/dart-src/plugins/postgres/example/lib/main.dart +0 -67
  164. package/dart-src/plugins/postgres/pubspec.yaml +0 -26
  165. package/dart-src/plugins/postgres/sql/bundles.sql +0 -45
  166. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +0 -21
  167. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +0 -171
  168. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +0 -126
  169. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +0 -125
  170. package/dart-src/plugins/postgres/sql/hash_user_id.sql +0 -30
  171. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +0 -237
  172. package/dart-src/plugins/postgres/sql/prepareSql.ts +0 -11
  173. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +0 -320
  174. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +0 -139
  175. package/dart-src/plugins/postgres/test/postgres_database_test.dart +0 -210
  176. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +0 -60
  177. package/dart-src/plugins/supabase/CHANGELOG.md +0 -24
  178. package/dart-src/plugins/supabase/LICENSE +0 -21
  179. package/dart-src/plugins/supabase/README.md +0 -53
  180. package/dart-src/plugins/supabase/example/lib/main.dart +0 -63
  181. package/dart-src/plugins/supabase/pubspec.yaml +0 -27
  182. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +0 -44
  183. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +0 -968
  184. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +0 -232
  185. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +0 -137
  186. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +0 -206
  187. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +0 -186
  188. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +0 -505
  189. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +0 -50
  190. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +0 -20
  191. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +0 -75
  192. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +0 -654
  193. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +0 -115
  194. package/dart-src/plugins/supabase/test/supabase_database_test.dart +0 -198
  195. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +0 -168
  196. /package/dart-src/{packages/core → root}/LICENSE +0 -0
  197. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/flutter_ota_kit_aws.dart +0 -0
  198. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_client.dart +0 -0
  199. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_cloudfront_signer.dart +0 -0
  200. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_config.dart +0 -0
  201. /package/dart-src/{plugins/aws/lib → root/lib/src/pkg/aws}/src/aws_ssm_client.dart +0 -0
  202. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/flutter_ota_kit_cloudflare.dart +0 -0
  203. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_client.dart +0 -0
  204. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/d1_config.dart +0 -0
  205. /package/dart-src/{plugins/cloudflare/lib → root/lib/src/pkg/cloudflare}/src/r2_config.dart +0 -0
  206. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/flutter_ota_kit_core.dart +0 -0
  207. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/app_update_info.dart +0 -0
  208. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle.dart +0 -0
  209. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_artifacts.dart +0 -0
  210. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/bundle_patch_artifact.dart +0 -0
  211. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/changed_asset.dart +0 -0
  212. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/get_bundles_args.dart +0 -0
  213. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/metadata.dart +0 -0
  214. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/patch_info.dart +0 -0
  215. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/platform.dart +0 -0
  216. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/rollout.dart +0 -0
  217. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver.dart +0 -0
  218. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_range.dart +0 -0
  219. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/semver_version.dart +0 -0
  220. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/strategy.dart +0 -0
  221. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/update_bundle_params.dart +0 -0
  222. /package/dart-src/{packages/core/lib → root/lib/src/pkg/core}/src/uuid.dart +0 -0
  223. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/asset_storage_layout.dart +0 -0
  224. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_helpers.dart +0 -0
  225. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_storage_layout.dart +0 -0
  226. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work.dart +0 -0
  227. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/bundle_unit_of_work_store.dart +0 -0
  228. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/calculate_pagination.dart +0 -0
  229. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/compression_format.dart +0 -0
  230. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/content_addressed_assets.dart +0 -0
  231. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_database_plugin.dart +0 -0
  232. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_key_builder.dart +0 -0
  233. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/create_storage_plugin.dart +0 -0
  234. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/generate_min_bundle_id.dart +0 -0
  235. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/is_object.dart +0 -0
  236. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/legacy_asset_storage_layout.dart +0 -0
  237. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/paginate_bundles.dart +0 -0
  238. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/parse_storage_uri.dart +0 -0
  239. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/query_bundles.dart +0 -0
  240. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_helpers.dart +0 -0
  241. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/storage_profile.dart +0 -0
  242. /package/dart-src/{plugins/plugin-core/lib → root/lib/src/pkg/plugin_core}/src/uuidv7.dart +0 -0
  243. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/flutter_ota_kit_pocketbase.dart +0 -0
  244. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_client.dart +0 -0
  245. /package/dart-src/{plugins/pocketbase/lib → root/lib/src/pkg/pocketbase}/src/pocketbase_config.dart +0 -0
  246. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/flutter_ota_kit_postgres.dart +0 -0
  247. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_client.dart +0 -0
  248. /package/dart-src/{plugins/postgres/lib → root/lib/src/pkg/postgres}/src/postgres_config.dart +0 -0
  249. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/edge.dart +0 -0
  250. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/flutter_ota_kit_supabase.dart +0 -0
  251. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/error_message.dart +0 -0
  252. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_adapter.dart +0 -0
  253. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_client_http.dart +0 -0
  254. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_config.dart +0 -0
  255. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/supabase_signed_url_batcher.dart +0 -0
  256. /package/dart-src/{plugins/supabase/lib → root/lib/src/pkg/supabase}/src/types.dart +0 -0
@@ -0,0 +1,401 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import android.content.Context
4
+ import kotlin.test.Test
5
+ import kotlin.test.assertEquals
6
+ import kotlin.test.assertFalse
7
+ import kotlin.test.assertNull
8
+ import kotlin.test.assertTrue
9
+ import java.io.File
10
+ import io.flutter.plugin.common.StandardMessageCodec
11
+ import org.json.JSONArray
12
+ import org.json.JSONObject
13
+ import org.mockito.Mockito
14
+
15
+ class PatchManagerTest {
16
+ @Test
17
+ fun validatePatchArgsAcceptsValidFullPatch() {
18
+ val result = PatchManager.validatePatchArgs(
19
+ version = "1.0.0-h1",
20
+ url = "https://example.com/libapp.so",
21
+ md5 = "0123456789abcdef0123456789abcdef",
22
+ mode = "full",
23
+ targetVersionCode = 100,
24
+ currentVersionCode = 100
25
+ )
26
+
27
+ assertNull(result)
28
+ }
29
+
30
+ @Test
31
+ fun validatePatchArgsRejectsInvalidMd5BeforeDownload() {
32
+ val result = PatchManager.validatePatchArgs(
33
+ version = "1.0.0-h1",
34
+ url = "https://example.com/libapp.so",
35
+ md5 = "bad-md5",
36
+ mode = "full",
37
+ targetVersionCode = 100,
38
+ currentVersionCode = 100
39
+ )
40
+
41
+ assertEquals(ApplyErrorCode.INVALID_ARGS, result?.errorCode)
42
+ }
43
+
44
+ @Test
45
+ fun validatePatchArgsRejectsTargetVersionCodeMismatch() {
46
+ val result = PatchManager.validatePatchArgs(
47
+ version = "1.0.0-h1",
48
+ url = "https://example.com/libapp.so",
49
+ md5 = "0123456789abcdef0123456789abcdef",
50
+ mode = "full",
51
+ targetVersionCode = 101,
52
+ currentVersionCode = 100
53
+ )
54
+
55
+ assertEquals(ApplyErrorCode.INVALID_ARGS, result?.errorCode)
56
+ }
57
+
58
+ @Test
59
+ fun validatePatchArgsRejectsUnsupportedMode() {
60
+ val result = PatchManager.validatePatchArgs(
61
+ version = "1.0.0-h1",
62
+ url = "https://example.com/app.patch",
63
+ md5 = "0123456789abcdef0123456789abcdef",
64
+ mode = "delta",
65
+ targetVersionCode = 100,
66
+ currentVersionCode = 100
67
+ )
68
+
69
+ assertEquals(ApplyErrorCode.INVALID_ARGS, result?.errorCode)
70
+ }
71
+
72
+ @Test
73
+ fun validatePatchArgsAcceptsBlankMd5() {
74
+ val result = PatchManager.validatePatchArgs(
75
+ version = "1.0.0-h1",
76
+ url = "https://example.com/libapp.so",
77
+ md5 = "",
78
+ mode = "full",
79
+ targetVersionCode = 100,
80
+ currentVersionCode = 100
81
+ )
82
+
83
+ assertNull(result)
84
+ }
85
+
86
+ @Test
87
+ fun validatePatchArgsRejectsBlankVersionOrUrl() {
88
+ val r1 = PatchManager.validatePatchArgs(
89
+ version = "",
90
+ url = "https://example.com/libapp.so",
91
+ md5 = "",
92
+ mode = "full",
93
+ targetVersionCode = 100,
94
+ currentVersionCode = 100
95
+ )
96
+ val r2 = PatchManager.validatePatchArgs(
97
+ version = "1.0.0-h1",
98
+ url = "",
99
+ md5 = "",
100
+ mode = "full",
101
+ targetVersionCode = 100,
102
+ currentVersionCode = 100
103
+ )
104
+ assertEquals(ApplyErrorCode.INVALID_ARGS, r1?.errorCode)
105
+ assertEquals(ApplyErrorCode.INVALID_ARGS, r2?.errorCode)
106
+ }
107
+
108
+ @Test
109
+ fun isSameInstalledPatchMatchesByVersionWhenMd5Blank() {
110
+ val installed = "1.0.0-h1" to "0123456789abcdef0123456789abcdef"
111
+ // 入参 md5 为空 → 仅按 version 判等
112
+ assertTrue(PatchManager.isSameInstalledPatch(installed, "1.0.0-h1", ""))
113
+ assertFalse(PatchManager.isSameInstalledPatch(installed, "1.0.0-h2", ""))
114
+ }
115
+
116
+ @Test
117
+ fun isSameInstalledPatchRequiresVersionAndMd5Match() {
118
+ val installed = "1.0.0-h1" to "0123456789abcdef0123456789abcdef"
119
+
120
+ assertTrue(
121
+ PatchManager.isSameInstalledPatch(
122
+ installed,
123
+ "1.0.0-h1",
124
+ "0123456789ABCDEF0123456789ABCDEF"
125
+ )
126
+ )
127
+ assertFalse(
128
+ PatchManager.isSameInstalledPatch(
129
+ installed,
130
+ "1.0.0-h1",
131
+ "ffffffffffffffffffffffffffffffff"
132
+ )
133
+ )
134
+ assertFalse(
135
+ PatchManager.isSameInstalledPatch(
136
+ installed,
137
+ "1.0.0-h2",
138
+ "0123456789abcdef0123456789abcdef"
139
+ )
140
+ )
141
+ }
142
+
143
+ @Test
144
+ fun isZipPayloadDetectsZipMagic() {
145
+ val zip = File.createTempFile("flutter_patcher", ".zip")
146
+ val so = File.createTempFile("flutter_patcher", ".so")
147
+ try {
148
+ zip.writeBytes(byteArrayOf(0x50, 0x4b, 0x03, 0x04, 0x00))
149
+ so.writeBytes(byteArrayOf(0x7f, 0x45, 0x4c, 0x46))
150
+
151
+ assertTrue(PatchManager.isZipPayload(zip))
152
+ assertFalse(PatchManager.isZipPayload(so))
153
+ } finally {
154
+ zip.delete()
155
+ so.delete()
156
+ }
157
+ }
158
+
159
+ @Test
160
+ fun isDartOnlyAssetsTreatsMissingOrEmptyFilesAsDartOnly() {
161
+ // null block → Dart-only patch.zip (pack omits the assets key entirely)
162
+ assertTrue(PatchManager.isDartOnlyAssets(null))
163
+ // present but no files key
164
+ assertTrue(PatchManager.isDartOnlyAssets(JSONObject()))
165
+ // present with empty files array
166
+ assertTrue(
167
+ PatchManager.isDartOnlyAssets(
168
+ JSONObject().put("files", JSONArray())
169
+ )
170
+ )
171
+ // present with at least one overlay file → real asset patch
172
+ assertFalse(
173
+ PatchManager.isDartOnlyAssets(
174
+ JSONObject().put(
175
+ "files",
176
+ JSONArray().put(JSONObject().put("path", "hero.png"))
177
+ )
178
+ )
179
+ )
180
+ }
181
+
182
+ @Test
183
+ fun selectPackageAbiUsesDevicePriorityOrder() {
184
+ val lib = org.json.JSONObject()
185
+ .put("armeabi-v7a", org.json.JSONObject().put("path", "lib/armeabi-v7a/libapp.so"))
186
+ .put("arm64-v8a", org.json.JSONObject().put("path", "lib/arm64-v8a/libapp.so"))
187
+
188
+ assertEquals(
189
+ "arm64-v8a",
190
+ PatchManager.selectPackageAbi(lib, arrayOf("arm64-v8a", "armeabi-v7a"))
191
+ )
192
+ assertNull(PatchManager.selectPackageAbi(lib, arrayOf("x86_64")))
193
+ }
194
+
195
+ @Test
196
+ fun codecBufferToByteArrayRewindsEncodedManifest() {
197
+ val manifest = linkedMapOf(
198
+ "assets/patch_demo.png" to listOf(mapOf("asset" to "assets/patch_demo.png"))
199
+ )
200
+ val encoded = StandardMessageCodec.INSTANCE.encodeMessage(manifest)!!
201
+ val bytes = codecBufferToByteArray(encoded)
202
+
203
+ assertTrue(bytes.isNotEmpty())
204
+ assertEquals(
205
+ manifest,
206
+ StandardMessageCodec.INSTANCE.decodeMessage(java.nio.ByteBuffer.wrap(bytes))
207
+ )
208
+ }
209
+
210
+ @Test
211
+ fun isSafeZipPathRejectsTraversal() {
212
+ assertTrue(PatchManager.isSafeZipPath("assets/images/hero.png"))
213
+ assertFalse(PatchManager.isSafeZipPath("../hero.png"))
214
+ assertFalse(PatchManager.isSafeZipPath("assets/../hero.png"))
215
+ assertFalse(PatchManager.isSafeZipPath("/assets/hero.png"))
216
+ }
217
+
218
+ @Test
219
+ fun recoverInterruptedInstallRestoresPreviousArtifacts() {
220
+ val root = tempDir()
221
+ try {
222
+ val manager = PatchManager(mockContext(root))
223
+ val patchDir = File(root, PatcherConfig.PATCH_DIR).apply { mkdirs() }
224
+
225
+ File(patchDir, "installing").writeText("installing")
226
+ File(patchDir, "${PatcherConfig.PATCH_FILENAME}.previous").writeText("old-so")
227
+ File(patchDir, "${PatcherConfig.META_FILENAME}.previous").writeText("old-meta")
228
+ File(patchDir, "flutter_assets.previous").apply {
229
+ mkdirs()
230
+ File(this, "AssetManifest.bin").writeText("old-assets")
231
+ }
232
+ File(patchDir, PatcherConfig.PATCH_FILENAME).writeText("new-so")
233
+ File(patchDir, PatcherConfig.META_FILENAME).writeText("new-meta")
234
+ File(patchDir, "flutter_assets").apply {
235
+ mkdirs()
236
+ File(this, "AssetManifest.bin").writeText("new-assets")
237
+ }
238
+ File(patchDir, "${PatcherConfig.PATCH_FILENAME}.pending").writeText("pending-so")
239
+
240
+ invokePrivate(manager, "recoverInterruptedInstall")
241
+
242
+ assertEquals("old-so", File(patchDir, PatcherConfig.PATCH_FILENAME).readText())
243
+ assertEquals("old-meta", File(patchDir, PatcherConfig.META_FILENAME).readText())
244
+ assertEquals(
245
+ "old-assets",
246
+ File(patchDir, "flutter_assets/AssetManifest.bin").readText()
247
+ )
248
+ assertFalse(File(patchDir, "installing").exists())
249
+ assertFalse(File(patchDir, "${PatcherConfig.PATCH_FILENAME}.pending").exists())
250
+ assertFalse(File(patchDir, "${PatcherConfig.PATCH_FILENAME}.previous").exists())
251
+ } finally {
252
+ root.deleteRecursively()
253
+ }
254
+ }
255
+
256
+ @Test
257
+ fun recoverInterruptedInstallWithPartialPreviousKeepsUnbackedCurrentMeta() {
258
+ val root = tempDir()
259
+ try {
260
+ val manager = PatchManager(mockContext(root))
261
+ val patchDir = File(root, PatcherConfig.PATCH_DIR).apply { mkdirs() }
262
+
263
+ File(patchDir, "installing").writeText("installing")
264
+ File(patchDir, "${PatcherConfig.PATCH_FILENAME}.previous").writeText("old-so")
265
+ File(patchDir, PatcherConfig.META_FILENAME).writeText("old-meta")
266
+
267
+ invokePrivate(manager, "recoverInterruptedInstall")
268
+
269
+ assertEquals("old-so", File(patchDir, PatcherConfig.PATCH_FILENAME).readText())
270
+ assertEquals("old-meta", File(patchDir, PatcherConfig.META_FILENAME).readText())
271
+ assertFalse(File(patchDir, "installing").exists())
272
+ } finally {
273
+ root.deleteRecursively()
274
+ }
275
+ }
276
+
277
+ @Test
278
+ fun rollbackPreparedCommitRestoresPreviousPatchWhenNewPatchWasPromoted() {
279
+ val root = tempDir()
280
+ try {
281
+ val manager = PatchManager(mockContext(root))
282
+ val patchDir = File(root, PatcherConfig.PATCH_DIR).apply { mkdirs() }
283
+
284
+ File(patchDir, PatcherConfig.PATCH_FILENAME).writeText("new-so")
285
+ File(patchDir, PatcherConfig.META_FILENAME).writeText("new-meta")
286
+ File(patchDir, "flutter_assets").apply {
287
+ mkdirs()
288
+ File(this, "AssetManifest.bin").writeText("new-assets")
289
+ }
290
+ File(patchDir, "${PatcherConfig.PATCH_FILENAME}.previous").writeText("old-so")
291
+ File(patchDir, "${PatcherConfig.META_FILENAME}.previous").writeText("old-meta")
292
+ File(patchDir, "flutter_assets.previous").apply {
293
+ mkdirs()
294
+ File(this, "AssetManifest.bin").writeText("old-assets")
295
+ }
296
+
297
+ invokePrivate(
298
+ manager,
299
+ "rollbackPreparedCommit",
300
+ true,
301
+ true,
302
+ true,
303
+ false,
304
+ true,
305
+ true,
306
+ true,
307
+ false,
308
+ )
309
+
310
+ assertEquals("old-so", File(patchDir, PatcherConfig.PATCH_FILENAME).readText())
311
+ assertEquals("old-meta", File(patchDir, PatcherConfig.META_FILENAME).readText())
312
+ assertEquals(
313
+ "old-assets",
314
+ File(patchDir, "flutter_assets/AssetManifest.bin").readText()
315
+ )
316
+ assertFalse(File(patchDir, "${PatcherConfig.PATCH_FILENAME}.previous").exists())
317
+ assertFalse(File(patchDir, "flutter_assets.previous").exists())
318
+ } finally {
319
+ root.deleteRecursively()
320
+ }
321
+ }
322
+
323
+ @Test
324
+ fun installPackagePatchReturnsAssetPackageInvalidForMalformedZip() {
325
+ val root = tempDir()
326
+ try {
327
+ val manager = PatchManager(mockContext(root))
328
+ val malformed = File(root, "patch.zip")
329
+ malformed.writeBytes(byteArrayOf(0x50, 0x4b, 0x03, 0x04, 0x00))
330
+
331
+ val result = invokePrivate(
332
+ manager,
333
+ "installPackagePatch",
334
+ malformed,
335
+ "1.0.0-h1",
336
+ "",
337
+ "0123456789abcdef0123456789abcdef",
338
+ "",
339
+ 100L,
340
+ ) as ApplyResult
341
+
342
+ assertFalse(result.ok)
343
+ assertEquals(ApplyErrorCode.ASSET_PACKAGE_INVALID, result.errorCode)
344
+ } finally {
345
+ root.deleteRecursively()
346
+ }
347
+ }
348
+
349
+ @Test
350
+ fun finalizePatchLegacySuccessRemovesOldAssetsOnlyAfterCommit() {
351
+ val root = tempDir()
352
+ try {
353
+ val manager = PatchManager(mockContext(root))
354
+ val patchDir = File(root, PatcherConfig.PATCH_DIR).apply { mkdirs() }
355
+ File(patchDir, PatcherConfig.PATCH_FILENAME).writeText("old-so")
356
+ File(patchDir, PatcherConfig.META_FILENAME).writeText(
357
+ JSONObject().put("version", "old").toString()
358
+ )
359
+ File(patchDir, "flutter_assets").apply {
360
+ mkdirs()
361
+ File(this, "AssetManifest.bin").writeText("old-assets")
362
+ }
363
+ File(patchDir, "flutter_assets.apk").writeText("old-asset-archive")
364
+
365
+ val newSo = File(root, "new.so").apply { writeText("new-so") }
366
+ val newMeta = JSONObject()
367
+ .put("version", "new")
368
+ .put("effectiveMd5", "0123456789abcdef0123456789abcdef")
369
+
370
+ val result = invokePrivate(manager, "finalizePatch", newSo, null, null, newMeta)
371
+
372
+ assertNull(result)
373
+ assertEquals("new-so", File(patchDir, PatcherConfig.PATCH_FILENAME).readText())
374
+ assertEquals("new", JSONObject(File(patchDir, PatcherConfig.META_FILENAME).readText())
375
+ .getString("version"))
376
+ assertFalse(File(patchDir, "flutter_assets").exists())
377
+ assertFalse(File(patchDir, "flutter_assets.apk").exists())
378
+ assertFalse(File(patchDir, "installing").exists())
379
+ } finally {
380
+ root.deleteRecursively()
381
+ }
382
+ }
383
+
384
+ private fun mockContext(filesDir: File): Context {
385
+ val context = Mockito.mock(Context::class.java)
386
+ Mockito.`when`(context.filesDir).thenReturn(filesDir)
387
+ return context
388
+ }
389
+
390
+ private fun tempDir(): File =
391
+ File(System.getProperty("java.io.tmpdir"), "flutter_patcher_test_${System.nanoTime()}")
392
+ .apply { mkdirs() }
393
+
394
+ private fun invokePrivate(target: Any, name: String, vararg args: Any?): Any? {
395
+ val method = target.javaClass.declaredMethods.first {
396
+ it.name == name && it.parameterTypes.size == args.size
397
+ }
398
+ method.isAccessible = true
399
+ return method.invoke(target, *args)
400
+ }
401
+ }
@@ -0,0 +1,30 @@
1
+ package com.flutter_patcher.flutter_patcher
2
+
3
+ import kotlin.test.Test
4
+ import kotlin.test.assertEquals
5
+
6
+ class PatcherConfigTest {
7
+ @Test
8
+ fun loaderFieldCandidatesRoundTripPreservesOrder() {
9
+ val encoded = PatcherConfig.encodeLoaderFieldCandidates(
10
+ listOf("firstLoader", "secondLoader", "flutterLoader")
11
+ )
12
+
13
+ assertEquals(
14
+ listOf("firstLoader", "secondLoader", "flutterLoader"),
15
+ PatcherConfig.decodeLoaderFieldCandidates(encoded)
16
+ )
17
+ }
18
+
19
+ @Test
20
+ fun loaderFieldCandidatesNormalizeBlankAndDuplicateValues() {
21
+ val encoded = PatcherConfig.encodeLoaderFieldCandidates(
22
+ listOf(" flutterLoader ", "", "customLoader", "flutterLoader")
23
+ )
24
+
25
+ assertEquals(
26
+ listOf("flutterLoader", "customLoader"),
27
+ PatcherConfig.decodeLoaderFieldCandidates(encoded)
28
+ )
29
+ }
30
+ }
@@ -0,0 +1,182 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:args/args.dart';
4
+ import 'package:flutter_ota_kit/src/pack/pack.dart';
5
+
6
+ Future<int> main(List<String> argv) async {
7
+ final parser = ArgParser()
8
+ ..addOption(
9
+ 'apk',
10
+ abbr: 'a',
11
+ help: 'Path to the release APK to extract libapp.so and assets from.',
12
+ )
13
+ ..addMultiOption(
14
+ 'assets',
15
+ help:
16
+ 'Flutter asset key(s) from pubspec.yaml to include in patch.zip. '
17
+ 'Can be repeated or comma-separated. '
18
+ 'Use --assets @path/to/list.txt to read keys from a UTF-8 file '
19
+ '(one per line, # starts comments). Inline keys and @file can be mixed: '
20
+ '--assets @list.txt,assets/extra.png',
21
+ )
22
+ ..addOption(
23
+ 'version',
24
+ help: 'Patch version string (goes into manifest.version).',
25
+ )
26
+ ..addOption(
27
+ 'target-version-code',
28
+ help: 'Host APK versionCode the patch is built for (integer).',
29
+ )
30
+ ..addOption(
31
+ 'abi',
32
+ help: 'Preferred ABI to report. Default: all ABIs found in APK.',
33
+ )
34
+ ..addOption(
35
+ 'out',
36
+ abbr: 'o',
37
+ help: 'Output directory. Created if absent.',
38
+ defaultsTo: 'dist',
39
+ )
40
+ ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help.');
41
+
42
+ final ArgResults args;
43
+ try {
44
+ args = parser.parse(argv);
45
+ } on FormatException catch (e) {
46
+ stderr.writeln('error: ${e.message}');
47
+ stderr.writeln(parser.usage);
48
+ return 64;
49
+ }
50
+
51
+ if (args['help'] as bool) {
52
+ stdout.writeln('flutter_ota_kit pack CLI\n');
53
+ stdout.writeln('usage: dart run bin/pack.dart [options]\n');
54
+ stdout.writeln(parser.usage);
55
+ return 0;
56
+ }
57
+
58
+ final apkPath = args['apk'] as String?;
59
+ final version = args['version'] as String?;
60
+ final vcRaw = args['target-version-code'] as String?;
61
+ if (apkPath == null || version == null || vcRaw == null) {
62
+ stderr.writeln(
63
+ 'error: --apk, --version, --target-version-code are required.',
64
+ );
65
+ stderr.writeln(parser.usage);
66
+ return 64;
67
+ }
68
+
69
+ final targetVersionCode = int.tryParse(vcRaw);
70
+ if (targetVersionCode == null) {
71
+ stderr.writeln('error: --target-version-code must be an integer.');
72
+ return 64;
73
+ }
74
+
75
+ final List<String> requestedAssets;
76
+ try {
77
+ requestedAssets = _readRequestedAssets(
78
+ assetsArgs: args['assets'] as List<String>,
79
+ );
80
+ } on PackException catch (e) {
81
+ stderr.writeln('error: ${e.message}');
82
+ return e.exitCode;
83
+ }
84
+
85
+ final apkFile = File(apkPath);
86
+ if (!apkFile.existsSync()) {
87
+ stderr.writeln('error: APK not found: $apkPath');
88
+ return 66;
89
+ }
90
+
91
+ stdout.writeln('[pack] reading ${apkFile.path}');
92
+ try {
93
+ final result = await packPatch(
94
+ apkPath: apkPath,
95
+ version: version,
96
+ targetVersionCode: targetVersionCode,
97
+ abi: args['abi'] as String?,
98
+ requestedAssets: requestedAssets,
99
+ out: args['out'] as String,
100
+ );
101
+ if (requestedAssets.isNotEmpty) {
102
+ stdout.writeln('[pack] assets: ${requestedAssets.length} key(s)');
103
+ stdout.writeln('[pack] overlay files: ${result.assetCount}');
104
+ }
105
+ stdout.writeln('[pack] abis: ${result.abis.join(', ')}');
106
+ stdout.writeln('[pack] payload: ${result.payloadPath}');
107
+ stdout.writeln('[pack] md5: ${result.md5}');
108
+ stdout.writeln('[pack] manifest: ${result.manifestPath}');
109
+ return 0;
110
+ } on PackException catch (e) {
111
+ stderr.writeln('error: ${e.message}');
112
+ return e.exitCode;
113
+ }
114
+ }
115
+
116
+ List<String> _readRequestedAssets({Iterable<String> assetsArgs = const []}) {
117
+ final result = <String>[];
118
+ final seen = <String>{};
119
+
120
+ void appendKey(String raw, {required String source}) {
121
+ final trimmed = raw.trim();
122
+ if (trimmed.isEmpty || trimmed.startsWith('#')) return;
123
+ if (trimmed.startsWith('@')) {
124
+ throw PackException(
125
+ 'nested @-includes are not supported inside $source. '
126
+ 'List asset keys directly, one per line.',
127
+ 65,
128
+ );
129
+ }
130
+ final normalized = trimmed.replaceAll('\\', '/');
131
+ _validateArchiveRelativePath(normalized, 'asset key');
132
+ if (seen.add(normalized)) result.add(normalized);
133
+ }
134
+
135
+ for (final assetsArg in assetsArgs) {
136
+ if (assetsArg.trim().isEmpty) continue;
137
+ for (final token in assetsArg.split(',')) {
138
+ final trimmed = token.trim();
139
+ if (trimmed.isEmpty) continue;
140
+ if (!trimmed.startsWith('@')) {
141
+ appendKey(trimmed, source: '--assets');
142
+ continue;
143
+ }
144
+ final filePath = trimmed.substring(1);
145
+ if (filePath.isEmpty) {
146
+ throw PackException(
147
+ 'invalid --assets value: "@" must be followed by a file path.',
148
+ 64,
149
+ );
150
+ }
151
+ final file = File(filePath);
152
+ if (!file.existsSync()) {
153
+ throw PackException('asset list file not found: $filePath', 66);
154
+ }
155
+ final List<String> lines;
156
+ try {
157
+ lines = file.readAsLinesSync();
158
+ } on FileSystemException catch (e) {
159
+ throw PackException(
160
+ 'failed to read asset list as UTF-8 text: $filePath. '
161
+ 'For a single binary asset, drop the "@" and pass the path directly. '
162
+ '(${e.message})',
163
+ 65,
164
+ );
165
+ }
166
+ for (final line in lines) {
167
+ appendKey(line, source: filePath);
168
+ }
169
+ }
170
+ }
171
+ return result;
172
+ }
173
+
174
+ void _validateArchiveRelativePath(String path, String label) {
175
+ if (path.isEmpty ||
176
+ path.startsWith('/') ||
177
+ path.startsWith('\\') ||
178
+ path.contains('\u0000') ||
179
+ path.split('/').contains('..')) {
180
+ throw PackException('invalid $label: $path', 65);
181
+ }
182
+ }