@_nazmiforreal/flutter-ota 0.1.11 → 0.1.13

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 (176) hide show
  1. package/README.md +1 -1
  2. package/bin/flutter-ota-linux-x64 +0 -0
  3. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
  4. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
  5. package/dart-src/packages/cli-tools/README.md +1 -1
  6. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
  7. package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
  8. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
  9. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
  10. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
  11. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
  12. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
  13. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
  14. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
  15. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
  16. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
  17. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +299 -190
  18. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
  19. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
  20. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
  21. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
  22. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
  23. package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
  24. package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
  25. package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
  26. package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
  27. package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
  28. package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
  29. package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
  30. package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
  31. package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
  32. package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
  33. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
  34. package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
  35. package/dart-src/packages/cli-tools/pubspec.lock +69 -116
  36. package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
  37. package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
  38. package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
  39. package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
  40. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
  41. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
  42. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
  43. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
  44. package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
  45. package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
  46. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
  47. package/dart-src/packages/core/README.md +2 -2
  48. package/dart-src/packages/core/example/lib/main.dart +69 -0
  49. package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
  50. package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
  51. package/dart-src/packages/core/lib/src/bundle.dart +102 -94
  52. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
  53. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
  54. package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
  55. package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
  56. package/dart-src/packages/core/lib/src/metadata.dart +9 -7
  57. package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
  58. package/dart-src/packages/core/lib/src/platform.dart +4 -3
  59. package/dart-src/packages/core/lib/src/rollout.dart +14 -11
  60. package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
  61. package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
  62. package/dart-src/packages/core/lib/src/status.dart +4 -3
  63. package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
  64. package/dart-src/packages/core/lib/src/uuid.dart +1 -2
  65. package/dart-src/packages/core/pubspec.yaml +6 -7
  66. package/dart-src/packages/core/test/rollout_test.dart +15 -8
  67. package/dart-src/packages/core/test/semver_test.dart +5 -2
  68. package/dart-src/packages/core/test/types_test.dart +26 -24
  69. package/dart-src/packages/core/test/uuid_test.dart +3 -1
  70. package/dart-src/packages/core/tool/dbg.dart +1 -0
  71. package/dart-src/plugins/aws/CHANGELOG.md +6 -0
  72. package/dart-src/plugins/aws/example/lib/main.dart +60 -0
  73. package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
  74. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
  75. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
  76. package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
  77. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
  78. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
  79. package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
  80. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
  81. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
  82. package/dart-src/plugins/aws/pubspec.yaml +15 -10
  83. package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
  84. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
  85. package/dart-src/plugins/cloudflare/CHANGELOG.md +7 -0
  86. package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
  87. package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
  88. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
  89. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
  90. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
  91. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
  92. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
  93. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
  94. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
  95. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
  96. package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
  97. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
  98. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
  99. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
  100. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
  101. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
  102. package/dart-src/plugins/plugin-core/CHANGELOG.md +16 -0
  103. package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
  104. package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
  105. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
  106. package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
  107. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
  108. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
  109. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
  110. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
  111. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
  112. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
  113. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
  114. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
  115. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
  116. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
  117. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
  118. package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
  119. package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
  120. package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
  121. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
  122. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
  123. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
  124. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
  125. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
  126. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
  127. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
  128. package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
  129. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
  130. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
  131. package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
  132. package/dart-src/plugins/pocketbase/LICENSE +21 -0
  133. package/dart-src/plugins/pocketbase/README.md +54 -0
  134. package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
  135. package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
  136. package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
  137. package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
  138. package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
  139. package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
  140. package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
  141. package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
  142. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
  143. package/dart-src/plugins/postgres/CHANGELOG.md +6 -0
  144. package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
  145. package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
  146. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
  147. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
  148. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
  149. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
  150. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
  151. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
  152. package/dart-src/plugins/postgres/pubspec.yaml +14 -9
  153. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
  154. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
  155. package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
  156. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
  157. package/dart-src/plugins/supabase/CHANGELOG.md +17 -0
  158. package/dart-src/plugins/supabase/README.md +2 -2
  159. package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
  160. package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
  161. package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
  162. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
  163. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
  164. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
  165. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
  166. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
  167. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
  168. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
  169. package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
  170. package/dart-src/plugins/supabase/pubspec.yaml +14 -9
  171. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
  172. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
  173. package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
  174. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
  175. package/package.json +2 -2
  176. package/scripts/vendor.sh +1 -1
