@_nazmiforreal/flutter-ota 0.1.5 → 0.1.7

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 (147) hide show
  1. package/README.md +13 -13
  2. package/bin/{flutter-patcher-linux-x64 → flutter-ota-linux-x64} +0 -0
  3. package/bin/{flutter-patcher.js → flutter-ota.js} +4 -4
  4. package/dart-src/packages/cli-tools/bin/{flutter_patcher.dart → flutter_ota_kit.dart} +2 -2
  5. package/dart-src/packages/cli-tools/lib/{flutter_patcher_cli.dart → flutter_ota_kit_cli.dart} +5 -3
  6. package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -25
  7. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +10 -4
  8. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +5 -3
  9. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +279 -10
  10. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +7 -7
  11. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +5 -5
  12. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +4 -4
  13. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -4
  14. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +3 -3
  15. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +1 -1
  16. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +294 -45
  17. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +1 -1
  18. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +218 -69
  19. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +5 -5
  20. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +117 -0
  21. package/dart-src/packages/cli-tools/lib/src/config.dart +29 -78
  22. package/dart-src/packages/cli-tools/lib/src/operations.dart +4 -4
  23. package/dart-src/packages/cli-tools/lib/src/pack.dart +12 -6
  24. package/dart-src/packages/cli-tools/lib/src/runner.dart +25 -6
  25. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +5 -3
  26. package/dart-src/packages/cli-tools/lib/src/verbose.dart +3 -0
  27. package/dart-src/packages/cli-tools/migrations/aws/README.md +7 -0
  28. package/dart-src/packages/cli-tools/migrations/postgres/bundles.sql +45 -0
  29. package/dart-src/packages/cli-tools/migrations/postgres/get_target_app_version_list.sql +21 -0
  30. package/dart-src/packages/cli-tools/migrations/postgres/get_update_info_by_app_version.sql +126 -0
  31. package/dart-src/packages/cli-tools/migrations/postgres/get_update_info_by_fingerprint_hash.sql +125 -0
  32. package/dart-src/packages/cli-tools/migrations/postgres/hash_user_id.sql +30 -0
  33. package/dart-src/packages/cli-tools/migrations/postgres/is_device_eligible.sql +237 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +11 -0
  35. package/dart-src/packages/cli-tools/pubspec.yaml +9 -11
  36. package/dart-src/packages/cli-tools/test/backends_test.dart +4 -4
  37. package/dart-src/packages/cli-tools/test/build_pack_test.dart +1 -1
  38. package/dart-src/packages/cli-tools/test/cli_test.dart +3 -3
  39. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
  40. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
  41. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +7 -7
  42. package/dart-src/packages/core/lib/{flutter_patcher_core.dart → flutter_ota_kit_core.dart} +1 -1
  43. package/dart-src/packages/core/pubspec.yaml +2 -2
  44. package/dart-src/packages/core/test/rollout_test.dart +1 -1
  45. package/dart-src/packages/core/test/semver_test.dart +1 -1
  46. package/dart-src/packages/core/test/types_test.dart +2 -2
  47. package/dart-src/packages/core/test/uuid_test.dart +1 -1
  48. package/dart-src/packages/core/tool/dbg.dart +1 -1
  49. package/dart-src/plugins/aws/lib/{flutter_patcher_aws.dart → flutter_ota_kit_aws.dart} +0 -2
  50. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +1 -1
  51. package/dart-src/plugins/aws/lib/src/aws_database.dart +1 -1
  52. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +1 -1
  53. package/dart-src/plugins/aws/lib/src/aws_storage.dart +1 -1
  54. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +25 -3
  55. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +1 -1
  56. package/dart-src/plugins/aws/pubspec.yaml +4 -4
  57. package/dart-src/plugins/aws/test/aws_database_test.dart +3 -3
  58. package/dart-src/plugins/aws/test/aws_storage_test.dart +2 -2
  59. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +2 -2
  60. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +1 -1
  61. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +1 -1
  62. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +1 -1
  63. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +1 -1
  64. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +2 -2
  65. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +1 -1
  66. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +1 -1
  67. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +1 -1
  68. package/dart-src/plugins/cloudflare/pubspec.yaml +4 -4
  69. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +2 -2
  70. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +2 -2
  71. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +2 -2
  72. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +2 -2
  73. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +2 -2
  74. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +2 -2
  75. package/dart-src/plugins/plugin-core/lib/{flutter_patcher_plugin_core.dart → flutter_ota_kit_plugin_core.dart} +1 -1
  76. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +1 -1
  77. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +1 -1
  78. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +1 -1
  79. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +1 -1
  80. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +1 -1
  81. package/dart-src/plugins/plugin-core/lib/src/types.dart +2 -2
  82. package/dart-src/plugins/plugin-core/pubspec.yaml +3 -3
  83. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +3 -3
  84. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +2 -2
  85. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +2 -2
  86. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +2 -2
  87. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +5 -5
  88. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +2 -2
  89. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +2 -2
  90. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +2 -2
  91. package/dart-src/plugins/plugin-core/test/helpers_test.dart +2 -2
  92. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +2 -2
  93. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +3 -3
  94. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +1 -1
  95. package/dart-src/plugins/postgres/lib/{flutter_patcher_postgres.dart → flutter_ota_kit_postgres.dart} +1 -1
  96. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +1 -1
  97. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +2 -2
  98. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +2 -2
  99. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +9 -9
  100. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +1 -1
  101. package/dart-src/plugins/postgres/pubspec.yaml +4 -4
  102. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +2 -2
  103. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +2 -2
  104. package/dart-src/plugins/postgres/test/postgres_database_test.dart +2 -2
  105. package/dart-src/plugins/supabase/lib/edge.dart +1 -1
  106. package/dart-src/plugins/supabase/lib/{flutter_patcher_supabase.dart → flutter_ota_kit_supabase.dart} +1 -1
  107. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +1 -1
  108. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +60 -20
  109. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +2 -2
  110. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +1 -1
  111. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +1 -1
  112. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +2 -5
  113. package/dart-src/plugins/supabase/pubspec.yaml +4 -4
  114. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +5 -5
  115. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +3 -3
  116. package/dart-src/plugins/supabase/test/supabase_database_test.dart +3 -3
  117. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +2 -2
  118. package/migrations/aws/README.md +7 -0
  119. package/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  120. package/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  121. package/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  122. package/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  123. package/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  124. package/migrations/cloudflare/bundles.sql +42 -0
  125. package/migrations/postgres/bundles.sql +45 -0
  126. package/migrations/postgres/get_target_app_version_list.sql +21 -0
  127. package/migrations/postgres/get_update_info_by_app_version.sql +126 -0
  128. package/migrations/postgres/get_update_info_by_fingerprint_hash.sql +125 -0
  129. package/migrations/postgres/hash_user_id.sql +30 -0
  130. package/migrations/postgres/is_device_eligible.sql +237 -0
  131. package/package.json +4 -4
  132. package/scripts/postinstall.js +15 -15
  133. package/scripts/vendor.sh +1 -1
  134. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +0 -496
  135. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +0 -743
  136. package/dart-src/packages/cli-tools/pubspec.lock +0 -638
  137. package/dart-src/packages/cli-tools/test/standalone_backend_test.dart +0 -315
  138. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +0 -72
  139. package/dart-src/plugins/aws/test/aws_lambda_edge_storage_test.dart +0 -190
  140. package/dart-src/plugins/standalone/.gitkeep +0 -0
  141. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +0 -12
  142. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +0 -107
  143. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +0 -233
  144. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +0 -175
  145. package/dart-src/plugins/standalone/pubspec.yaml +0 -18
  146. package/dart-src/plugins/standalone/test/standalone_test.dart +0 -264
  147. /package/dart-src/plugins/cloudflare/lib/{flutter_patcher_cloudflare.dart → flutter_ota_kit_cloudflare.dart} +0 -0
