@_nazmiforreal/flutter-ota 0.1.0 → 0.1.2

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 (167) hide show
  1. package/bin/flutter-patcher.js +48 -39
  2. package/dart-src/packages/cli-tools/.gitkeep +0 -0
  3. package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
  4. package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
  5. package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
  6. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
  9. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
  10. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
  11. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
  12. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
  13. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
  14. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
  15. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
  16. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
  17. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
  18. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
  19. package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
  20. package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
  21. package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
  22. package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
  23. package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
  24. package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  25. package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  26. package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  27. package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  28. package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  29. package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
  30. package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
  31. package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
  32. package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
  33. package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
  35. package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
  36. package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
  37. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
  38. package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
  39. package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
  40. package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
  41. package/dart-src/packages/core/lib/src/bundle.dart +191 -0
  42. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
  43. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
  44. package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
  45. package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
  46. package/dart-src/packages/core/lib/src/metadata.dart +31 -0
  47. package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
  48. package/dart-src/packages/core/lib/src/platform.dart +15 -0
  49. package/dart-src/packages/core/lib/src/rollout.dart +209 -0
  50. package/dart-src/packages/core/lib/src/semver.dart +70 -0
  51. package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
  52. package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
  53. package/dart-src/packages/core/lib/src/status.dart +24 -0
  54. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  55. package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
  56. package/dart-src/packages/core/lib/src/uuid.dart +45 -0
  57. package/dart-src/packages/core/pubspec.yaml +9 -0
  58. package/dart-src/packages/core/tool/dbg.dart +6 -0
  59. package/dart-src/plugins/aws/.gitkeep +0 -0
  60. package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
  61. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
  62. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
  63. package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
  64. package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
  65. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
  66. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
  67. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
  68. package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
  69. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
  70. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
  71. package/dart-src/plugins/aws/pubspec.yaml +22 -0
  72. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  73. package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
  74. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
  75. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
  76. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
  77. package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
  78. package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
  79. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
  80. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
  81. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
  82. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
  83. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
  84. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
  85. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
  86. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
  87. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
  88. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
  89. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
  90. package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
  91. package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
  92. package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
  93. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
  94. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
  95. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
  96. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
  97. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
  98. package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
  99. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
  100. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
  101. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
  102. package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
  103. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
  104. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
  105. package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
  106. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
  107. package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
  108. package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
  109. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
  110. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
  111. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
  112. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
  113. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
  114. package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
  115. package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
  116. package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
  117. package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
  118. package/dart-src/plugins/postgres/.gitkeep +0 -0
  119. package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
  120. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
  121. package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
  122. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
  123. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
  124. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
  125. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
  126. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
  127. package/dart-src/plugins/postgres/pubspec.yaml +18 -0
  128. package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
  129. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
  130. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
  131. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
  132. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
  133. package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
  134. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
  135. package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
  136. package/dart-src/plugins/standalone/.gitkeep +0 -0
  137. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
  138. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
  139. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
  140. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
  141. package/dart-src/plugins/standalone/pubspec.yaml +18 -0
  142. package/dart-src/plugins/supabase/lib/edge.dart +10 -0
  143. package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
  144. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
  145. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
  146. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
  147. package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
  148. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
  149. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
  150. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
  151. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
  152. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
  153. package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
  154. package/dart-src/plugins/supabase/pubspec.yaml +19 -0
  155. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
  156. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
  157. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
  158. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
  159. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
  160. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
  161. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
  162. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
  163. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
  164. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
  165. package/package.json +2 -1
  166. package/scripts/postinstall.js +53 -25
  167. /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