@@ -1,16 +1,25 @@
1
1
  import 'dart:convert' show jsonDecode;
2
2
 
3
3
  import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart'
4
- show Bundle, BundleMetadata, BundlePatchArtifact, Platform, getBundlePatches;
4
+ show
5
+ Bundle,
6
+ BundleMetadata,
7
+ BundlePatchArtifact,
8
+ Platform,
9
+ getBundlePatches;
10
+
11
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
12
+ show buildBundlePatchId, normalizeMetadata;
13
+ // Re-export the shared `buildBundlePatchId` so the cloudflare package's public
14
+ // API still exposes it; `parseMetadata` remains a backward-compatible alias of
15
+ // the shared `normalizeMetadata`.
16
+ export 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
17
+ show buildBundlePatchId;
5
18
 
6
19
  /// Default rollout cohort count — full rollout in the Dart port's per-mille
7
20
  /// scale (matches core's `defaultRolloutCohortCount` = `numericCohortSize`).
8
21
  const int defaultRolloutCohortCount = 1000;
9
22
 
10
- /// Compound patch id `${bundleId}:${baseBundleId}` (matches D1 schema).
11
- String buildBundlePatchId(String bundleId, String baseBundleId) =>
12
- '$bundleId:$baseBundleId';
13
-
14
23
  /// Parse `target_cohorts` which is stored as a JSON string (or already a list).
15
24
  List<String>? parseTargetCohorts(dynamic value) {
16
25
  if (value == null) return null;
@@ -36,21 +45,8 @@ List<String>? parseTargetCohorts(dynamic value) {
36
45
  return null;
37
46
  }
38
47
 
39
- /// Parse `metadata` which may be a JSON string or an already-decoded object.
40
- Map<String, Object?>? parseMetadata(dynamic value) {
41
- if (value == null) return null;
42
- if (value is String) {
43
- try {
44
- return parseMetadata(jsonDecode(value));
45
- } catch (_) {
46
- return null;
47
- }
48
- }
49
- if (value is Map) {
50
- return value.cast<String, Object?>();
51
- }
52
- return null;
53
- }
48
+ /// Backward-compatible alias for [normalizeMetadata].
49
+ Map<String, Object?>? parseMetadata(dynamic value) => normalizeMetadata(value);
54
50
 
55
51
  /// Convert a [Bundle] into the rows inserted into `bundle_patches`.
56
52
  List<Map<String, dynamic>> bundleToPatchRows(Bundle bundle) =>
@@ -72,18 +68,19 @@ Bundle transformRowToBundle(
72
68
  Map<String, dynamic> row, [
73
69
  List<Map<String, dynamic>> patchRows = const [],
74
70
  ]) {
75
- final rawMetadata = parseMetadata(row['metadata']);
71
+ final rawMetadata = normalizeMetadata(row['metadata']);
76
72
  final metadata = rawMetadata == null
77
73
  ? null
78
74
  : BundleMetadata.fromJson(rawMetadata.cast<String, dynamic>());
79
- final patches = patchRows
80
- .toList()
75
+ final patches = patchRows.toList()
81
76
  ..sort(
82
77
  (left, right) =>
83
- (left['order_index'] as num? ?? 0)
84
- .compareTo(right['order_index'] as num? ?? 0) +
85
- (left['base_bundle_id'] as String? ?? '')
86
- .compareTo(right['base_bundle_id'] as String? ?? ''),
78
+ (left['order_index'] as num? ?? 0).compareTo(
79
+ right['order_index'] as num? ?? 0,
80
+ ) +
81
+ (left['base_bundle_id'] as String? ?? '').compareTo(
82
+ right['base_bundle_id'] as String? ?? '',
83
+ ),
87
84
  );
88
85
 
89
86
  final bundlePatches = patches.map((patch) {
@@ -24,10 +24,7 @@ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
24
24
  ResolveUpdateInfoFromBundlesOptions;
25
25
 
26
26
  import 'd1_bundle_mapper.dart'
27
- show
28
- bundleToPatchRows,
29
- defaultRolloutCohortCount,
30
- transformRowToBundle;
27
+ show bundleToPatchRows, defaultRolloutCohortCount, transformRowToBundle;
31
28
  import 'd1_build_where.dart' show buildWhereClause;
32
29
  import 'd1_client.dart' show D1ClientLike;
33
30
 
@@ -77,8 +74,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
77
74
  DatabaseBundleQueryOrder? orderBy,
78
75
  ) async {
79
76
  final where = buildWhereClause(conditions);
80
- final orderBySql =
81
- orderBy?.direction == 'asc' ? 'ORDER BY id ASC' : 'ORDER BY id DESC';
77
+ final orderBySql = orderBy?.direction == 'asc'
78
+ ? 'ORDER BY id ASC'
79
+ : 'ORDER BY id DESC';
82
80
 
83
81
  final rows = await _client.query(
84
82
  'SELECT * FROM bundles ${where.sql} $orderBySql LIMIT ? OFFSET ?',
@@ -89,7 +87,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
89
87
  rows.map((row) => row['id'] as String).toList(),
90
88
  );
91
89
  return rows
92
- .map((row) => transformRowToBundle(row, patchMap[row['id']] ?? const []))
90
+ .map(
91
+ (row) => transformRowToBundle(row, patchMap[row['id']] ?? const []),
92
+ )
93
93
  .toList();
94
94
  }
95
95
 
@@ -105,7 +105,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
105
105
  rows.map((row) => row['id'] as String).toList(),
106
106
  );
107
107
  return rows
108
- .map((row) => transformRowToBundle(row, patchMap[row['id']] ?? const []))
108
+ .map(
109
+ (row) => transformRowToBundle(row, patchMap[row['id']] ?? const []),
110
+ )
109
111
  .toList();
110
112
  }
111
113
 
@@ -121,9 +123,7 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
121
123
  'GROUP BY target_app_version',
122
124
  [channel, platform, minBundleId],
123
125
  );