@@ -0,0 +1,125 @@
1
+ -- HotUpdater.get_update_info_by_fingerprint_hash
2
+
3
+ DROP FUNCTION IF EXISTS get_update_info_by_fingerprint_hash;
4
+
5
+ CREATE OR REPLACE FUNCTION get_update_info_by_fingerprint_hash (
6
+ app_platform platforms,
7
+ bundle_id uuid,
8
+ min_bundle_id uuid,
9
+ target_channel text,
10
+ target_fingerprint_hash text,
11
+ cohort TEXT DEFAULT NULL
12
+ )
13
+ RETURNS TABLE (
14
+ id uuid,
15
+ should_force_update boolean,
16
+ message text,
17
+ status text,
18
+ storage_uri text,
19
+ file_hash text
20
+ )
21
+ LANGUAGE plpgsql
22
+ AS
23
+ $$
24
+ DECLARE
25
+ NIL_UUID CONSTANT uuid := '00000000-0000-0000-0000-000000000000';
26
+ BEGIN
27
+ RETURN QUERY
28
+ WITH candidate_bundles AS (
29
+ SELECT
30
+ b.id,
31
+ b.should_force_update,
32
+ b.message,
33
+ b.storage_uri,
34
+ b.file_hash,
35
+ b.rollout_cohort_count,
36
+ b.target_cohorts
37
+ FROM bundles b
38
+ WHERE b.enabled = TRUE
39
+ AND b.platform = app_platform
40
+ AND b.id >= min_bundle_id
41
+ AND b.channel = target_channel
42
+ AND b.fingerprint_hash = target_fingerprint_hash
43
+ ),
44
+ current_candidate AS (
45
+ SELECT
46
+ cb.id,
47
+ is_cohort_eligible(
48
+ cb.id,
49
+ cohort,
50
+ cb.rollout_cohort_count,
51
+ cb.target_cohorts
52
+ ) AS is_eligible
53
+ FROM candidate_bundles cb
54
+ WHERE cb.id = bundle_id
55
+ LIMIT 1
56
+ ),
57
+ eligible_update_candidate AS (
58
+ SELECT
59
+ cb.id,
60
+ cb.should_force_update,
61
+ cb.message,
62
+ 'UPDATE' AS status,
63
+ cb.storage_uri,
64
+ cb.file_hash
65
+ FROM candidate_bundles cb
66
+ WHERE cb.id > bundle_id
67
+ AND is_cohort_eligible(
68
+ cb.id,
69
+ cohort,
70
+ cb.rollout_cohort_count,
71
+ cb.target_cohorts
72
+ )
73
+ ORDER BY cb.id DESC
74
+ LIMIT 1
75
+ ),
76
+ rollback_candidate AS (
77
+ SELECT
78
+ cb.id,
79
+ TRUE AS should_force_update,
80
+ cb.message,
81
+ 'ROLLBACK' AS status,
82
+ cb.storage_uri,
83
+ cb.file_hash
84
+ FROM candidate_bundles cb
85
+ WHERE cb.id < bundle_id
86
+ AND NOT EXISTS (
87
+ SELECT 1
88
+ FROM current_candidate
89
+ WHERE current_candidate.is_eligible = TRUE
90
+ )
91
+ AND NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
92
+ ORDER BY cb.id DESC
93
+ LIMIT 1
94
+ ),
95
+ final_result AS (
96
+ SELECT * FROM eligible_update_candidate
97
+ UNION ALL
98
+ SELECT * FROM rollback_candidate
99
+ WHERE NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
100
+ )
101
+ SELECT *
102
+ FROM final_result
103
+ WHERE final_result.id != bundle_id
104
+
105
+ UNION ALL
106
+
107
+ SELECT
108
+ NIL_UUID AS id,
109
+ TRUE AS should_force_update,
110
+ NULL AS message,
111
+ 'ROLLBACK' AS status,
112
+ NULL AS storage_uri,
113
+ NULL AS file_hash
114
+ WHERE (SELECT COUNT(*) FROM final_result) = 0
115
+ AND bundle_id != NIL_UUID
116
+ AND bundle_id > min_bundle_id
117
+ AND NOT EXISTS (
118
+ SELECT 1
119
+ FROM current_candidate
120
+ WHERE current_candidate.is_eligible = TRUE
121
+ )
122
+ AND NOT EXISTS (SELECT 1 FROM eligible_update_candidate)
123
+ AND NOT EXISTS (SELECT 1 FROM rollback_candidate);
124
+ END;
125
+ $$;
@@ -0,0 +1,30 @@
1
+ -- Deterministic hash function matching JavaScript implementation
2
+ -- Returns hash value in range [0, 99]
3
+ CREATE OR REPLACE FUNCTION hash_user_id(user_id TEXT)
4
+ RETURNS INTEGER
5
+ LANGUAGE plpgsql
6
+ IMMUTABLE
7
+ AS $$
8
+ DECLARE
9
+ hash BIGINT := 0;
10
+ char_code INTEGER;
11
+ i INTEGER;
12
+ normalized BIGINT;
13
+ BEGIN
14
+ -- Replicate JavaScript hash algorithm
15
+ FOR i IN 1..length(user_id) LOOP
16
+ char_code := ascii(substring(user_id from i for 1));
17
+ hash := (hash * 31) + char_code;
18
+
19
+ -- Simulate JavaScript's |= 0 by wrapping into a signed 32-bit range.
20
+ normalized := mod(hash + 2147483648, 4294967296);
21
+ IF normalized < 0 THEN
22
+ normalized := normalized + 4294967296;
23
+ END IF;
24
+ hash := normalized - 2147483648;
25
+ END LOOP;
26
+
27
+ -- Return absolute value modulo 100
28
+ RETURN abs(hash % 100);
29
+ END;
30
+ $$;
@@ -0,0 +1,237 @@
1
+ -- HotUpdater.is_cohort_eligible
2
+ -- Cohort eligibility helpers matching @hot-updater/core rollout.ts
3
+
4
+ CREATE OR REPLACE FUNCTION positive_mod(
5
+ value INTEGER,
6
+ modulus INTEGER
7
+ )
8
+ RETURNS INTEGER
9
+ LANGUAGE plpgsql
10
+ IMMUTABLE
11
+ AS $$
12
+ BEGIN
13
+ RETURN ((value % modulus) + modulus) % modulus;
14
+ END;
15
+ $$;
16
+
17
+ CREATE OR REPLACE FUNCTION hash_rollout_value(input TEXT)
18
+ RETURNS INTEGER
19
+ LANGUAGE plpgsql
20
+ IMMUTABLE
21
+ AS $$
22
+ DECLARE
23
+ hash_value NUMERIC := 0;
24
+ char_code INTEGER;
25
+ i INTEGER;
26
+ BEGIN
27
+ FOR i IN 1..length(input) LOOP
28
+ char_code := ascii(substring(input from i for 1));
29
+ hash_value := mod((hash_value * 31) + char_code, 4294967296);
30
+ END LOOP;
31
+
32
+ IF hash_value >= 2147483648 THEN
33
+ hash_value := hash_value - 4294967296;
34
+ END IF;
35
+
36
+ RETURN hash_value::INTEGER;
37
+ END;
38
+ $$;
39
+
40
+ CREATE OR REPLACE FUNCTION normalize_cohort_value(cohort TEXT)
41
+ RETURNS TEXT
42
+ LANGUAGE plpgsql
43
+ IMMUTABLE
44
+ AS $$
45
+ DECLARE
46
+ normalized TEXT;
47
+ cohort_value INTEGER;
48
+ BEGIN
49
+ IF cohort IS NULL THEN
50
+ RETURN NULL;
51
+ END IF;
52
+
53
+ normalized := lower(btrim(cohort));
54
+
55
+ IF normalized ~ '^[0-9]+$' THEN
56
+ cohort_value := normalized::INTEGER;
57
+ IF cohort_value BETWEEN 1 AND 1000 THEN
58
+ RETURN cohort_value::TEXT;
59
+ END IF;
60
+ END IF;
61
+
62
+ RETURN normalized;
63
+ END;
64
+ $$;
65
+
66
+ CREATE OR REPLACE FUNCTION gcd_int(a INTEGER, b INTEGER)
67
+ RETURNS INTEGER
68
+ LANGUAGE plpgsql
69
+ IMMUTABLE
70
+ AS $$
71
+ DECLARE
72
+ x INTEGER := abs(a);
73
+ y INTEGER := abs(b);
74
+ next_value INTEGER;
75
+ BEGIN
76
+ WHILE y <> 0 LOOP
77
+ next_value := x % y;
78
+ x := y;
79
+ y := next_value;
80
+ END LOOP;
81
+
82
+ RETURN x;
83
+ END;
84
+ $$;
85
+
86
+ CREATE OR REPLACE FUNCTION get_rollout_multiplier(bundle_id UUID)
87
+ RETURNS INTEGER
88
+ LANGUAGE plpgsql
89
+ IMMUTABLE
90
+ AS $$
91
+ DECLARE
92
+ candidate INTEGER := positive_mod(
93
+ hash_rollout_value(bundle_id::TEXT || ':multiplier'),
94
+ 997
95
+ );
96
+ BEGIN
97
+ IF candidate = 0 THEN
98
+ candidate := 1;
99
+ END IF;
100
+
101
+ WHILE gcd_int(candidate, 1000) <> 1 LOOP
102
+ candidate := positive_mod(candidate + 1, 1000);
103
+ IF candidate = 0 THEN
104
+ candidate := 1;
105
+ END IF;
106
+ END LOOP;
107
+
108
+ RETURN candidate;
109
+ END;
110
+ $$;
111
+
112
+ CREATE OR REPLACE FUNCTION get_rollout_offset(bundle_id UUID)
113
+ RETURNS INTEGER
114
+ LANGUAGE plpgsql
115
+ IMMUTABLE
116
+ AS $$
117
+ BEGIN
118
+ RETURN positive_mod(hash_rollout_value(bundle_id::TEXT || ':offset'), 1000);
119
+ END;
120
+ $$;
121
+
122
+ CREATE OR REPLACE FUNCTION get_modular_inverse(value INTEGER, modulus INTEGER)
123
+ RETURNS INTEGER
124
+ LANGUAGE plpgsql
125
+ IMMUTABLE
126
+ AS $$
127
+ DECLARE
128
+ candidate INTEGER;
129
+ BEGIN
130
+ FOR candidate IN 1..(modulus - 1) LOOP
131
+ IF positive_mod(value * candidate, modulus) = 1 THEN
132
+ RETURN candidate;
133
+ END IF;
134
+ END LOOP;
135
+
136
+ RAISE EXCEPTION 'No modular inverse for % mod %', value, modulus;
137
+ END;
138
+ $$;
139
+
140
+ CREATE OR REPLACE FUNCTION is_numeric_cohort(cohort TEXT)
141
+ RETURNS BOOLEAN
142
+ LANGUAGE plpgsql
143
+ IMMUTABLE
144
+ AS $$
145
+ DECLARE
146
+ normalized_cohort TEXT := normalize_cohort_value(cohort);
147
+ cohort_value INTEGER;
148
+ BEGIN
149
+ IF normalized_cohort IS NULL OR normalized_cohort !~ '^[0-9]+$' THEN
150
+ RETURN FALSE;
151
+ END IF;
152
+
153
+ cohort_value := normalized_cohort::INTEGER;
154
+ RETURN cohort_value BETWEEN 1 AND 1000;
155
+ END;
156
+ $$;
157
+
158
+ CREATE OR REPLACE FUNCTION get_numeric_cohort_rollout_position(
159
+ bundle_id UUID,
160
+ cohort TEXT
161
+ )
162
+ RETURNS INTEGER
163
+ LANGUAGE plpgsql
164
+ IMMUTABLE
165
+ AS $$
166
+ DECLARE
167
+ normalized_cohort TEXT := normalize_cohort_value(cohort);
168
+ cohort_value INTEGER;
169
+ multiplier INTEGER;
170
+ offset_value INTEGER;
171
+ inverse_multiplier INTEGER;
172
+ BEGIN
173
+ IF NOT is_numeric_cohort(normalized_cohort) THEN
174
+ RAISE EXCEPTION 'Invalid numeric cohort: %', cohort;
175
+ END IF;
176
+
177
+ cohort_value := normalized_cohort::INTEGER - 1;
178
+ multiplier := get_rollout_multiplier(bundle_id);
179
+ offset_value := get_rollout_offset(bundle_id);
180
+ inverse_multiplier := get_modular_inverse(multiplier, 1000);
181
+
182
+ RETURN positive_mod(
183
+ inverse_multiplier * (cohort_value - offset_value),
184
+ 1000
185
+ );
186
+ END;
187
+ $$;
188
+
189
+ CREATE OR REPLACE FUNCTION is_cohort_eligible(
190
+ bundle_id UUID,
191
+ cohort TEXT,
192
+ rollout_cohort_count INTEGER,
193
+ target_cohorts TEXT[]
194
+ )
195
+ RETURNS BOOLEAN
196
+ LANGUAGE plpgsql
197
+ IMMUTABLE
198
+ AS $$
199
+ DECLARE
200
+ normalized_cohort TEXT := normalize_cohort_value(cohort);
201
+ normalized_rollout_count INTEGER := COALESCE(rollout_cohort_count, 1000);
202
+ normalized_target_cohorts TEXT[];
203
+ BEGIN
204
+ IF target_cohorts IS NOT NULL THEN
205
+ normalized_target_cohorts := ARRAY(
206
+ SELECT normalize_cohort_value(value)
207
+ FROM unnest(target_cohorts) AS value
208
+ );
209
+ END IF;
210
+
211
+ IF normalized_target_cohorts IS NOT NULL
212
+ AND array_length(normalized_target_cohorts, 1) > 0
213
+ AND normalized_cohort IS NOT NULL
214
+ AND normalized_cohort = ANY(normalized_target_cohorts) THEN
215
+ RETURN TRUE;
216
+ END IF;
217
+
218
+ IF normalized_rollout_count <= 0 THEN
219
+ RETURN FALSE;
220
+ END IF;
221
+
222
+ IF normalized_cohort IS NULL THEN
223
+ RETURN normalized_rollout_count >= 1000;
224
+ END IF;
225
+
226
+ IF NOT is_numeric_cohort(normalized_cohort) THEN
227
+ RETURN FALSE;
228
+ END IF;
229
+
230
+ IF normalized_rollout_count >= 1000 THEN
231
+ RETURN TRUE;
232
+ END IF;
233
+
234
+ RETURN get_numeric_cohort_rollout_position(bundle_id, normalized_cohort)
235
+ < normalized_rollout_count;
236
+ END;
237
+ $$;
@@ -46,3 +46,14 @@ ALTER FUNCTION public.get_update_info_by_app_version(
46
46
  text
47
47
  )
