@_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
@@ -84,11 +84,7 @@ class AwsS3Client implements AwsS3ClientLike {
84
84
  }
85
85
 
86
86
  @override
87
- Future<void> putObject(
88
- String key,
89
- List<int> body,
90
- String contentType,
91
- ) async {
87
+ Future<void> putObject(String key, List<int> body, String contentType) async {
92
88
  final response = await _send(
93
89
  'PUT',
94
90
  _objectUri(key),
@@ -104,8 +100,8 @@ class AwsS3Client implements AwsS3ClientLike {
104
100
  @override
105
101
  Future<bool> headObject(String key) async {
106
102
  final uri = _objectUri(key);
107
- final request =
108
- Request('HEAD', uri)..headers.addAll(_authHeaders('HEAD', uri, null));
103
+ final request = Request('HEAD', uri)
104
+ ..headers.addAll(_authHeaders('HEAD', uri, null));
109
105
  final response = await _http.send(request);
110
106
  if (response.statusCode == 404) return false;
111
107
  if (response.statusCode < 200 || response.statusCode >= 300) {
@@ -148,10 +144,11 @@ class AwsS3Client implements AwsS3ClientLike {
148
144
 
149
145
  ...tokenParam,
150
146
  };
151
- final canonicalQuery = params.entries
152
- .map((e) => '${_uriEncode(e.key)}=${_uriEncode(e.value)}')
153
- .toList()
154
- ..sort();
147
+ final canonicalQuery =
148
+ params.entries
149
+ .map((e) => '${_uriEncode(e.key)}=${_uriEncode(e.value)}')
150
+ .toList()
151
+ ..sort();
155
152
  final queryString = canonicalQuery.join('&');
156
153
 
157
154
  const payloadHash = 'UNSIGNED-PAYLOAD';
@@ -188,7 +185,8 @@ class AwsS3Client implements AwsS3ClientLike {
188
185
  final scope = '$dateStamp/${config.region}/$_service/aws4_request';
189
186
  final payloadHash = body == null ? _hashHex(const []) : _hashHex(body);
190
187
 
191
- final canonicalHeaders = 'host:${uri.host}\n'
188
+ final canonicalHeaders =
189
+ 'host:${uri.host}\n'
192
190
  'x-amz-content-sha256:$payloadHash\n'
193
191
  'x-amz-date:$amzDate\n'
194
192
  '${config.sessionToken != null ? 'x-amz-security-token:${config.sessionToken}\n' : ''}';
@@ -196,7 +194,8 @@ class AwsS3Client implements AwsS3ClientLike {
196
194
  ? 'host;x-amz-content-sha256;x-amz-date;x-amz-security-token'
197
195
  : 'host;x-amz-content-sha256;x-amz-date';
198
196
 
199
- final canonicalRequest = '$method\n${uri.path}\n'
197
+ final canonicalRequest =
198
+ '$method\n${uri.path}\n'
200
199
  '\n$canonicalHeaders$signedHeaders\n$payloadHash';
201
200
  final stringToSign = _stringToSign(amzDate, scope, canonicalRequest);
202
201
  final signingKey = _signingKey(dateStamp, config.region);
@@ -231,8 +230,10 @@ class AwsS3Client implements AwsS3ClientLike {
231
230
 
232
231
  List<StorageObject> _parseListObjects(String xml) {
233
232
  final objects = <StorageObject>[];
234
- final contents =
235
- _allMatches(xml, RegExp(r'<Contents>([\s\S]*?)</Contents>'));
233
+ final contents = _allMatches(
234
+ xml,
235
+ RegExp(r'<Contents>([\s\S]*?)</Contents>'),
236
+ );
236
237
  for (final block in contents) {
237
238
  final key = _tag(block, 'Key');
238
239
  final size = int.tryParse(_tag(block, 'Size') ?? '');
@@ -273,8 +274,11 @@ class AwsS3Client implements AwsS3ClientLike {
273
274
 
274
275
  static String _uriEncode(String value) => Uri.encodeQueryComponent(value);
275
276
 
276
- static String _xmlEscape(String value) =>
277
- value.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');
277
+ static String _xmlEscape(String value) => value
278
+ .replaceAll('&', '&amp;')
279
+ .replaceAll('<', '&lt;')
280
+ .replaceAll('>', '&gt;')
281
+ .replaceAll('"', '&quot;');
278
282
 
279
283
  static Uint8List _hmac(List<int> key, String data) =>
280
284
  Hmac(sha256, key).convert(utf8.encode(data)).bytes as Uint8List;
@@ -302,7 +306,8 @@ class AwsS3Client implements AwsS3ClientLike {
302
306
  }
303
307
 
304
308
  /// Read a local file's bytes (node profile helper).
305
- Future<List<int>> readFileBytes(String filePath) => File(filePath).readAsBytes();
309
+ Future<List<int>> readFileBytes(String filePath) =>
310
+ File(filePath).readAsBytes();
306
311
 
307
312
  /// Write bytes to a local file, creating parent directories.
308
313
  Future<void> writeFileBytes(String filePath, List<int> bytes) async {
@@ -55,7 +55,8 @@ class AwsSsmClient {
55
55
  final scope = '$dateStamp/$region/$_service/aws4_request';
56
56
  final payloadHash = _hashHex(body);
57
57
 
58
- final canonicalHeaders = 'host:${uri.host}\n'
58
+ final canonicalHeaders =
59
+ 'host:${uri.host}\n'
59
60
  'x-amz-content-sha256:$payloadHash\n'
60
61
  'x-amz-date:$amzDate\n'
61
62
  '${sessionToken != null ? 'x-amz-security-token:$sessionToken\n' : ''}'
@@ -64,7 +65,8 @@ class AwsSsmClient {
64
65
  ? 'host;x-amz-content-sha256;x-amz-date;x-amz-security-token'
65
66
  : 'host;x-amz-content-sha256;x-amz-date';
66
67
 
67
- final canonicalRequest = '$method\n${uri.path}\n'
68
+ final canonicalRequest =
69
+ '$method\n${uri.path}\n'
68
70
  '\n$canonicalHeaders$signedHeaders\n$payloadHash';
69
71
  final stringToSign = _stringToSign(amzDate, scope, canonicalRequest);
70
72
  final signingKey = _signingKey(dateStamp);
@@ -109,8 +111,7 @@ class AwsSsmClient {
109
111
  static Uint8List _hmac(List<int> key, String data) =>
110
112
  Hmac(sha256, key).convert(utf8.encode(data)).bytes as Uint8List;
111
113
 
112
- static String _hashHex(List<int> data) =>
113
- _toHex(sha256.convert(data).bytes);
114
+ static String _hashHex(List<int> data) => _toHex(sha256.convert(data).bytes);
114
115
 
115
116
  static String _toHex(List<int> bytes) =>
116
117
  bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
@@ -2,9 +2,8 @@ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
2
2
  show createUniversalStoragePlugin;
3
3
 
4
4
  import 'aws_config.dart' show AwsS3StorageConfig;
5
- import 'aws_storage_profile.dart' show
6
- createS3RuntimeStorageProfile,
7
- createS3StorageProfile;
5
+ import 'aws_storage_profile.dart'
6
+ show createS3RuntimeStorageProfile, createS3StorageProfile;
8
7
 
9
8
  /// AWS S3 storage plugin (faithful port of hot-updater `s3Storage.ts`).
10
9
  ///
@@ -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 'aws_config.dart' show AwsS3StorageConfig, resolveAwsS3Client;
13
14
  import 'aws_s3_client.dart' show AwsS3ClientLike, 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 AWS S3.
24
17
  NodeStorageProfile createS3StorageProfile(AwsS3StorageConfig config) {
25
18
  final AwsS3ClientLike client = resolveAwsS3Client(config);
@@ -43,9 +36,10 @@ class _S3NodeProfile implements NodeStorageProfile {
43
36
 
44
37
  String _getListPrefix(String prefix) {
45
38
  final normalizedPrefix = prefix.replaceAll(RegExp(r'^/+|/+$'), '');
46
- final value = [_config.basePath ?? '', normalizedPrefix]
47
- .where((s) => s.isNotEmpty)
48
- .join('/');
39
+ final value = [
40
+ _config.basePath ?? '',
41
+ normalizedPrefix,
42
+ ].where((s) => s.isNotEmpty).join('/');
49
43
  return value.isNotEmpty ? '$value/' : '';
50
44
  }
51
45
 
@@ -63,14 +57,14 @@ class _S3NodeProfile implements NodeStorageProfile {
63
57
  @override
64
58
  Future<bool> exists(String storageUri) async {
65
59
  final parsed = parseStorageUri(storageUri, 's3');
66
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
60
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
67
61
  return _client.headObject(parsed.key);
68
62
  }
69
63
 
70
64
  @override
71
65
  Future<void> delete(String storageUri) async {
72
66
  final parsed = parseStorageUri(storageUri, 's3');
73
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
67
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
74
68
 
75
69
  final list = await _client.listObjects(parsed.key);
76
70
  if (list.isEmpty) {
@@ -82,7 +76,7 @@ class _S3NodeProfile implements NodeStorageProfile {
82
76
  @override
83
77
  Future<void> downloadFile(String storageUri, String filePath) async {
84
78
  final parsed = parseStorageUri(storageUri, 's3');
85
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
79
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
86
80
  final bytes = await _client.getObjectAsBytes(parsed.key);
87
81
  await writeFileBytes(filePath, bytes);
88
82
  }
@@ -127,7 +121,7 @@ class _S3RuntimeProfile implements RuntimeStorageProfile {
127
121
  @override
128
122
  Future<Map<String, String>> getDownloadUrl(String storageUri) async {
129
123
  final parsed = parseStorageUri(storageUri, 's3');
130
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
124
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
131
125
  final fileUrl = await _client.getPresignedUrl(parsed.key);
132
126
  return {'fileUrl': fileUrl};
133
127
  }
@@ -135,7 +129,7 @@ class _S3RuntimeProfile implements RuntimeStorageProfile {
135
129
  @override
136
130
  Future<String?> readText(String storageUri) async {
137
131
  final parsed = parseStorageUri(storageUri, 's3');
138
- _ensureExpectedBucket(parsed.bucket, _config.bucketName);
132
+ ensureExpectedBucket(parsed.bucket, _config.bucketName);
139
133
  try {
140
134
  return await _client.getObjectAsString(parsed.key);
141
135
  } catch (_) {
@@ -1,8 +1,5 @@
1
1
  import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
2
- show
3
- RuntimeStorageProfile,
4
- StoragePlugin,
5
- StoragePluginProfiles;
2
+ show RuntimeStorageProfile, StoragePlugin, StoragePluginProfiles;
6
3
 
7
4
  import 'aws_cloudfront_signer.dart' show cloudfrontSignedUrl;
8
5
  import 'aws_ssm_client.dart' show AwsSsmClient;
@@ -67,10 +64,8 @@ class _CloudFrontSignedStoragePlugin implements StoragePlugin {
67
64
  String get supportedProtocol => _base.supportedProtocol;
68
65
 
69
66
  @override
70
- StoragePluginProfiles get profiles => StoragePluginProfiles(
71
- node: _base.profiles.node,
72
- runtime: _runtime,
73
- );
67
+ StoragePluginProfiles get profiles =>
68
+ StoragePluginProfiles(node: _base.profiles.node, runtime: _runtime);
74
69
  }
75
70
 
76
71
  class _CloudFrontSignedRuntimeProfile implements RuntimeStorageProfile {
@@ -124,7 +119,9 @@ class _CloudFrontSignedRuntimeProfile implements RuntimeStorageProfile {
124
119
  if (_config.publicBaseUrlResolver != null) {
125
120
  final value = await _config.publicBaseUrlResolver!();
126
121
  if (value.isEmpty) {
127
- throw StateError('CloudFront publicBaseUrl resolver returned empty URL');
122
+ throw StateError(
123
+ 'CloudFront publicBaseUrl resolver returned empty URL',
124
+ );
128
125
  }
129
126
  return value;
130
127
  }
@@ -1,25 +1,30 @@
1
1
  name: flutter_ota_kit_aws
2
2
  description: AWS S3 storage + S3/DynamoDB database plugins for flutter_ota_kit (faithful port of hot-updater plugins/aws).
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
20
- pointycastle: ^3.9.1
19
+ pointycastle: ^4.0.0
21
20
  asn1lib: ^1.5.4
22
21
 
23
22
  dev_dependencies:
24
23
  test: ^1.25.0
25
24
  lints: ^4.0.0
25
+
26
+ dependency_overrides:
27
+ flutter_ota_kit_core:
28
+ path: ../../packages/core
29
+ flutter_ota_kit_plugin_core:
30
+ path: ../plugin-core
@@ -1,9 +1,5 @@
1
1
  import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart'
2
- show
3
- AppVersionGetBundlesArgs,
4
- Bundle,
5
- Platform,
6
- nilUuid;
2
+ show AppVersionGetBundlesArgs, Bundle, Platform, nilUuid;
7
3
  import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
8
4
  show DatabaseBundleQueryOptions, DatabasePlugin;
9
5
  import 'package:flutter_ota_kit_aws/flutter_ota_kit_aws.dart'
@@ -33,20 +29,19 @@ Bundle _makeBundle({
33
29
  String channel = 'production',
34
30
  String? targetAppVersion,
35
31
  String? fingerprintHash,
36
- }) =>
37
- Bundle(
38
- id: id,
39
- channel: channel,
40
- enabled: true,
41
- shouldForceUpdate: false,
42
- fileHash: 'h-$id',
43
- platform: Platform.android,
44
- targetAppVersion: targetAppVersion,
45
- storageUri: 's3://test-bucket/$id',
46
- fingerprintHash: fingerprintHash,
47
- message: 'msg-$id',
48
- patches: null,
49
- );
32
+ }) => Bundle(
33
+ id: id,
34
+ channel: channel,
35
+ enabled: true,
36
+ shouldForceUpdate: false,
37
+ fileHash: 'h-$id',
38
+ platform: Platform.android,
39
+ targetAppVersion: targetAppVersion,
40
+ storageUri: 's3://test-bucket/$id',
41
+ fingerprintHash: fingerprintHash,
42
+ message: 'msg-$id',
43
+ patches: null,
44
+ );
50
45
 
51
46
  void main() {
52
47
  group('aws s3Database', () {
@@ -57,46 +52,46 @@ void main() {
57
52
  S3DatabaseConfig config({
58
53
  required AwsS3ClientLike Function(S3DatabaseConfig) clientFactory,
59
54
  String? cloudfrontDistributionId,
60
- }) =>
61
- S3DatabaseConfig(
62
- bucketName: 'test-bucket',
63
- region: 'us-east-1',
64
- accessKeyId: 'ak',
65
- secretAccessKey: 'sk',
66
- clientFactory: clientFactory,
67
- cloudfrontDistributionId: cloudfrontDistributionId,
68
- cloudfrontClientFactory: (_) => cf,
69
- );
55
+ }) => S3DatabaseConfig(
56
+ bucketName: 'test-bucket',
57
+ region: 'us-east-1',
58
+ accessKeyId: 'ak',
59
+ secretAccessKey: 'sk',
60
+ clientFactory: clientFactory,
61
+ cloudfrontDistributionId: cloudfrontDistributionId,
62
+ cloudfrontClientFactory: (_) => cf,
63
+ );
70
64
 
71
65
  setUp(() {
72
66
  store = Store();
73
67
  cf = MockCloudFrontClient();
74
- plugin = s3Database(config(
75
- clientFactory: (_) => MockAwsS3Client(store),
76
- cloudfrontDistributionId: 'DIST123',
77
- ))();
68
+ plugin = s3Database(
69
+ config(
70
+ clientFactory: (_) => MockAwsS3Client(store),
71
+ cloudfrontDistributionId: 'DIST123',
72
+ ),
73
+ )();
78
74
  });
79
75
 
80
76
  test('plugin metadata', () {
81
77
  expect(plugin.name, 's3Database');
82
78
  });
83
79
 
84
- test('commitBundle writes update.json and invalidates CloudFront',
85
- () async {
86
- await plugin.appendBundle(
87
- _makeBundle(id: 'b1', targetAppVersion: '1.0.0'),
88
- );
89
- await plugin.commitBundle();
80
+ test(
81
+ 'commitBundle writes update.json and invalidates CloudFront',
82
+ () async {
83
+ await plugin.appendBundle(
84
+ _makeBundle(id: 'b1', targetAppVersion: '1.0.0'),
85
+ );
86
+ await plugin.commitBundle();
90
87
 
91
- final keys = store.objects.keys.toList();
92
- expect(
93
- keys,
94
- contains('production/android/1.0.0/update.json'),
95
- );
88
+ final keys = store.objects.keys.toList();
89
+ expect(keys, contains('production/android/1.0.0/update.json'));
96
90
 
97
- expect(cf.invalidations, isNotEmpty);
98
- expect(cf.invalidations.first.$1, 'DIST123');
99
- });
91
+ expect(cf.invalidations, isNotEmpty);
92
+ expect(cf.invalidations.first.$1, 'DIST123');
93
+ },
94
+ );
100
95
 
101
96
  test('getUpdateInfo (appVersion) resolves committed bundle', () async {
102
97
  await plugin.appendBundle(
@@ -135,14 +130,15 @@ void main() {
135
130
  _makeBundle(id: 'b1', targetAppVersion: '1.0.0'),
136
131
  );
137
132
  await plugin.commitBundle();
138
- expect(store.objects.keys, contains('production/android/1.0.0/update.json'));
133
+ expect(
134
+ store.objects.keys,
135
+ contains('production/android/1.0.0/update.json'),
136
+ );
139
137
 
140
138
  cf.invalidations.clear();
141
139
  final bundle = (await plugin.getBundles(
142
140
  const DatabaseBundleQueryOptions(limit: 100, offset: 0),
143
- ))
144
- .data
145
- .first;
141
+ )).data.first;
146
142
  await plugin.deleteBundle(bundle);
147
143
  await plugin.commitBundle();
148
144
 
@@ -154,12 +150,10 @@ void main() {
154
150
  });
155
151
 
156
152
  test('no CloudFront invalidation when distribution id omitted', () async {
157
- final noCf = s3Database(config(
158
- clientFactory: (_) => MockAwsS3Client(store),
159
- ))();
160
- await noCf.appendBundle(
161
- _makeBundle(id: 'b1', targetAppVersion: '1.0.0'),
162
- );
153
+ final noCf = s3Database(
154
+ config(clientFactory: (_) => MockAwsS3Client(store)),
155
+ )();
156
+ await noCf.appendBundle(_makeBundle(id: 'b1', targetAppVersion: '1.0.0'));
163
157
  await noCf.commitBundle();
164
158
  expect(cf.invalidations, isEmpty);
165
159
  });
@@ -29,8 +29,7 @@ class MockAwsS3Client implements AwsS3ClientLike {
29
29
  }
30
30
 
31
31
  @override
32
- Future<bool> headObject(String key) async =>
33
- store.objects.containsKey(key);
32
+ Future<bool> headObject(String key) async => store.objects.containsKey(key);
34
33
 
35
34
  @override
36
35
  Future<List<int>> getObjectAsBytes(String key) async {
@@ -64,20 +63,16 @@ class MockAwsS3Client implements AwsS3ClientLike {
64
63
  }
65
64
 
66
65
  @override
67
- Future<void> putObject(
68
- String key,
69
- List<int> body,
70
- String contentType,
71
- ) async {
66
+ Future<void> putObject(String key, List<int> body, String contentType) async {
72
67
  store.objects[key] = body;
73
68
  }
74
69
  }
75
70
 
76
71
  /// Build an [AwsS3StorageConfig] wired to an in-memory [Store].
77
72
  AwsS3StorageConfig mockConfig(Store store) => AwsS3StorageConfig(
78
- bucketName: 'test-bucket',
79
- region: 'us-east-1',
80
- accessKeyId: 'test-access-key',
81
- secretAccessKey: 'test-secret',
82
- clientFactory: (_) => MockAwsS3Client(store),
83
- );
73
+ bucketName: 'test-bucket',
74
+ region: 'us-east-1',
75
+ accessKeyId: 'test-access-key',
76
+ secretAccessKey: 'test-secret',
77
+ clientFactory: (_) => MockAwsS3Client(store),
78
+ );
@@ -1,3 +1,10 @@
1
+ ## 0.1.1
2
+
3
+ - Add `parseMetadata` / `parseTargetCohorts` exports from the D1 bundle
4
+ mapper for writing custom Cloudflare Worker logic.
5
+ - Add `defaultRolloutCohortCount` export.
6
+ - Repository moved to `https://github.com/HYPER12755/flutter-ota-kit`.
7
+
1
8
  ## 0.1.0
2
9
 
3
10
  - Initial release of `flutter_ota_kit_cloudflare` as part of the flutter_ota_kit OTA toolkit.
@@ -0,0 +1,62 @@
1
+ // flutter_ota_kit_cloudflare example
2
+ //
3
+ // `flutter_ota_kit_cloudflare` provides Cloudflare D1 (database) + R2
4
+ // (storage) plugins for `flutter_ota_kit`. D1 is Cloudflare's SQLite-
5
+ // based serverless database; R2 is S3-compatible object storage. The
6
+ // combo is the cheapest self-hostable option for low-traffic apps.
7
+ //
8
+ // Most apps don't import this package directly; they wire it through
9
+ // `flutter_ota_kit.configureCloudflare(...)` in their app boot path.
10
+ // This file shows the low-level config types in case you're writing a
11
+ // custom server or a CLI extension.
12
+ //
13
+ // Run: `dart run example/main.dart`
14
+
15
+ import 'package:flutter_ota_kit_cloudflare/flutter_ota_kit_cloudflare.dart';
16
+
17
+ void main() {
18
+ // ── 1. D1 database config (Cloudflare's SQLite). ─────────────────
19
+ // `databaseId` is the UUID of the D1 database. `accountId` is your
20
+ // Cloudflare account ID. `cloudflareApiToken` needs D1 read/write.
21
+ const d1Config = D1DatabaseConfig(
22
+ databaseId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
23
+ accountId: '0123456789abcdef0123456789abcdef',
24
+ cloudflareApiToken: 'cf-token-...',
25
+ );
26
+ print('D1 database: ${d1Config.databaseId}');
27
+ print('Account: ${d1Config.accountId}');
28
+
29
+ // ── 2. R2 storage config (S3-compatible object storage). ──────────
30
+ // R2 is S3-compatible: same `accessKeyId`/`secretAccessKey` flow,
31
+ // but the endpoint host is `{accountId}.r2.cloudflarestorage.com`.
32
+ const r2Config = R2S3StorageConfig(
33
+ accountId: '0123456789abcdef0123456789abcdef',
34
+ bucketName: 'flutter-ota-bundles',
35
+ accessKeyId: 'r2-access-key',
36
+ secretAccessKey: 'r2-secret-key',
37
+ basePath: 'bundles',
38
+ );
39
+ print('R2 bucket: ${r2Config.bucketName}');
40
+ print('Region: ${r2Config.region}');
41
+
42
+ // ── 3. Resolving a custom client (test seam). ────────────────────
43
+ // The `clientFactory` lets you inject a mock S3 client in tests or
44
+ // use a custom S3-compatible client in production.
45
+ // final client = resolveR2Client(r2Config);
46
+
47
+ // ── 4. Row-to-Bundle mapping (server-side helper). ────────────────
48
+ // Use this when reading bundles from a custom D1 query and passing
49
+ // them to the SDK's `PatchInfo.fromJson`. The mapper handles both
50
+ // camelCase and snake_case column names.
51
+ // const row = {
52
+ // 'id': '01a059a6-7d3c-4f3e-8a2c-9c2e3a4b5d6e',
53
+ // 'channel': 'production',
54
+ // 'enabled': true,
55
+ // 'file_hash': '7c4a8d09ca3762af61e59520943dc26494f8941b',
56
+ // 'storage_uri': 'r2://flutter-ota-bundles/01a059a6/patch.zip',
57
+ // 'target_app_version': '1.0.0',
58
+ // 'rollout_cohort_count': 100,
59
+ // };
60
+ // final bundle = transformRowToBundle(row);
61
+ // print('Mapped bundle: ${bundle.id} (${bundle.storageUri})');
62
+ }
@@ -9,10 +9,12 @@ export 'src/d1_bundle_mapper.dart'
9
9
  parseTargetCohorts,
10
10
  transformRowToBundle;
11
11
  export 'src/d1_client.dart' show D1Client, D1ClientLike;
12
- export 'src/d1_config.dart' show D1ClientFactory, D1DatabaseConfig, resolveD1Client;
12
+ export 'src/d1_config.dart'
13
+ show D1ClientFactory, D1DatabaseConfig, resolveD1Client;
13
14
  export 'src/d1_database.dart' show d1Database;
14
15
  export 'src/cloudflare_worker_database.dart'
15
16
  show CloudflareWorkerDatabaseConfig, cloudflareWorkerDatabase;
16
- export 'src/r2_config.dart' show R2S3ClientFactory, R2S3StorageConfig, resolveR2Client;
17
+ export 'src/r2_config.dart'
18
+ show R2S3ClientFactory, R2S3StorageConfig, resolveR2Client;
17
19
  export 'src/r2_s3_client.dart' show R2S3Client, R2S3ClientLike;
18
20
  export 'src/r2_storage.dart' show r2Storage;
@@ -24,6 +24,6 @@ class CloudflareWorkerDatabaseConfig {
24
24
  /// client source differs (an in-process D1 binding instead of the REST API).
25
25
  final cloudflareWorkerDatabase =
26
26
  createDatabasePlugin<CloudflareWorkerDatabaseConfig>(
27
- name: 'd1WorkerDatabase',
28
- factory: (config) => D1DatabasePlugin(config.getDb()),
29
- );
27
+ name: 'd1WorkerDatabase',
28
+ factory: (config) => D1DatabasePlugin(config.getDb()),
29
+ );
@@ -87,7 +87,8 @@ _BuildWhereResult buildWhereClause(DatabaseBundleQueryWhere conditions) {
87
87
  params.add(conditions.fingerprintHash);
88
88
  }
89
89
 
90
- final whereClause =
91
- clauses.isNotEmpty ? ' WHERE ${clauses.join(' AND ')}' : '';
90
+ final whereClause = clauses.isNotEmpty
91
+ ? ' WHERE ${clauses.join(' AND ')}'
92
+ : '';
92
93
  return _BuildWhereResult(whereClause, params);
93
94
  }