124
- return rows
125
- .map((row) => row['target_app_version'] as String)
126
- .toList();
126
+ return rows.map((row) => row['target_app_version'] as String).toList();
127
127
  }
128
128
 
129
129
  @override
@@ -197,7 +197,8 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
197
197
  ) async {
198
198
  final where = options.where ?? const DatabaseBundleQueryWhere();
199
199
  final limit = options.limit;
200
- final offset = options.offset ??
200
+ final offset =
201
+ options.offset ??
201
202
  (options.page != null ? (options.page! - 1) * limit : 0);
202
203
 
203
204
  final totalCount = await _getTotalCount(where);
@@ -232,25 +233,19 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
232
233
  Future<void> onUnmount() async {}
233
234
 
234
235
  @override
235
- Future<void> commitBundle({
236
- required List<BundleChange> changedSets,
237
- }) async {
236
+ Future<void> commitBundle({required List<BundleChange> changedSets}) async {
238
237
  if (changedSets.isEmpty) return;
239
238
 
240
239
  for (final op in changedSets) {
241
240
  if (op.operation == BundleChangeOperation.delete) {
242
- await _client.query(
243
- 'DELETE FROM bundle_patches WHERE bundle_id = ?',
244
- [op.data.id],
245
- );
241
+ await _client.query('DELETE FROM bundle_patches WHERE bundle_id = ?', [
242
+ op.data.id,
243
+ ]);
246
244
  await _client.query(
247
245
  'DELETE FROM bundle_patches WHERE base_bundle_id = ?',
248
246
  [op.data.id],
249
247
  );
250
- await _client.query(
251
- 'DELETE FROM bundles WHERE id = ?',
252
- [op.data.id],
253
- );
248
+ await _client.query('DELETE FROM bundles WHERE id = ?', [op.data.id]);
254
249
  } else {
255
250
  final bundle = op.data;
256
251
  await _client.query(
@@ -272,9 +267,7 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
272
267
  bundle.targetAppVersion,
273
268
  bundle.storageUri,
274
269
  bundle.fingerprintHash,
275
- jsonEncode(
276
- bundle.metadata?.toJson() ?? <String, dynamic>{},
277
- ),
270
+ jsonEncode(bundle.metadata?.toJson() ?? <String, dynamic>{}),
278
271
  bundle.manifestStorageUri,
279
272
  bundle.manifestFileHash,
280
273
  bundle.assetBaseStorageUri,
@@ -285,10 +278,9 @@ class D1DatabasePlugin implements AbstractDatabasePlugin {
285
278
  ],
286
279
  );
287
280
 
288
- await _client.query(
289
- 'DELETE FROM bundle_patches WHERE bundle_id = ?',
290
- [bundle.id],
291
- );
281
+ await _client.query('DELETE FROM bundle_patches WHERE bundle_id = ?', [
282
+ bundle.id,
283
+ ]);
292
284
 
293
285
  final patchRows = bundleToPatchRows(bundle);
294
286
  for (final patchRow in patchRows) {
@@ -35,7 +35,7 @@ class R2S3StorageConfig {
35
35
  /// R2 region (defaults to "auto").
36
36
  final String region;
37
37
 
38
- /// Override endpoint (defaults to `https://<accountId>.r2.cloudflarestorage.com`).
38
+ /// Override endpoint (defaults to `https://{accountId}.r2.cloudflarestorage.com`).
39
39
  final String? endpoint;
40
40
 
41
41
  /// Optional factory used to inject an [R2S3ClientLike] in tests.
@@ -44,7 +44,8 @@ class R2S3Client implements R2S3ClientLike {
44
44
  static const String _service = 'r2';
45
45
 
46
46
  Uri _objectUri(String key) {
47
- final host = config.endpoint ??
47
+ final host =
48
+ config.endpoint ??
48
49
  'https://${config.accountId}.r2.cloudflarestorage.com';
49
50
  return Uri.parse('$host/${config.bucketName}/$key');
50
51
  }
@@ -56,11 +57,7 @@ class R2S3Client implements R2S3ClientLike {
56
57
  }
57
58
 
58
59
  @override
59
- Future<void> putObject(
60
- String key,
61
- List<int> body,
62
- String contentType,
63
- ) async {
60
+ Future<void> putObject(String key, List<int> body, String contentType) async {
64
61
  final uri = _objectUri(key);
65
62
  final response = await _send(
66
63
  'PUT',
@@ -77,7 +74,8 @@ class R2S3Client implements R2S3ClientLike {
77
74
  @override
78
75
  Future<bool> headObject(String key) async {
79
76
  final uri = _objectUri(key);
80
- final request = Request('HEAD', uri)..headers.addAll(_authHeaders('HEAD', uri, null));
77
+ final request = Request('HEAD', uri)
78
+ ..headers.addAll(_authHeaders('HEAD', uri, null));
81
79
  final response = await _http.send(request);
82
80
  if (response.statusCode == 404) return false;
83
81
  if (response.statusCode < 200 || response.statusCode >= 300) {
@@ -114,21 +112,18 @@ class R2S3Client implements R2S3ClientLike {
114
112
  'X-Amz-Expires': '$expiresIn',
115
113
  'X-Amz-SignedHeaders': 'host',
116
114
  };
117
- final canonicalQuery = params.entries
118
- .map((e) => '${_uriEncode(e.key)}=${_uriEncode(e.value)}')
119
- .toList()
120
- ..sort();
115
+ final canonicalQuery =
116
+ params.entries
117
+ .map((e) => '${_uriEncode(e.key)}=${_uriEncode(e.value)}')
118
+ .toList()
119
+ ..sort();
121
120
  final queryString = canonicalQuery.join('&');
122
121
 
123
122
  const payloadHash = 'UNSIGNED-PAYLOAD';
124
123
  final canonicalHeaders = 'host:${uri.host}\n';
125
124
  final canonicalRequest =
126
125
  'GET\n${uri.path}\n$queryString\n$canonicalHeaders\nhost\n$payloadHash';
127
- final stringToSign = _stringToSign(
128
- amzDate,
129
- scope,
130
- canonicalRequest,
131
- );
126
+ final stringToSign = _stringToSign(amzDate, scope, canonicalRequest);
132
127
  final signingKey = _signingKey(dateStamp, config.region);
133
128
  final signature = _toHex(_hmac(signingKey, stringToSign));
134
129
  return '${uri.scheme}://${uri.host}${uri.path}?$queryString'
@@ -158,23 +153,21 @@ class R2S3Client implements R2S3ClientLike {
158
153
 
159
154
  // --- signing helpers -------------------------------------------------------
160
155
 
161
- Map<String, String> _authHeaders(
162
- String method,
163
- Uri uri,
164
- List<int>? body,
165
- ) {
156
+ Map<String, String> _authHeaders(String method, Uri uri, List<int>? body) {
166
157
  final now = DateTime.now().toUtc();
167
158
  final amzDate = _amzDate(now);
168
159
  final dateStamp = amzDate.substring(0, 8);
169
160
  final scope = '$dateStamp/${config.region}/$_service/aws4_request';
170
161
  final payloadHash = body == null ? _hashHex(const []) : _hashHex(body);
171
162
 
172
- final canonicalHeaders = 'host:${uri.host}\n'
163
+ final canonicalHeaders =
164
+ 'host:${uri.host}\n'
173
165
  'x-amz-content-sha256:$payloadHash\n'
174
166
  'x-amz-date:$amzDate\n';
175
167
  const signedHeaders = 'host;x-amz-content-sha256;x-amz-date';
176
168
 
177
- final canonicalRequest = '$method\n${uri.path}\n'
169
+ final canonicalRequest =
170
+ '$method\n${uri.path}\n'
178
171
  '\n$canonicalHeaders$signedHeaders\n$payloadHash';
179
172
  final stringToSign = _stringToSign(amzDate, scope, canonicalRequest);
180
173
  final signingKey = _signingKey(dateStamp, config.region);
@@ -205,7 +198,10 @@ class R2S3Client implements R2S3ClientLike {
205
198
 
206
199
  List<StorageObject> _parseListObjects(String xml) {
207
200
  final objects = <StorageObject>[];
208
- final contents = _allMatches(xml, RegExp(r'<Contents>([\s\S]*?)</Contents>'));
201
+ final contents = _allMatches(
202
+ xml,
203
+ RegExp(r'<Contents>([\s\S]*?)</Contents>'),
204
+ );
209
205
  for (final block in contents) {
210
206
  final key = _tag(block, 'Key');
211
207
  final size = int.tryParse(_tag(block, 'Size') ?? '');
@@ -262,11 +258,7 @@ class R2S3Client implements R2S3ClientLike {
262
258
  return m?.group(1);
263
259
  }
264
260
 
265
- static void _assertStatus(
266
- Response response,
267
- List<int> ok,
268
- String operation,
269
- ) {
261
+ static void _assertStatus(Response response, List<int> ok, String operation) {
270
262
  if (!ok.contains(response.statusCode)) {
271
263
  throw Exception(
272
264
  'R2 $operation failed (${response.statusCode}): ${response.body}',
@@ -14,10 +14,10 @@ import 'r2_storage_profile.dart'
14
14
  /// ported here.
15
15
  final StoragePlugin Function(R2S3StorageConfig config) r2Storage =
16
16
  createUniversalStoragePlugin<R2S3StorageConfig>(
17
- name: 'r2Storage',
18
- supportedProtocol: 'r2',
19
- factory: (config) => (
20
- node: createS3StorageProfile(config),
21
- runtime: createS3RuntimeStorageProfile(config),
22
- ),
23
- );
17
+ name: 'r2Storage',
18
+ supportedProtocol: 'r2',
19
+ factory: (config) => (
20
+ node: createS3StorageProfile(config),
21
+ runtime: createS3RuntimeStorageProfile(config),
22
+ ),
23
+ );
@@ -6,20 +6,13 @@ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
6
6
  RuntimeStorageProfile,
7
7
  StorageObject,
8
8
  createStorageKeyBuilder,
9
+ ensureExpectedBucket,
9
10
  getContentType,
10
11
  parseStorageUri;
11
12
 
12
13
  import 'r2_config.dart' show R2S3StorageConfig, resolveR2Client;
13
14
  import 'r2_s3_client.dart' show R2S3ClientLike, readFileBytes, writeFileBytes;
14
15
 
15
- void _ensureExpectedBucket(String bucket, String bucketName) {
16
- if (bucket != bucketName) {
17
- throw ArgumentError(
18
- 'Bucket name mismatch: expected "$bucketName", but found "$bucket".',
19
- );
20
- }
21
- }
22
-
23
16
  /// Build the node (deploy/CLI) storage profile for R2 S3.
24
17
  NodeStorageProfile createS3StorageProfile(R2S3StorageConfig config) {
25
18
  final R2S3ClientLike client = resolveR2Client(config);
@@ -55,21 +48,21 @@ class _R2NodeProfile implements NodeStorageProfile {
55
48
  @override
56
49
  Future<bool> exists(String storageUri) async {
57
50
  final parsed = parseStorageUri(storageUri, 'r2');
58
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
51
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
59
52
  return _client.headObject(parsed.key);
60
53
  }
61
54
 
62
55
  @override
63
56
  Future<void> delete(String storageUri) async {
64
57
  final parsed = parseStorageUri(storageUri, 'r2');
65
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
58
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
66
59
  await _client.deleteObject(parsed.key);
67
60
  }
68
61
 
69
62
  @override
70
63
  Future<void> downloadFile(String storageUri, String filePath) async {
71
64
  final parsed = parseStorageUri(storageUri, 'r2');
72
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
65
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
73
66
  final bytes = await _client.getObjectAsBytes(parsed.key);
74
67
  await writeFileBytes(filePath, bytes);
75
68
  }
@@ -79,8 +72,7 @@ class _R2NodeProfile implements NodeStorageProfile {
79
72
  _client.listObjects(prefix);
80
73
 
81
74
  @override
82
- Future<void> deleteObjects(List<String> keys) =>
83
- _client.deleteObjects(keys);
75
+ Future<void> deleteObjects(List<String> keys) => _client.deleteObjects(keys);
84
76
  }
85
77
 
86
78
  class _R2RuntimeProfile implements RuntimeStorageProfile {
@@ -92,7 +84,7 @@ class _R2RuntimeProfile implements RuntimeStorageProfile {
92
84
  @override
93
85
  Future<Map<String, String>> getDownloadUrl(String storageUri) async {
94
86
  final parsed = parseStorageUri(storageUri, 'r2');
95
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
87
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
96
88
  final fileUrl = await _client.getSignedUrl(parsed.key);
97
89
  return {'fileUrl': fileUrl};
98
90
  }
@@ -100,7 +92,7 @@ class _R2RuntimeProfile implements RuntimeStorageProfile {
100
92
  @override
101
93
  Future<String?> readText(String storageUri) async {
102
94
  final parsed = parseStorageUri(storageUri, 'r2');
103
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
95
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
104
96
  try {
105
97
  return await _client.getObjectAsString(parsed.key);
106
98
  } catch (_) {
@@ -1,19 +1,18 @@
1
1
  name: flutter_ota_kit_cloudflare
2
2
  description: Cloudflare D1 + R2 storage plugins for flutter_ota_kit (faithful port of hot-updater plugins/cloudflare).
3
- version: 0.1.0
4
- publish_to: https://pub.dev
5
- homepage: https://github.com/HYPER12755/flutter_ota_kit
6
- repository: https://github.com/HYPER12755/flutter_ota_kit
7
- issue_tracker: https://github.com/HYPER12755/flutter_ota_kit/issues
8
- documentation: https://github.com/HYPER12755/flutter_ota_kit#readme
3
+ version: 0.1.2
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
9
8
  license: MIT
10
9
 
11
10
  environment:
12
- sdk: ">=3.3.0 <4.0.0"
11
+ sdk: ">=3.13.2 <4.0.0"
13
12
 
14
13
  dependencies:
15
- flutter_ota_kit_core: ^0.1.0
16
- flutter_ota_kit_plugin_core: ^0.0.1
14
+ flutter_ota_kit_core: ^0.1.1
15
+ flutter_ota_kit_plugin_core: ^0.0.3
17
16
  crypto: ^3.0.0
18
17
  http: ^1.2.0
19
18
  path: ^1.9.0
@@ -21,3 +20,9 @@ dependencies:
21
20
  dev_dependencies:
22
21
  test: ^1.25.0
23
22
  lints: ^4.0.0
23
+
24
+ dependency_overrides:
25
+ flutter_ota_kit_core:
26
+ path: ../../packages/core
27
+ flutter_ota_kit_plugin_core:
28
+ path: ../plugin-core
@@ -21,20 +21,19 @@ Bundle _makeBundle({
21
21
  String? targetAppVersion,
22
22
  String? message,
23
23
  List<BundlePatchArtifact>? patches,
24
- }) =>
25
- Bundle(
26
- id: id,
27
- channel: channel,
28
- enabled: enabled,
29
- shouldForceUpdate: false,
30
- fileHash: 'h-$id',
31
- platform: Platform.android,
32
- targetAppVersion: targetAppVersion,
33
- storageUri: 's3://$id',
34
- fingerprintHash: fingerprintHash,
35
- message: message,
36
- patches: patches,
37
- );
24
+ }) => Bundle(
25
+ id: id,
26
+ channel: channel,
27
+ enabled: enabled,
28
+ shouldForceUpdate: false,
29
+ fileHash: 'h-$id',
30
+ platform: Platform.android,
31
+ targetAppVersion: targetAppVersion,
32
+ storageUri: 's3://$id',
33
+ fingerprintHash: fingerprintHash,
34
+ message: message,
35
+ patches: patches,
36
+ );
38
37
 
39
38
  void main() {
40
39
  group('cloudflare d1WorkerDatabase', () {
@@ -57,19 +56,20 @@ void main() {
57
56
  expect(channels, containsAll(['production', 'staging']));
58
57
  });
59
58
 
60
- test('getBundleById returns null for missing, bundle for existing',
61
- () async {
62
- await plugin.appendBundle(_makeBundle(id: 'b1'));
63
- await plugin.commitBundle();
59
+ test(
60
+ 'getBundleById returns null for missing, bundle for existing',
61
+ () async {
62
+ await plugin.appendBundle(_makeBundle(id: 'b1'));
63
+ await plugin.commitBundle();
64
64
 
65
- expect(await plugin.getBundleById('missing'), isNull);
66
- final got = await plugin.getBundleById('b1');
67
- expect(got, isNotNull);
68
- expect(got!.id, 'b1');
69
- });
65
+ expect(await plugin.getBundleById('missing'), isNull);
66
+ final got = await plugin.getBundleById('b1');
67
+ expect(got, isNotNull);
68
+ expect(got!.id, 'b1');
69
+ },
70
+ );
70
71
 
71
- test('commitBundle append -> update -> delete reflects in store',
72
- () async {
72
+ test('commitBundle append -> update -> delete reflects in store', () async {
73
73
  await plugin.appendBundle(_makeBundle(id: 'b1', message: 'original'));
74
74
  await plugin.commitBundle();
75
75
  expect((await plugin.getBundleById('b1'))!.message, 'original');
@@ -85,24 +85,28 @@ void main() {
85
85
  expect(await plugin.getBundleById('b1'), isNull);
86
86
  });
87
87
 
88
- test('getUpdateInfo (appVersion) returns UpdateInfo for compatible bundle',
89
- () async {
90
- await plugin.appendBundle(_makeBundle(id: 'b1', targetAppVersion: '1.0.0'));
91
- await plugin.commitBundle();
88
+ test(
89
+ 'getUpdateInfo (appVersion) returns UpdateInfo for compatible bundle',
90
+ () async {
91
+ await plugin.appendBundle(
92
+ _makeBundle(id: 'b1', targetAppVersion: '1.0.0'),
93
+ );
94
+ await plugin.commitBundle();
92
95
 
93
- final info = await plugin.getUpdateInfo(
94
- AppVersionGetBundlesArgs(
95
- platform: Platform.android,
96
- bundleId: nilUuid,
97
- appVersion: '1.0.0',
98
- channel: 'production',
99
- minBundleId: nilUuid,
100
- ),
101
- );
102
- expect(info, isNotNull);
103
- expect(info!.id, 'b1');
104
- expect(info.status, UpdateStatus.update);
105
- });
96
+ final info = await plugin.getUpdateInfo(
97
+ AppVersionGetBundlesArgs(
98
+ platform: Platform.android,
99
+ bundleId: nilUuid,
100
+ appVersion: '1.0.0',
101
+ channel: 'production',
102
+ minBundleId: nilUuid,
103
+ ),
104
+ );
105
+ expect(info, isNotNull);
106
+ expect(info!.id, 'b1');
107
+ expect(info.status, UpdateStatus.update);
108
+ },
109
+ );
106
110
 
107
111
  test('getUpdateInfo (fingerprint) returns matching bundle', () async {
108
112
  await plugin.appendBundle(_makeBundle(id: 'fp', fingerprintHash: 'fp1'));