48
48
  SET search_path = public, pg_catalog;
49
+
50
+ -- OTA update checks are unauthenticated by design (the device only carries the
51
+ -- anon key), so bundle metadata must be publicly readable. hot-updater's hosted
52
+ -- Supabase flow relies on these policies existing.
53
+ CREATE POLICY "allow public read bundles" ON public.bundles FOR SELECT USING (true);
54
+ CREATE POLICY "allow public read bundle_patches" ON public.bundle_patches FOR SELECT USING (true);
55
+
56
+ -- Allow anon/authenticated to resolve signed download URLs for the bundles bucket.
57
+ CREATE POLICY "public bundles read" ON storage.objects
58
+ FOR SELECT TO anon, authenticated
59
+ USING (bucket_id = 'bundles');
@@ -1,6 +1,6 @@
1
- name: flutter_patcher_cli
1
+ name: flutter_ota_kit_cli
2
2
  description: >-
3
- Command-line interface for flutter_patcher (a hot-updater compatible OTA code
3
+ Command-line interface for flutter_ota_kit (a hot-updater compatible OTA code
4
4
  push tool for Flutter). Deploy bundles, manage channels/rollbacks, run
5
5
  migrations, and inspect the backend — mirroring the `hot-updater` CLI UX.
6
6
  version: 0.1.0
@@ -17,21 +17,19 @@ dependencies:
17
17
  collection: ^1.18.0
18
18
  cryptography: ^2.7.0
19
19
  postgres: ^3.4.0
20
- flutter_patcher_core:
20
+ flutter_ota_kit_core:
21
21
  path: ../core
22
- flutter_patcher_plugin_core:
22
+ flutter_ota_kit_plugin_core:
23
23
  path: ../../plugins/plugin-core
24
- flutter_patcher_supabase:
24
+ flutter_ota_kit_supabase:
25
25
  path: ../../plugins/supabase
26
- flutter_patcher_postgres:
26
+ flutter_ota_kit_postgres:
27
27
  path: ../../plugins/postgres
28
- flutter_patcher_cloudflare:
28
+ flutter_ota_kit_cloudflare:
29
29
  path: ../../plugins/cloudflare
30
- flutter_patcher_aws:
30
+ flutter_ota_kit_aws:
31
31
  path: ../../plugins/aws
32
- flutter_patcher_standalone:
33
- path: ../../plugins/standalone
32
+ http: ^1.2.0
34
33
 
35
34
  dev_dependencies:
36
35
  test: ^1.25.0
37
- http: ^1.2.0
@@ -1,9 +1,9 @@
1
1
  import 'dart:io';
2
2
 
3
- import 'package:flutter_patcher_aws/flutter_patcher_aws.dart';
4
- import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
5
- import 'package:flutter_patcher_cloudflare/flutter_patcher_cloudflare.dart';
6
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
3
+ import 'package:flutter_ota_kit_aws/flutter_ota_kit_aws.dart';
4
+ import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
5
+ import 'package:flutter_ota_kit_cloudflare/flutter_ota_kit_cloudflare.dart';
6
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart';
7
7
  import 'package:test/test.dart';
8
8
 
9
9
  import 'mocks/mock_aws_s3_client.dart' as aws;
@@ -4,7 +4,7 @@ import 'dart:typed_data';
4
4
 
5
5
  import 'package:archive/archive.dart';
6
6
  import 'package:crypto/crypto.dart';
7
- import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
7
+ import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
8
8
  import 'package:test/test.dart';
9
9
 
10
10
  // Builds an in-memory APK containing libapp.so for every supported ABI, then
@@ -1,8 +1,8 @@
1
1
  import 'dart:io';
2
2
  import 'dart:typed_data';
3
3
 
4
- import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
5
- import 'package:flutter_patcher_supabase/src/supabase_client_adapter.dart';
4
+ import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
5
+ import 'package:flutter_ota_kit_supabase/src/supabase_client_adapter.dart';
6
6
  import 'package:path/path.dart' as p;
7
7
  import 'package:test/test.dart';
8
8
 
@@ -342,7 +342,7 @@ Backend _backend(MockSupabaseClient mock) =>
342
342
  resolveBackend(_config(), supabaseClientFactory: (url, key) => mock);
343
343
 
344
344
  void main() {
345
- group('flutter_patcher_cli', () {
345
+ group('flutter_ota_kit_cli', () {
346
346
  late MockSupabaseClient mock;
347
347
  late Backend backend;
348
348
 
@@ -1,9 +1,9 @@
1
1
  import 'dart:convert' show utf8;
2
2
 
3
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
3
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
4
4
  show StorageObject;
5
5
 
6
- import 'package:flutter_patcher_aws/flutter_patcher_aws.dart'
6
+ import 'package:flutter_ota_kit_aws/flutter_ota_kit_aws.dart'
7
7
  show AwsS3ClientLike, AwsS3StorageConfig;
8
8
 
9
9
  /// In-memory store backing [MockAwsS3Client].
@@ -1,13 +1,13 @@
1
1
  import 'dart:convert' show jsonDecode;
2
2
 
3
- import 'package:flutter_patcher_cloudflare/flutter_patcher_cloudflare.dart'
3
+ import 'package:flutter_ota_kit_cloudflare/flutter_ota_kit_cloudflare.dart'
4
4
  show
5
5
  CloudflareWorkerDatabaseConfig,
6
6
  D1ClientLike,
7
7
  D1DatabaseConfig,
8
8
  cloudflareWorkerDatabase,
9
9
  d1Database;
10
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
10
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
11
11
  show DatabasePlugin;
12
12
 
13
13
  /// In-memory store backing [MockD1Client], mirroring the D1 tables.
@@ -1,8 +1,8 @@
1
1
  import 'dart:typed_data';
2
2
 
3
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
3
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart';
4
4
 
5
- import 'package:flutter_patcher_postgres/flutter_patcher_postgres.dart';
5
+ import 'package:flutter_ota_kit_postgres/flutter_ota_kit_postgres.dart';
6
6
 
7
7
  /// In-memory store backing [MockPostgresClient]. Mirrors the `bundles` and
8
8
  /// `bundle_patches` tables (snake_case row maps).
@@ -90,7 +90,7 @@ class MockPostgresClient implements PostgresClientLike {
90
90
  if (sql.contains('SELECT * FROM bundles')) {
91
91
  return _selectBundles(sql, params);
92
92
  }
93
- if (sql.contains('flutter_patcher_storage')) {
93
+ if (sql.contains('flutter_ota_kit_storage')) {
94
94
  return _handleStorage(sql, params);
95
95
  }
96
96
  return [];
@@ -100,7 +100,7 @@ class MockPostgresClient implements PostgresClientLike {
100
100
  String sql,
101
101
  Map<String, dynamic> params,
102
102
  ) {
103
- if (sql.contains('INSERT INTO flutter_patcher_storage')) {
103
+ if (sql.contains('INSERT INTO flutter_ota_kit_storage')) {
104
104
  final key = params['@key'] as String;
105
105
  final data = params['@data'];
106
106
  store.storageObjects[key] = data is List<int>
@@ -110,16 +110,16 @@ class MockPostgresClient implements PostgresClientLike {
110
110
  : <int>[];
111
111
  return [];
112
112
  }
113
- if (sql.contains('SELECT 1 FROM flutter_patcher_storage')) {
113
+ if (sql.contains('SELECT 1 FROM flutter_ota_kit_storage')) {
114
114
  final key = params['@key'] as String;
115
115
  return store.storageObjects.containsKey(key) ? [{'1': 1}] : [];
116
116
  }
117
- if (sql.contains('DELETE FROM flutter_patcher_storage')) {
117
+ if (sql.contains('DELETE FROM flutter_ota_kit_storage')) {
118
118
  final key = params['@key'] as String;
119
119
  store.storageObjects.remove(key);
120
120
  return [];
121
121
  }
122
- if (sql.contains('SELECT data FROM flutter_patcher_storage')) {
122
+ if (sql.contains('SELECT data FROM flutter_ota_kit_storage')) {
123
123
  final key = params['@key'] as String;
124
124
  final data = store.storageObjects[key];
125
125
  if (data == null) return [];
@@ -1,4 +1,4 @@
1
- /// flutter_patcher core — Dart translation of hot-updater's packages/core.
1
+ /// flutter_ota_kit core — Dart translation of hot-updater's packages/core.
2
2
  ///
3
3
  /// Pure Dart, no Flutter dependency. Modules:
4
4
  /// - types: bundle/update data model (platform, status, strategy)
@@ -1,5 +1,5 @@
1
- name: flutter_patcher_core
2
- description: Core data model, rollout cohorts, and shared utilities for flutter_patcher — Dart translation of hot-updater's packages/core.
1
+ name: flutter_ota_kit_core
2
+ description: Core data model, rollout cohorts, and shared utilities for flutter_ota_kit — Dart translation of hot-updater's packages/core.
3
3
  version: 0.1.0
4
4
  publish_to: none
5
5
 
@@ -1,4 +1,4 @@
1
- import 'package:flutter_patcher_core/flutter_patcher_core.dart';
1
+ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
2
2
  import 'package:test/test.dart';
3
3
 
4
4
  /// Ground-truth values generated by executing hot-updater's actual
@@ -1,7 +1,7 @@
1
1
  import 'dart:convert';
2
2
  import 'dart:io';
3
3
 
4
- import 'package:flutter_patcher_core/flutter_patcher_core.dart';
4
+ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
5
5
  import 'package:test/test.dart';
6
6
 
7
7
  /// Ground truth generated by executing the REAL verkit package
@@ -1,4 +1,4 @@
1
- import 'package:flutter_patcher_core/flutter_patcher_core.dart';
1
+ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
2
2
  import 'package:test/test.dart';
3
3
 
4
4
  Bundle _sample() => Bundle(
@@ -7,7 +7,7 @@ Bundle _sample() => Bundle(
7
7
  shouldForceUpdate: false,
8
8
  enabled: true,
9
9
  fileHash: 'a' * 64,
10
- storageUri: 'supabase-storage://flutter-patcher-storage/bundles/x/bundle.zip',
10
+ storageUri: 'supabase-storage://flutter-ota-storage/bundles/x/bundle.zip',
11
11
  gitCommitHash: 'deadbeef',
12
12
  message: 'test bundle',
13
13
  channel: 'production',
@@ -1,4 +1,4 @@
1
- import 'package:flutter_patcher_core/flutter_patcher_core.dart';
1
+ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
2
2
  import 'package:test/test.dart';
3
3
 
4
4
  void main() {
@@ -1,4 +1,4 @@
1
- import 'package:flutter_patcher_core/flutter_patcher_core.dart';
1
+ import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
2
2
  void main() {
3
3
  // ignore: avoid_print
4
4
  print('coerce=${semverCoerce('app version 2.3.6!')}');
@@ -8,5 +8,3 @@ export 'src/aws_cloudfront_signer.dart'
8
8
  show cloudfrontSignedUrl, verifyCloudfrontSignedUrl;
9
9
  export 'src/with_cloudfront_signed_url.dart'
10
10
  show WithCloudFrontSignedUrlOptions, withCloudFrontSignedUrl;
11
- export 'src/aws_lambda_edge_storage.dart'
12
- show AwsLambdaEdgeStorageConfig, awsLambdaEdgeStorage, s3LambdaEdgeStorage;
@@ -93,7 +93,7 @@ class AwsCloudFrontClient implements AwsCloudFrontClientLike {
93
93
  // continue (mirrors hot-updater's s3Database behaviour).
94
94
  // ignore: avoid_print
95
95
  print(
96
- '[flutter_patcher/aws] CloudFront invalidation failed for '
96
+ '[flutter_ota_kit/aws] CloudFront invalidation failed for '
97
97
  '$distributionId; continuing without cache invalidation: $error',
98
98
  );
99
99
  }
@@ -1,6 +1,6 @@
1
1
  import 'dart:convert' show jsonDecode, jsonEncode, utf8;
2
2
 
3
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
3
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
4
4
  show BlobOperations, createBlobDatabasePlugin;
5
5
 
6
6
  import 'aws_cloudfront_client.dart'
@@ -3,7 +3,7 @@ import 'dart:io' show File;
3
3
  import 'dart:typed_data' show Uint8List;
4
4
 
5
5
  import 'package:crypto/crypto.dart' show Hmac, sha256;
6
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
6
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
7
7
  show StorageObject;
8
8
  import 'package:http/http.dart' show Client, Request, Response;
9
9
 
@@ -1,4 +1,4 @@
1
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
1
+ 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;