@@ -0,0 +1,110 @@
1
+ import 'package:path/path.dart' show basename;
2
+
3
+ import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
4
+ show
5
+ NodeStorageProfile,
6
+ RuntimeStorageProfile,
7
+ StorageObject,
8
+ createStorageKeyBuilder,
9
+ getContentType,
10
+ parseStorageUri;
11
+
12
+ import 'r2_config.dart' show R2S3StorageConfig, resolveR2Client;
13
+ import 'r2_s3_client.dart' show R2S3ClientLike, readFileBytes, writeFileBytes;
14
+
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
+ /// Build the node (deploy/CLI) storage profile for R2 S3.
24
+ NodeStorageProfile createS3StorageProfile(R2S3StorageConfig config) {
25
+ final R2S3ClientLike client = resolveR2Client(config);
26
+ final getStorageKey = createStorageKeyBuilder(config.basePath);
27
+
28
+ return _R2NodeProfile(client, config, getStorageKey);
29
+ }
30
+
31
+ /// Build the runtime (update-check) storage profile for R2 S3.
32
+ RuntimeStorageProfile createS3RuntimeStorageProfile(R2S3StorageConfig config) {
33
+ final R2S3ClientLike client = resolveR2Client(config);
34
+ return _R2RuntimeProfile(client, config);
35
+ }
36
+
37
+ class _R2NodeProfile implements NodeStorageProfile {
38
+ _R2NodeProfile(this._client, this._config, this._getStorageKey);
39
+
40
+ final R2S3ClientLike _client;
41
+ final R2S3StorageConfig _config;
42
+ final String Function(String, [String, String, String]) _getStorageKey;
43
+
44
+ @override
45
+ Future<Map<String, String>> upload(String key, String filePath) async {
46
+ final bytes = await readFileBytes(filePath);
47
+ final contentType = getContentType(filePath);
48
+ final filename = basename(filePath);
49
+ final storageKey = _getStorageKey(key, filename);
50
+
51
+ await _client.putObject(storageKey, bytes, contentType);
52
+ return {'storageUri': 'r2://${_config.bucketName}/$storageKey'};
53
+ }
54
+
55
+ @override
56
+ Future<bool> exists(String storageUri) async {
57
+ final parsed = parseStorageUri(storageUri, 'r2');
58
+ _ensureExpectedBucket(parsed.bucket, _config.bucketName);
59
+ return _client.headObject(parsed.key);
60
+ }
61
+
62
+ @override
63
+ Future<void> delete(String storageUri) async {
64
+ final parsed = parseStorageUri(storageUri, 'r2');
65
+ _ensureExpectedBucket(parsed.bucket, _config.bucketName);
66
+ await _client.deleteObject(parsed.key);
67
+ }
68
+
69
+ @override
70
+ Future<void> downloadFile(String storageUri, String filePath) async {
71
+ final parsed = parseStorageUri(storageUri, 'r2');
72
+ _ensureExpectedBucket(parsed.bucket, _config.bucketName);
73
+ final bytes = await _client.getObjectAsBytes(parsed.key);
74
+ await writeFileBytes(filePath, bytes);
75
+ }
76
+
77
+ @override
78
+ Future<List<StorageObject>> listObjects([String? prefix]) =>
79
+ _client.listObjects(prefix);
80
+
81
+ @override
82
+ Future<void> deleteObjects(List<String> keys) =>
83
+ _client.deleteObjects(keys);
84
+ }
85
+
86
+ class _R2RuntimeProfile implements RuntimeStorageProfile {
87
+ _R2RuntimeProfile(this._client, this._config);
88
+
89
+ final R2S3ClientLike _client;
90
+ final R2S3StorageConfig _config;
91
+
92
+ @override
93
+ Future<Map<String, String>> getDownloadUrl(String storageUri) async {
94
+ final parsed = parseStorageUri(storageUri, 'r2');
95
+ _ensureExpectedBucket(parsed.bucket, _config.bucketName);
96
+ final fileUrl = await _client.getSignedUrl(parsed.key);
97
+ return {'fileUrl': fileUrl};
98
+ }
99
+
100
+ @override
101
+ Future<String?> readText(String storageUri) async {
102
+ final parsed = parseStorageUri(storageUri, 'r2');
103
+ _ensureExpectedBucket(parsed.bucket, _config.bucketName);
104
+ try {
105
+ return await _client.getObjectAsString(parsed.key);
106
+ } catch (_) {
107
+ return null;
108
+ }
109
+ }
110
+ }
@@ -0,0 +1,16 @@
1
+ -- Migration number: 0001 2025-01-27T08:37:50.374Z
2
+ -- HotUpdater.bundles
3
+
4
+ CREATE TABLE bundles (
5
+ id TEXT PRIMARY KEY,
6
+ platform TEXT NOT NULL CHECK (platform IN ('ios', 'android')),
7
+ target_app_version TEXT NOT NULL,
8
+ should_force_update INTEGER NOT NULL CHECK (should_force_update IN (0, 1)),
9
+ enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
10
+ file_url TEXT NOT NULL,
11
+ file_hash TEXT NOT NULL,
12
+ git_commit_hash TEXT,
13
+ message TEXT
14
+ );
15
+
16
+ CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
@@ -0,0 +1,8 @@
1
+ -- Migration number: 0002 2025-03-07T16:25:12.486Z
2
+ -- HotUpdater.bundles
3
+
4
+ ALTER TABLE bundles
5
+ ADD COLUMN channel TEXT NOT NULL DEFAULT 'production';
6
+
7
+ ALTER TABLE bundles
8
+ DROP COLUMN file_url;
@@ -0,0 +1,38 @@
1
+ -- Migration number: 0003 2025-05-18T16:25:12.486Z
2
+ -- HotUpdater.bundles
3
+
4
+ ALTER TABLE bundles ADD COLUMN fingerprint_hash TEXT;
5
+ ALTER TABLE bundles ADD COLUMN storage_uri TEXT;
6
+ ALTER TABLE bundles ADD COLUMN metadata JSONB DEFAULT '{}';
7
+
8
+ UPDATE bundles
9
+ SET storage_uri = 'r2://%%BUCKET_NAME%%/' || id || '/bundle.zip'
10
+ WHERE storage_uri IS NULL;
11
+
12
+ CREATE TABLE bundles_temp (
13
+ id TEXT PRIMARY KEY,
14
+ platform TEXT NOT NULL,
15
+ should_force_update INTEGER NOT NULL,
16
+ enabled INTEGER NOT NULL,
17
+ file_hash TEXT NOT NULL,
18
+ git_commit_hash TEXT,
19
+ message TEXT,
20
+ channel TEXT NOT NULL DEFAULT 'production',
21
+ storage_uri TEXT NOT NULL,
22
+ target_app_version TEXT,
23
+ fingerprint_hash TEXT,
24
+ metadata JSONB DEFAULT '{}',
25
+ CHECK ((target_app_version IS NOT NULL) OR (fingerprint_hash IS NOT NULL))
26
+ );
27
+
28
+ INSERT INTO bundles_temp
29
+ SELECT id, platform, should_force_update, enabled, file_hash, git_commit_hash, message, channel, storage_uri, target_app_version, fingerprint_hash, metadata
30
+ FROM bundles;
31
+
32
+ DROP TABLE bundles;
33
+
34
+ ALTER TABLE bundles_temp RENAME TO bundles;
35
+
36
+ CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
37
+ CREATE INDEX bundles_fingerprint_hash_idx ON bundles(fingerprint_hash);
38
+ CREATE INDEX bundles_channel_idx ON bundles(channel);
@@ -0,0 +1,11 @@
1
+ -- Migration number: 0004 2025-12-21T00:00:00.000Z
2
+
3
+ -- HotUpdater.bundles
4
+
5
+ ALTER TABLE bundles ADD COLUMN rollout_cohort_count INTEGER DEFAULT 1000
6
+ CHECK (rollout_cohort_count >= 0 AND rollout_cohort_count <= 1000);
7
+
8
+ CREATE INDEX IF NOT EXISTS bundles_rollout_idx ON bundles(rollout_cohort_count);
9
+
10
+ -- SQLite doesn't have array type, store as JSON text
11
+ ALTER TABLE bundles ADD COLUMN target_cohorts TEXT;
@@ -0,0 +1,24 @@
1
+ -- Migration number: 0005 2026-04-22T00:00:00.000Z
2
+
3
+ -- HotUpdater.bundle_artifact_columns
4
+
5
+ ALTER TABLE bundles ADD COLUMN manifest_storage_uri TEXT;
6
+ ALTER TABLE bundles ADD COLUMN manifest_file_hash TEXT;
7
+ ALTER TABLE bundles ADD COLUMN asset_base_storage_uri TEXT;
8
+
9
+ CREATE TABLE bundle_patches (
10
+ id TEXT PRIMARY KEY,
11
+ bundle_id TEXT NOT NULL,
12
+ base_bundle_id TEXT NOT NULL,
13
+ base_file_hash TEXT NOT NULL,
14
+ patch_file_hash TEXT NOT NULL,
15
+ patch_storage_uri TEXT NOT NULL,
16
+ order_index INTEGER NOT NULL DEFAULT 0,
17
+ FOREIGN KEY (bundle_id) REFERENCES bundles(id) ON DELETE CASCADE,
18
+ FOREIGN KEY (base_bundle_id) REFERENCES bundles(id) ON DELETE CASCADE
19
+ );
20
+
21
+ CREATE INDEX IF NOT EXISTS bundle_patches_bundle_id_idx
22
+ ON bundle_patches(bundle_id);
23
+ CREATE INDEX IF NOT EXISTS bundle_patches_base_bundle_id_idx
24
+ ON bundle_patches(base_bundle_id);
@@ -0,0 +1,20 @@
1
+ name: flutter_patcher_cloudflare
2
+ description: Cloudflare D1 + R2 storage plugins for flutter_patcher (faithful port of hot-updater plugins/cloudflare).
3
+ version: 0.1.0
4
+ publish_to: none
5
+
6
+ environment:
7
+ sdk: ">=3.3.0 <4.0.0"
8
+
9
+ dependencies:
10
+ flutter_patcher_core:
11
+ path: ../../packages/core
12
+ flutter_patcher_plugin_core:
13
+ path: ../../plugins/plugin-core
14
+ crypto: ^3.0.0
15
+ http: ^1.2.0
16
+ path: ^1.9.0
17
+
18
+ dev_dependencies:
19
+ test: ^1.25.0
20
+ lints: ^4.0.0
@@ -0,0 +1,42 @@
1
+ -- HotUpdater.bundles
2
+
3
+ CREATE TABLE bundles (
4
+ id TEXT PRIMARY KEY,
5
+ platform TEXT NOT NULL CHECK (platform IN ('ios', 'android')),
6
+ target_app_version TEXT,
7
+ should_force_update INTEGER NOT NULL CHECK (should_force_update IN (0, 1)),
8
+ enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
9
+ file_hash TEXT NOT NULL,
10
+ git_commit_hash TEXT,
11
+ message TEXT,
12
+ channel TEXT NOT NULL DEFAULT 'production',
13
+ storage_uri TEXT NOT NULL,
14
+ fingerprint_hash TEXT,
15
+ metadata JSONB DEFAULT '{}',
16
+ manifest_storage_uri TEXT,
17
+ manifest_file_hash TEXT,
18
+ asset_base_storage_uri TEXT,
19
+ rollout_cohort_count INTEGER DEFAULT 1000
20
+ CHECK (rollout_cohort_count >= 0 AND rollout_cohort_count <= 1000),
21
+ target_cohorts TEXT,
22
+ CHECK ((target_app_version IS NOT NULL) OR (fingerprint_hash IS NOT NULL))
23
+ );
24
+
25
+ CREATE TABLE bundle_patches (
26
+ id TEXT PRIMARY KEY,
27
+ bundle_id TEXT NOT NULL,
28
+ base_bundle_id TEXT NOT NULL,
29
+ base_file_hash TEXT NOT NULL,
30
+ patch_file_hash TEXT NOT NULL,
31
+ patch_storage_uri TEXT NOT NULL,
32
+ order_index INTEGER NOT NULL DEFAULT 0,
33
+ FOREIGN KEY (bundle_id) REFERENCES bundles(id) ON DELETE CASCADE,
34
+ FOREIGN KEY (base_bundle_id) REFERENCES bundles(id) ON DELETE CASCADE
35
+ );
36
+
37
+ CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
38
+ CREATE INDEX bundles_fingerprint_hash_idx ON bundles(fingerprint_hash);
39
+ CREATE INDEX bundles_channel_idx ON bundles(channel);
40
+ CREATE INDEX bundles_rollout_idx ON bundles(rollout_cohort_count);
41
+ CREATE INDEX bundle_patches_bundle_id_idx ON bundle_patches(bundle_id);
42
+ CREATE INDEX bundle_patches_base_bundle_id_idx ON bundle_patches(base_bundle_id);
@@ -0,0 +1,42 @@
1
+ library;
2
+
3
+ // Re-export core types for convenience.
4
+ export 'package:flutter_patcher_core/flutter_patcher_core.dart'
5
+ show
6
+ Bundle,
7
+ GetBundlesArgs,
8
+ Platform,
9
+ UpdateInfo,
10
+ semverSatisfies;
11
+
12
+ // Plugin-core types.
13
+ export 'src/types.dart';
14
+
15
+ // Helpers.
16
+ export 'src/calculate_pagination.dart';
17
+ export 'src/query_bundles.dart';
18
+ export 'src/paginate_bundles.dart';
19
+ export 'src/filter_compatible_app_versions.dart';
20
+ export 'src/parse_storage_uri.dart';
21
+ export 'src/content_addressed_assets.dart';
22
+ export 'src/compression_format.dart';
23
+ export 'src/storage_profile.dart';
24
+ export 'src/create_storage_key_builder.dart';
25
+ export 'src/is_object.dart';
26
+ export 'src/asset_storage_layout.dart';
27
+ export 'src/bundle_storage_layout.dart';
28
+ export 'src/legacy_asset_storage_layout.dart';
29
+
30
+ // Factories.
31
+ export 'src/bundle_unit_of_work.dart';
32
+ export 'src/bundle_unit_of_work_store.dart'
33
+ show getRequestBundleUnitOfWork, clearUnitOfWorkStore;
34
+ export 'src/request_update_bundle_state.dart';
35
+ export 'src/create_database_plugin.dart';
36
+ export 'src/create_blob_database_plugin.dart';
37
+ export 'src/create_storage_plugin.dart';
38
+ export 'src/generate_min_bundle_id.dart';
39
+ export 'src/uuidv7.dart';
40
+ export 'src/get_update_info.dart';
41
+ export 'src/resolve_update_info_from_bundles.dart';
42
+ export 'src/resolve_update_info_from_bundles.dart';
@@ -0,0 +1,94 @@
1
+ import 'content_addressed_assets.dart' show getContentAddressedAssetStoragePath;
2
+ import 'legacy_asset_storage_layout.dart' show getLegacyManifestAssetStoragePath;
3
+
4
+ /// The two supported asset storage layout strategies.
5
+ typedef AssetStorageLayout = String;
6
+
7
+ const String assetLayoutContentAddressed = 'content-addressed';
8
+ const String assetLayoutLegacyFiles = 'legacy-files';
9
+
10
+ /// Whether [assetPath] is a brotli-compressed manifest path
11
+ /// (e.g. `index.android.bundle` → `index.android.bundle.br`).
12
+ bool isBrotliManifestAssetPath(String assetPath) {
13
+ return RegExp(r'(^|/)index\.[^/]+\.bundle$')
14
+ .hasMatch(assetPath.replaceAll('\\', '/'));
15
+ }
16
+
17
+ /// Append `.br` to brotli manifest asset paths, pass through otherwise.
18
+ String getManifestAssetDownloadPath(String assetPath) {
19
+ return isBrotliManifestAssetPath(assetPath) ? '$assetPath.br' : assetPath;
20
+ }
21
+
22
+ /// Build a full storage URI by joining a base URI with a relative path.
23
+ ///
24
+ /// Segments of [relativePath] are percent-encoded individually.
25
+ String createStorageUriWithRelativePath({
26
+ required String baseStorageUri,
27
+ required String relativePath,
28
+ }) {
29
+ final storageUrl = Uri.parse(baseStorageUri);
30
+ final normalizedBasePath =
31
+ storageUrl.path.replaceAll(RegExp(r'/+$'), '');
32
+ final normalizedRelativePath = relativePath
33
+ .replaceAll('\\', '/')
34
+ .split('/')
35
+ .where((s) => s.isNotEmpty)
36
+ .map(Uri.encodeComponent)
37
+ .join('/');
38
+
39
+ return storageUrl
40
+ .replace(path: '$normalizedBasePath/$normalizedRelativePath')
41
+ .toString();
42
+ }
43
+
44
+ /// Determine the asset storage layout from the base URI.
45
+ ///
46
+ /// Returns `'content-addressed'` if the URI path ends with `/assets`,
47
+ /// otherwise `'legacy-files'`.
48
+ AssetStorageLayout getAssetStorageLayout(String assetBaseStorageUri) {
49
+ final pathname =
50
+ Uri.parse(assetBaseStorageUri).path.replaceAll(RegExp(r'/+$'), '');
51
+ return pathname.endsWith('/assets') || pathname == '/assets'
52
+ ? assetLayoutContentAddressed
53
+ : assetLayoutLegacyFiles;
54
+ }
55
+
56
+ /// Convenience check: is the base URI using content-addressed layout?
57
+ bool isContentAddressedAssetBaseStorageUri(String assetBaseStorageUri) {
58
+ return getAssetStorageLayout(assetBaseStorageUri) ==
59
+ assetLayoutContentAddressed;
60
+ }
61
+
62
+ /// Compute the storage path for a manifest asset, dispatching on layout.
63
+ String getManifestAssetStoragePath({
64
+ required String assetBaseStorageUri,
65
+ required String assetPath,
66
+ required String fileHash,
67
+ }) {
68
+ final layout = getAssetStorageLayout(assetBaseStorageUri);
69
+
70
+ if (layout == assetLayoutContentAddressed) {
71
+ return getContentAddressedAssetStoragePath(
72
+ assetPath: assetPath,
73
+ fileHash: fileHash,
74
+ );
75
+ }
76
+
77
+ return getLegacyManifestAssetStoragePath(assetPath: assetPath);
78
+ }
79
+
80
+ /// Build a full storage URI for a manifest asset.
81
+ String resolveManifestAssetStorageUri({
82
+ required String assetBaseStorageUri,
83
+ required String assetPath,
84
+ required String fileHash,
85
+ }) {
86
+ return createStorageUriWithRelativePath(
87
+ baseStorageUri: assetBaseStorageUri,
88
+ relativePath: getManifestAssetStoragePath(
89
+ assetBaseStorageUri: assetBaseStorageUri,
90
+ assetPath: assetPath,
91
+ fileHash: fileHash,
92
+ ),
93
+ );
94
+ }
@@ -0,0 +1,44 @@
1
+ /// The top-level prefix for bundle storage keys.
2
+ const String bundleStoragePrefix = 'bundles';
3
+
4
+ /// Build a storage key for a bundle, e.g. `bundles/<bundleId>/patch.zip`.
5
+ String createBundleStorageKey(String bundleId, [List<String> relativePaths = const []]) {
6
+ return [bundleStoragePrefix, bundleId, ...relativePaths]
7
+ .where((s) => s.isNotEmpty)
8
+ .join('/');
9
+ }
10
+
11
+ /// Replace the bundle-id segment in a storage URI with a relative path.
12
+ ///
13
+ /// For example, given a storage URI containing `.../bundles/<bundleId>/patch.zip`
14
+ /// and a [relativePath] of `files/manifest.json`, this returns a URI pointing
15
+ /// to `.../files/manifest.json`.
16
+ String createStorageRootUriWithPath(
17
+ String storageUri,
18
+ String bundleId,
19
+ String relativePath,
20
+ ) {
21
+ final storageUrl = Uri.parse(storageUri);
22
+ final segments = storageUrl.path.split('/').where((s) => s.isNotEmpty).toList();
23
+ final bundleIndex = segments.lastIndexOf(bundleId);
24
+ if (bundleIndex < 0) {
25
+ throw StateError(
26
+ 'Storage URI does not contain bundle id: $bundleId',
27
+ );
28
+ }
29
+
30
+ final rootSegments = segments.sublist(0, bundleIndex);
31
+ if (rootSegments.isNotEmpty && rootSegments.last == bundleStoragePrefix) {
32
+ rootSegments.removeLast();
33
+ }
34
+
35
+ final relativeSegments = relativePath
36
+ .split('/')
37
+ .where((s) => s.isNotEmpty)
38
+ .map(Uri.encodeComponent)
39
+ .toList();
40
+
41
+ return storageUrl
42
+ .replace(path: '/${[...rootSegments, ...relativeSegments].join('/')}')
43
+ .toString();
44
+ }