@_nazmiforreal/flutter-ota 0.1.12 → 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.
- package/README.md +1 -1
- package/bin/flutter-ota-linux-x64 +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
- package/dart-src/packages/cli-tools/README.md +1 -1
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
- package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +290 -181
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
- package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
- package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
- package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
- package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
- package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
- package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
- package/dart-src/packages/cli-tools/pubspec.lock +69 -116
- package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
- package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
- package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
- package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
- package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
- package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
- package/dart-src/packages/core/README.md +2 -2
- package/dart-src/packages/core/example/lib/main.dart +69 -0
- package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
- package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
- package/dart-src/packages/core/lib/src/bundle.dart +102 -94
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
- package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
- package/dart-src/packages/core/lib/src/metadata.dart +9 -7
- package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
- package/dart-src/packages/core/lib/src/platform.dart +4 -3
- package/dart-src/packages/core/lib/src/rollout.dart +14 -11
- package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
- package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
- package/dart-src/packages/core/lib/src/status.dart +4 -3
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
- package/dart-src/packages/core/lib/src/uuid.dart +1 -2
- package/dart-src/packages/core/pubspec.yaml +6 -7
- package/dart-src/packages/core/test/rollout_test.dart +15 -8
- package/dart-src/packages/core/test/semver_test.dart +5 -2
- package/dart-src/packages/core/test/types_test.dart +26 -24
- package/dart-src/packages/core/test/uuid_test.dart +3 -1
- package/dart-src/packages/core/tool/dbg.dart +1 -0
- package/dart-src/plugins/aws/CHANGELOG.md +6 -0
- package/dart-src/plugins/aws/example/lib/main.dart +60 -0
- package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
- package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
- package/dart-src/plugins/aws/pubspec.yaml +15 -10
- package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
- package/dart-src/plugins/cloudflare/CHANGELOG.md +7 -0
- package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
- package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
- package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
- package/dart-src/plugins/plugin-core/CHANGELOG.md +16 -0
- package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
- package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
- package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
- package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
- package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
- package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
- package/dart-src/plugins/pocketbase/LICENSE +21 -0
- package/dart-src/plugins/pocketbase/README.md +54 -0
- package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
- package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
- package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +6 -0
- package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
- package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
- package/dart-src/plugins/postgres/pubspec.yaml +14 -9
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
- package/dart-src/plugins/supabase/CHANGELOG.md +17 -0
- package/dart-src/plugins/supabase/README.md +2 -2
- package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
- package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
- package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
- package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
- package/dart-src/plugins/supabase/pubspec.yaml +14 -9
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
- package/package.json +3 -3
- package/scripts/vendor.sh +1 -1
|
@@ -5,11 +5,17 @@ import 'package:test/test.dart';
|
|
|
5
5
|
|
|
6
6
|
void main() {
|
|
7
7
|
group('paginateBundles', () {
|
|
8
|
-
List<Bundle> makeBundles(int count) =>
|
|
9
|
-
|
|
8
|
+
List<Bundle> makeBundles(int count) => List.generate(
|
|
9
|
+
count,
|
|
10
|
+
(i) => _testBundle(id: 'b-${i.toString().padLeft(3, '0')}'),
|
|
11
|
+
);
|
|
10
12
|
|
|
11
13
|
test('offset-based first page', () {
|
|
12
|
-
final result = paginateBundles(
|
|
14
|
+
final result = paginateBundles(
|
|
15
|
+
bundles: makeBundles(25),
|
|
16
|
+
limit: 10,
|
|
17
|
+
offset: 0,
|
|
18
|
+
);
|
|
13
19
|
expect(result.data.length, 10);
|
|
14
20
|
expect(result.pagination.total, 25);
|
|
15
21
|
expect(result.pagination.hasNextPage, isTrue);
|
|
@@ -20,7 +26,11 @@ void main() {
|
|
|
20
26
|
});
|
|
21
27
|
|
|
22
28
|
test('offset-based last page', () {
|
|
23
|
-
final result = paginateBundles(
|
|
29
|
+
final result = paginateBundles(
|
|
30
|
+
bundles: makeBundles(25),
|
|
31
|
+
limit: 10,
|
|
32
|
+
offset: 20,
|
|
33
|
+
);
|
|
24
34
|
expect(result.data.length, 5);
|
|
25
35
|
expect(result.pagination.hasNextPage, isFalse);
|
|
26
36
|
expect(result.pagination.hasPreviousPage, isTrue);
|
|
@@ -66,18 +76,20 @@ void main() {
|
|
|
66
76
|
|
|
67
77
|
group('filterCompatibleAppVersions', () {
|
|
68
78
|
test('filters and sorts descending', () {
|
|
69
|
-
final result = filterCompatibleAppVersions(
|
|
70
|
-
|
|
71
|
-
'
|
|
72
|
-
|
|
79
|
+
final result = filterCompatibleAppVersions([
|
|
80
|
+
'^1.0.0',
|
|
81
|
+
'^2.0.0',
|
|
82
|
+
'^0.5.0',
|
|
83
|
+
], '1.3.0');
|
|
73
84
|
expect(result, ['^1.0.0']);
|
|
74
85
|
});
|
|
75
86
|
|
|
76
87
|
test('multiple compatible versions sorted desc', () {
|
|
77
|
-
final result = filterCompatibleAppVersions(
|
|
78
|
-
|
|
79
|
-
'1.
|
|
80
|
-
|
|
88
|
+
final result = filterCompatibleAppVersions([
|
|
89
|
+
'>=1.0.0',
|
|
90
|
+
'>=1.2.0',
|
|
91
|
+
'>=2.0.0',
|
|
92
|
+
], '1.5.0');
|
|
81
93
|
expect(result, ['>=1.2.0', '>=1.0.0']);
|
|
82
94
|
});
|
|
83
95
|
});
|
|
@@ -143,19 +155,31 @@ void main() {
|
|
|
143
155
|
|
|
144
156
|
group('compressionFormat', () {
|
|
145
157
|
test('detects zip', () {
|
|
146
|
-
expect(
|
|
158
|
+
expect(
|
|
159
|
+
detectCompressionFormat('bundle.zip').format,
|
|
160
|
+
CompressionFormat.zip,
|
|
161
|
+
);
|
|
147
162
|
});
|
|
148
163
|
|
|
149
164
|
test('detects tar.br', () {
|
|
150
|
-
expect(
|
|
165
|
+
expect(
|
|
166
|
+
detectCompressionFormat('bundle.tar.br').format,
|
|
167
|
+
CompressionFormat.tarBr,
|
|
168
|
+
);
|
|
151
169
|
});
|
|
152
170
|
|
|
153
171
|
test('detects tar.gz', () {
|
|
154
|
-
expect(
|
|
172
|
+
expect(
|
|
173
|
+
detectCompressionFormat('bundle.tar.gz').format,
|
|
174
|
+
CompressionFormat.tarGz,
|
|
175
|
+
);
|
|
155
176
|
});
|
|
156
177
|
|
|
157
178
|
test('defaults to zip', () {
|
|
158
|
-
expect(
|
|
179
|
+
expect(
|
|
180
|
+
detectCompressionFormat('bundle.xyz').format,
|
|
181
|
+
CompressionFormat.zip,
|
|
182
|
+
);
|
|
159
183
|
});
|
|
160
184
|
|
|
161
185
|
test('getContentType returns mime type for zip', () {
|
|
@@ -217,8 +241,8 @@ Bundle _testBundle({String id = 'test-bundle-id'}) {
|
|
|
217
241
|
|
|
218
242
|
class _DummyStoragePlugin extends StoragePlugin {
|
|
219
243
|
_DummyStoragePlugin({required bool node, required bool runtime})
|
|
220
|
-
|
|
221
|
-
|
|
244
|
+
: _node = node,
|
|
245
|
+
_runtime = runtime;
|
|
222
246
|
|
|
223
247
|
final bool _node;
|
|
224
248
|
final bool _runtime;
|
|
@@ -231,9 +255,9 @@ class _DummyStoragePlugin extends StoragePlugin {
|
|
|
231
255
|
|
|
232
256
|
@override
|
|
233
257
|
StoragePluginProfiles get profiles => StoragePluginProfiles(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
258
|
+
node: _node ? _DummyNodeStorageProfile() : null,
|
|
259
|
+
runtime: _runtime ? _DummyRuntimeStorageProfile() : null,
|
|
260
|
+
);
|
|
237
261
|
}
|
|
238
262
|
|
|
239
263
|
class _DummyNodeStorageProfile extends NodeStorageProfile {
|
|
@@ -44,18 +44,42 @@ void main() {
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
test('eq match', () {
|
|
47
|
-
expect(
|
|
48
|
-
|
|
47
|
+
expect(
|
|
48
|
+
bundleIdMatchesFilter('abc', const DatabaseBundleIdFilter(eq: 'abc')),
|
|
49
|
+
isTrue,
|
|
50
|
+
);
|
|
51
|
+
expect(
|
|
52
|
+
bundleIdMatchesFilter('abc', const DatabaseBundleIdFilter(eq: 'def')),
|
|
53
|
+
isFalse,
|
|
54
|
+
);
|
|
49
55
|
});
|
|
50
56
|
|
|
51
57
|
test('gt match', () {
|
|
52
|
-
expect(
|
|
53
|
-
|
|
58
|
+
expect(
|
|
59
|
+
bundleIdMatchesFilter('b', const DatabaseBundleIdFilter(gt: 'a')),
|
|
60
|
+
isTrue,
|
|
61
|
+
);
|
|
62
|
+
expect(
|
|
63
|
+
bundleIdMatchesFilter('a', const DatabaseBundleIdFilter(gt: 'b')),
|
|
64
|
+
isFalse,
|
|
65
|
+
);
|
|
54
66
|
});
|
|
55
67
|
|
|
56
68
|
test('in match', () {
|
|
57
|
-
expect(
|
|
58
|
-
|
|
69
|
+
expect(
|
|
70
|
+
bundleIdMatchesFilter(
|
|
71
|
+
'a',
|
|
72
|
+
const DatabaseBundleIdFilter(ins: ['a', 'b']),
|
|
73
|
+
),
|
|
74
|
+
isTrue,
|
|
75
|
+
);
|
|
76
|
+
expect(
|
|
77
|
+
bundleIdMatchesFilter(
|
|
78
|
+
'c',
|
|
79
|
+
const DatabaseBundleIdFilter(ins: ['a', 'b']),
|
|
80
|
+
),
|
|
81
|
+
isFalse,
|
|
82
|
+
);
|
|
59
83
|
});
|
|
60
84
|
|
|
61
85
|
test('combined filters', () {
|
|
@@ -73,20 +97,56 @@ void main() {
|
|
|
73
97
|
|
|
74
98
|
test('filters by channel', () {
|
|
75
99
|
final bundle = _testBundle(channel: 'prod');
|
|
76
|
-
expect(
|
|
77
|
-
|
|
100
|
+
expect(
|
|
101
|
+
bundleMatchesQueryWhere(
|
|
102
|
+
bundle,
|
|
103
|
+
const DatabaseBundleQueryWhere(channel: 'prod'),
|
|
104
|
+
),
|
|
105
|
+
isTrue,
|
|
106
|
+
);
|
|
107
|
+
expect(
|
|
108
|
+
bundleMatchesQueryWhere(
|
|
109
|
+
bundle,
|
|
110
|
+
const DatabaseBundleQueryWhere(channel: 'dev'),
|
|
111
|
+
),
|
|
112
|
+
isFalse,
|
|
113
|
+
);
|
|
78
114
|
});
|
|
79
115
|
|
|
80
116
|
test('filters by enabled', () {
|
|
81
117
|
final bundle = _testBundle(enabled: true);
|
|
82
|
-
expect(
|
|
83
|
-
|
|
118
|
+
expect(
|
|
119
|
+
bundleMatchesQueryWhere(
|
|
120
|
+
bundle,
|
|
121
|
+
const DatabaseBundleQueryWhere(enabled: true),
|
|
122
|
+
),
|
|
123
|
+
isTrue,
|
|
124
|
+
);
|
|
125
|
+
expect(
|
|
126
|
+
bundleMatchesQueryWhere(
|
|
127
|
+
bundle,
|
|
128
|
+
const DatabaseBundleQueryWhere(enabled: false),
|
|
129
|
+
),
|
|
130
|
+
isFalse,
|
|
131
|
+
);
|
|
84
132
|
});
|
|
85
133
|
|
|
86
134
|
test('filters by targetAppVersion null', () {
|
|
87
135
|
final bundle = _testBundle(targetAppVersion: null);
|
|
88
|
-
expect(
|
|
89
|
-
|
|
136
|
+
expect(
|
|
137
|
+
bundleMatchesQueryWhere(
|
|
138
|
+
bundle,
|
|
139
|
+
const DatabaseBundleQueryWhere(targetAppVersionNotNull: true),
|
|
140
|
+
),
|
|
141
|
+
isFalse,
|
|
142
|
+
);
|
|
143
|
+
expect(
|
|
144
|
+
bundleMatchesQueryWhere(
|
|
145
|
+
bundle,
|
|
146
|
+
const DatabaseBundleQueryWhere(targetAppVersionNotNull: false),
|
|
147
|
+
),
|
|
148
|
+
isTrue,
|
|
149
|
+
);
|
|
90
150
|
});
|
|
91
151
|
});
|
|
92
152
|
|
|
@@ -107,7 +167,10 @@ void main() {
|
|
|
107
167
|
_testBundle(id: 'a'),
|
|
108
168
|
_testBundle(id: 'b'),
|
|
109
169
|
];
|
|
110
|
-
final sorted = sortBundles(
|
|
170
|
+
final sorted = sortBundles(
|
|
171
|
+
bundles,
|
|
172
|
+
const DatabaseBundleQueryOrder(direction: 'asc'),
|
|
173
|
+
);
|
|
111
174
|
expect(sorted.map((b) => b.id).toList(), ['a', 'b', 'c']);
|
|
112
175
|
});
|
|
113
176
|
});
|
|
@@ -23,9 +23,14 @@ void main() {
|
|
|
23
23
|
test('produces valid UUID v7 format', () {
|
|
24
24
|
final uuid = createUUIDv7();
|
|
25
25
|
// 8-4-4-4-12 format
|
|
26
|
-
expect(
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
expect(
|
|
27
|
+
uuid,
|
|
28
|
+
matches(
|
|
29
|
+
RegExp(
|
|
30
|
+
r'^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
);
|
|
29
34
|
});
|
|
30
35
|
|
|
31
36
|
test('generates unique UUIDs', () {
|
|
@@ -60,18 +65,24 @@ void main() {
|
|
|
60
65
|
test('produces different random bits', () {
|
|
61
66
|
final original = createUUIDv7();
|
|
62
67
|
// Generate a few — at least one should differ in random part
|
|
63
|
-
final differentFound = List.generate(
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
final differentFound = List.generate(
|
|
69
|
+
10,
|
|
70
|
+
(_) => createUUIDv7WithSameTimestamp(original),
|
|
71
|
+
).any((u) => u != original);
|
|
66
72
|
expect(differentFound, isTrue);
|
|
67
73
|
});
|
|
68
74
|
|
|
69
75
|
test('derived UUID still has version 7 and correct variant', () {
|
|
70
76
|
final original = createUUIDv7();
|
|
71
77
|
final derived = createUUIDv7WithSameTimestamp(original);
|
|
72
|
-
expect(
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
expect(
|
|
79
|
+
derived,
|
|
80
|
+
matches(
|
|
81
|
+
RegExp(
|
|
82
|
+
r'^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
);
|
|
75
86
|
});
|
|
76
87
|
});
|
|
77
88
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial release of `flutter_ota_kit_pocketbase` — PocketBase
|
|
6
|
+
database + storage plugins for the flutter_ota_kit OTA toolkit.
|
|
7
|
+
See the [PocketBase backend guide](https://github.com/HYPER12755/flutter-ota-kit/blob/main/doc/backends.md#pocketbase)
|
|
8
|
+
for usage.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xuelinger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# flutter_ota_kit_pocketbase
|
|
2
|
+
|
|
3
|
+
PocketBase backend plugin for
|
|
4
|
+
[`flutter_ota_kit`](https://github.com/HYPER12755/flutter-ota-kit) — a
|
|
5
|
+
self-hostable code-push (OTA update) toolkit for Flutter Android.
|
|
6
|
+
|
|
7
|
+
PocketBase is a single-binary, Go-based backend that gives you SQLite,
|
|
8
|
+
auth, file storage, realtime, and an admin UI in one ~15 MB executable.
|
|
9
|
+
This plugin makes PocketBase a drop-in `DatabasePlugin` + `StoragePlugin`
|
|
10
|
+
for `flutter_ota_kit`.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
dependencies:
|
|
16
|
+
flutter_ota_kit: ^0.1.10
|
|
17
|
+
flutter_ota_kit_pocketbase: ^0.1.0
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```dart
|
|
23
|
+
import 'package:flutter_ota_kit/flutter_ota_kit.dart';
|
|
24
|
+
import 'package:flutter_ota_kit_pocketbase/flutter_ota_kit_pocketbase.dart';
|
|
25
|
+
|
|
26
|
+
await FlutterPatcher.init(
|
|
27
|
+
pocketbaseConfig: PocketBaseUpdateConfig(
|
|
28
|
+
url: 'http://localhost:8090',
|
|
29
|
+
adminEmail: 'admin@example.com',
|
|
30
|
+
adminPassword: 'super-secret',
|
|
31
|
+
),
|
|
32
|
+
// ... other FlutterPatcher options
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Start a local PocketBase with the schema pre-installed
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
flutter-ota serve -b pocketbase
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This downloads PocketBase (if missing), creates the `bundles` and
|
|
43
|
+
`channels` collections, installs validation hooks, and starts the
|
|
44
|
+
admin UI at `http://localhost:8090/_/`.
|
|
45
|
+
|
|
46
|
+
## Documentation
|
|
47
|
+
|
|
48
|
+
- [Backend guide → PocketBase](https://github.com/HYPER12755/flutter-ota-kit/blob/main/doc/backends.md#pocketbase)
|
|
49
|
+
- [API reference](https://github.com/HYPER12755/flutter-ota-kit/blob/main/doc/api-reference.md)
|
|
50
|
+
- [Main README](https://github.com/HYPER12755/flutter-ota-kit)
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// flutter_ota_kit_pocketbase example
|
|
2
|
+
//
|
|
3
|
+
// `flutter_ota_kit_pocketbase` is a PocketBase backend for
|
|
4
|
+
// `flutter_ota_kit`. PocketBase is a single-binary, self-hostable
|
|
5
|
+
// backend (similar to Supabase) that gives you SQLite, auth, file
|
|
6
|
+
// storage, realtime, and an admin UI in one ~15MB Go binary. This
|
|
7
|
+
// plugin makes it a drop-in `DatabasePlugin` + `StoragePlugin`.
|
|
8
|
+
//
|
|
9
|
+
// Pair this with `flutter_ota_kit serve` (cli-tools) to start a local
|
|
10
|
+
// PocketBase instance with the flutter_ota_kit schema pre-installed,
|
|
11
|
+
// plus an admin UI at `http://localhost:8090/_/`.
|
|
12
|
+
//
|
|
13
|
+
// Run: `dart run example/main.dart`
|
|
14
|
+
|
|
15
|
+
import 'package:flutter_ota_kit_pocketbase/flutter_ota_kit_pocketbase.dart';
|
|
16
|
+
|
|
17
|
+
void main() {
|
|
18
|
+
// ── 1. Database config (the `bundles` collection). ──────────────
|
|
19
|
+
// `url` is the base URL of your PocketBase instance. `adminEmail` /
|
|
20
|
+
// `adminPassword` are the superuser credentials — used only for
|
|
21
|
+
// server-side reads/writes against the `bundles` and `channels`
|
|
22
|
+
// collections.
|
|
23
|
+
const dbConfig = PocketBaseConfig(
|
|
24
|
+
url: 'http://localhost:8090',
|
|
25
|
+
adminEmail: 'admin@example.com',
|
|
26
|
+
adminPassword: 'super-secret',
|
|
27
|
+
);
|
|
28
|
+
print('PocketBase URL: ${dbConfig.url}');
|
|
29
|
+
print('Bundles collection: ${dbConfig.bundlesCollection}');
|
|
30
|
+
|
|
31
|
+
// ── 2. Storage config (the `bundles` file bucket). ───────────────
|
|
32
|
+
// The `bundlesCollection` is the collection whose records carry the
|
|
33
|
+
// patch file attachments. The `bundlesBucket` is the file-storage
|
|
34
|
+
// bucket name within PocketBase.
|
|
35
|
+
const storageConfig = PocketBaseStorageConfig(
|
|
36
|
+
url: 'http://localhost:8090',
|
|
37
|
+
adminEmail: 'admin@example.com',
|
|
38
|
+
adminPassword: 'super-secret',
|
|
39
|
+
bundlesCollection: 'bundles',
|
|
40
|
+
bundlesBucket: 'bundles',
|
|
41
|
+
);
|
|
42
|
+
print('Bucket: ${storageConfig.bundlesBucket}');
|
|
43
|
+
|
|
44
|
+
// ── 3. Storage URI helpers. ──────────────────────────────────────
|
|
45
|
+
// Build a `pb://<bucket>/<recordId>/<file>` URI for a given
|
|
46
|
+
// record+filename. The device SDK understands this format and
|
|
47
|
+
// downloads via the PocketBase `/api/files/{collection}/{id}/{file}`
|
|
48
|
+
// endpoint (with a short-lived token).
|
|
49
|
+
final uri = buildPocketBaseStorageUri(
|
|
50
|
+
'01a059a6-7d3c-4f3e-8a2c-9c2e3a4b5d6e',
|
|
51
|
+
'patch.zip',
|
|
52
|
+
);
|
|
53
|
+
print('Storage URI: $uri');
|
|
54
|
+
|
|
55
|
+
// Round-trip it:
|
|
56
|
+
final parsed = parsePocketBaseStorageUri(uri);
|
|
57
|
+
if (parsed != null) {
|
|
58
|
+
print('Parsed bucket: ${parsed.bucket}');
|
|
59
|
+
print('Parsed recordId: ${parsed.recordId}');
|
|
60
|
+
print('Parsed filename: ${parsed.filename}');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/// flutter_ota_kit_pocketbase — PocketBase backend for flutter_ota_kit.
|
|
2
|
+
///
|
|
3
|
+
/// PocketBase is a single-binary, self-hostable backend (similar to Supabase)
|
|
4
|
+
/// that gives you SQLite, auth, file storage, realtime, and an admin UI in
|
|
5
|
+
/// one ~15MB Go binary. This plugin makes it a drop-in `DatabasePlugin` +
|
|
6
|
+
/// `StoragePlugin` for flutter_ota_kit.
|
|
7
|
+
///
|
|
8
|
+
/// Pair this with `flutter_ota_kit serve` (cli-tools) to start a local
|
|
9
|
+
/// PocketBase instance with the flutter_ota_kit schema pre-installed, plus
|
|
10
|
+
/// an admin UI at `http://localhost:8090/_/`.
|
|
11
|
+
library;
|
|
12
|
+
|
|
13
|
+
export 'src/pocketbase_config.dart';
|
|
14
|
+
export 'src/pocketbase_client.dart'
|
|
15
|
+
show
|
|
16
|
+
PocketBaseClient,
|
|
17
|
+
PocketBaseClientFactory,
|
|
18
|
+
PocketBaseException,
|
|
19
|
+
PocketBaseList;
|
|
20
|
+
export 'src/pocketbase_database.dart'
|
|
21
|
+
show pocketbaseDatabase, PocketBaseDatabaseConfig;
|
|
22
|
+
export 'src/pocketbase_storage.dart'
|
|
23
|
+
show
|
|
24
|
+
pocketbaseStorage,
|
|
25
|
+
PocketBaseStorageConfig,
|
|
26
|
+
parsePocketBaseStorageUri,
|
|
27
|
+
buildPocketBaseStorageUri;
|
|
28
|
+
export 'src/pocketbase_bundle_mapper.dart';
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/// Maps PocketBase bundle records to [Bundle] and back.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
|
|
5
|
+
import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart'
|
|
6
|
+
show normalizeMetadata;
|
|
7
|
+
|
|
8
|
+
/// Row shape of a `bundles` record as stored in PocketBase.
|
|
9
|
+
class PocketBaseBundleRow {
|
|
10
|
+
PocketBaseBundleRow({
|
|
11
|
+
required this.id,
|
|
12
|
+
required this.channel,
|
|
13
|
+
required this.enabled,
|
|
14
|
+
required this.platform,
|
|
15
|
+
required this.shouldForceUpdate,
|
|
16
|
+
required this.fileHash,
|
|
17
|
+
required this.storageUri,
|
|
18
|
+
required this.rolloutCohortCount,
|
|
19
|
+
this.gitCommitHash,
|
|
20
|
+
this.message,
|
|
21
|
+
this.fingerprintHash,
|
|
22
|
+
this.targetAppVersion,
|
|
23
|
+
this.manifestStorageUri,
|
|
24
|
+
this.manifestFileHash,
|
|
25
|
+
this.assetBaseStorageUri,
|
|
26
|
+
this.targetCohorts,
|
|
27
|
+
this.metadata,
|
|
28
|
+
this.created,
|
|
29
|
+
this.updated,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
factory PocketBaseBundleRow.fromJson(Map<String, dynamic> j) =>
|
|
33
|
+
PocketBaseBundleRow(
|
|
34
|
+
id: j['id'] as String? ?? '',
|
|
35
|
+
channel: j['channel'] as String? ?? 'production',
|
|
36
|
+
enabled: j['enabled'] as bool? ?? true,
|
|
37
|
+
platform: j['platform'] as String? ?? 'android',
|
|
38
|
+
shouldForceUpdate: j['should_force_update'] as bool? ?? false,
|
|
39
|
+
fileHash: j['file_hash'] as String? ?? '',
|
|
40
|
+
storageUri: j['storage_uri'] as String? ?? '',
|
|
41
|
+
rolloutCohortCount:
|
|
42
|
+
(j['rollout_cohort_count'] as num?)?.toInt() ?? 1000,
|
|
43
|
+
gitCommitHash: j['git_commit_hash'] as String?,
|
|
44
|
+
message: j['message'] as String?,
|
|
45
|
+
fingerprintHash: j['fingerprint_hash'] as String?,
|
|
46
|
+
targetAppVersion: j['target_app_version'] as String?,
|
|
47
|
+
manifestStorageUri: j['manifest_storage_uri'] as String?,
|
|
48
|
+
manifestFileHash: j['manifest_file_hash'] as String?,
|
|
49
|
+
assetBaseStorageUri: j['asset_base_storage_uri'] as String?,
|
|
50
|
+
targetCohorts: (j['target_cohorts'] as List?)?.cast<String>(),
|
|
51
|
+
metadata: (j['metadata'] as Map?)?.cast<String, dynamic>(),
|
|
52
|
+
created: j['created'] as String?,
|
|
53
|
+
updated: j['updated'] as String?,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
final String id;
|
|
57
|
+
final String channel;
|
|
58
|
+
final bool enabled;
|
|
59
|
+
final String platform;
|
|
60
|
+
final bool shouldForceUpdate;
|
|
61
|
+
final String fileHash;
|
|
62
|
+
final String storageUri;
|
|
63
|
+
final int rolloutCohortCount;
|
|
64
|
+
final String? gitCommitHash;
|
|
65
|
+
final String? message;
|
|
66
|
+
final String? fingerprintHash;
|
|
67
|
+
final String? targetAppVersion;
|
|
68
|
+
final String? manifestStorageUri;
|
|
69
|
+
final String? manifestFileHash;
|
|
70
|
+
final String? assetBaseStorageUri;
|
|
71
|
+
final List<String>? targetCohorts;
|
|
72
|
+
final Map<String, dynamic>? metadata;
|
|
73
|
+
final String? created;
|
|
74
|
+
final String? updated;
|
|
75
|
+
|
|
76
|
+
Map<String, dynamic> toCreateJson() => {
|
|
77
|
+
'id': id,
|
|
78
|
+
'channel': channel,
|
|
79
|
+
'enabled': enabled,
|
|
80
|
+
'platform': platform,
|
|
81
|
+
'should_force_update': shouldForceUpdate,
|
|
82
|
+
'file_hash': fileHash,
|
|
83
|
+
'storage_uri': storageUri,
|
|
84
|
+
'rollout_cohort_count': rolloutCohortCount,
|
|
85
|
+
if (gitCommitHash != null) 'git_commit_hash': gitCommitHash,
|
|
86
|
+
if (message != null) 'message': message,
|
|
87
|
+
if (fingerprintHash != null) 'fingerprint_hash': fingerprintHash,
|
|
88
|
+
if (targetAppVersion != null) 'target_app_version': targetAppVersion,
|
|
89
|
+
if (manifestStorageUri != null) 'manifest_storage_uri': manifestStorageUri,
|
|
90
|
+
if (manifestFileHash != null) 'manifest_file_hash': manifestFileHash,
|
|
91
|
+
if (assetBaseStorageUri != null)
|
|
92
|
+
'asset_base_storage_uri': assetBaseStorageUri,
|
|
93
|
+
if (targetCohorts != null) 'target_cohorts': targetCohorts,
|
|
94
|
+
if (metadata != null) 'metadata': metadata,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Convert a [PocketBaseBundleRow] into a [Bundle].
|
|
99
|
+
Bundle mapRowToBundle(PocketBaseBundleRow row) {
|
|
100
|
+
final rawMetadata = normalizeMetadata(row.metadata);
|
|
101
|
+
return Bundle(
|
|
102
|
+
id: row.id,
|
|
103
|
+
channel: row.channel,
|
|
104
|
+
enabled: row.enabled,
|
|
105
|
+
shouldForceUpdate: row.shouldForceUpdate,
|
|
106
|
+
fileHash: row.fileHash,
|
|
107
|
+
gitCommitHash: row.gitCommitHash,
|
|
108
|
+
message: row.message,
|
|
109
|
+
platform: Platform.fromValue(row.platform),
|
|
110
|
+
targetAppVersion: row.targetAppVersion,
|
|
111
|
+
fingerprintHash: row.fingerprintHash,
|
|
112
|
+
storageUri: row.storageUri,
|
|
113
|
+
metadata: rawMetadata != null ? BundleMetadata.fromJson(rawMetadata) : null,
|
|
114
|
+
manifestStorageUri: row.manifestStorageUri,
|
|
115
|
+
manifestFileHash: row.manifestFileHash,
|
|
116
|
+
assetBaseStorageUri: row.assetBaseStorageUri,
|
|
117
|
+
patches: const [],
|
|
118
|
+
rolloutCohortCount: row.rolloutCohortCount,
|
|
119
|
+
targetCohorts: row.targetCohorts,
|
|
120
|
+
);
|
|
121
|
+
}
|