@_nazmiforreal/flutter-ota 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +299 -190
- 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 +2 -2
- package/scripts/vendor.sh +1 -1
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/// Installs the flutter_ota_kit PocketBase schema (collections, indexes,
|
|
2
|
+
/// default admin) on a running PB instance.
|
|
3
|
+
///
|
|
4
|
+
/// PocketBase is initialized with:
|
|
5
|
+
/// - `bundles` collection (the OTA bundle metadata)
|
|
6
|
+
/// - `channels` collection (release channels with a current_bundle pointer)
|
|
7
|
+
/// - `audit_log` collection (immutable event log)
|
|
8
|
+
/// - `admins` is left to PB's built-in bootstrapping
|
|
9
|
+
/// (set PB_ADMIN_EMAIL + PB_ADMIN_PASSWORD env vars on first start)
|
|
10
|
+
///
|
|
11
|
+
/// Idempotent: re-running the installer is a no-op when collections exist.
|
|
12
|
+
library;
|
|
13
|
+
|
|
14
|
+
import 'dart:convert';
|
|
15
|
+
|
|
16
|
+
import 'package:flutter_ota_kit_pocketbase/flutter_ota_kit_pocketbase.dart';
|
|
17
|
+
import 'package:http/http.dart' as http;
|
|
18
|
+
|
|
19
|
+
const _kBundlesCollection = r'''
|
|
20
|
+
{
|
|
21
|
+
"name": "bundles",
|
|
22
|
+
"type": "base",
|
|
23
|
+
"listRule": null,
|
|
24
|
+
"viewRule": null,
|
|
25
|
+
"createRule": null,
|
|
26
|
+
"updateRule": null,
|
|
27
|
+
"deleteRule": null,
|
|
28
|
+
"fields": [
|
|
29
|
+
{"name": "channel", "type": "text", "required": true, "options": {"min": 1, "max": 64}},
|
|
30
|
+
{"name": "enabled", "type": "bool"},
|
|
31
|
+
{"name": "platform", "type": "text", "required": true, "options": {"min": 1, "max": 16}},
|
|
32
|
+
{"name": "should_force_update", "type": "bool"},
|
|
33
|
+
{"name": "file_hash", "type": "text", "required": true},
|
|
34
|
+
{"name": "storage_uri", "type": "text"},
|
|
35
|
+
{"name": "rollout_cohort_count", "type": "number", "options": {"min": 0, "max": 1000}},
|
|
36
|
+
{"name": "message", "type": "text", "options": {"max": 1024}},
|
|
37
|
+
{"name": "fingerprint_hash", "type": "text", "options": {"max": 128}},
|
|
38
|
+
{"name": "target_app_version", "type": "text", "options": {"max": 64}},
|
|
39
|
+
{"name": "git_commit_hash", "type": "text", "options": {"max": 64}},
|
|
40
|
+
{"name": "manifest_storage_uri", "type": "text", "options": {"max": 512}},
|
|
41
|
+
{"name": "manifest_file_hash", "type": "text", "options": {"max": 128}},
|
|
42
|
+
{"name": "asset_base_storage_uri", "type": "text", "options": {"max": 512}},
|
|
43
|
+
{"name": "target_cohorts", "type": "json"},
|
|
44
|
+
{"name": "metadata", "type": "json"},
|
|
45
|
+
{"name": "artifact", "type": "file", "options": {"maxSelect": 1, "maxSize": 0, "mimeTypes": []}}
|
|
46
|
+
],
|
|
47
|
+
"indexes": [
|
|
48
|
+
"CREATE INDEX idx_bundles_channel ON bundles (channel)",
|
|
49
|
+
"CREATE INDEX idx_bundles_platform ON bundles (platform)",
|
|
50
|
+
"CREATE INDEX idx_bundles_enabled ON bundles (enabled)",
|
|
51
|
+
"CREATE INDEX idx_bundles_created ON bundles (created)"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
''';
|
|
55
|
+
|
|
56
|
+
const _kChannelsCollection = r'''
|
|
57
|
+
{
|
|
58
|
+
"name": "channels",
|
|
59
|
+
"type": "base",
|
|
60
|
+
"listRule": null,
|
|
61
|
+
"viewRule": null,
|
|
62
|
+
"createRule": null,
|
|
63
|
+
"updateRule": null,
|
|
64
|
+
"deleteRule": null,
|
|
65
|
+
"fields": [
|
|
66
|
+
{"name": "name", "type": "text", "required": true, "options": {"min": 1, "max": 64, "pattern": "^[a-zA-Z0-9_-]+$"}},
|
|
67
|
+
{"name": "current_bundle", "type": "text", "options": {"max": 64}}
|
|
68
|
+
],
|
|
69
|
+
"indexes": [
|
|
70
|
+
"CREATE UNIQUE INDEX idx_channels_name ON channels (name)"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
''';
|
|
74
|
+
|
|
75
|
+
const _kAuditLogCollection = r'''
|
|
76
|
+
{
|
|
77
|
+
"name": "audit_log",
|
|
78
|
+
"type": "base",
|
|
79
|
+
"listRule": null,
|
|
80
|
+
"viewRule": null,
|
|
81
|
+
"createRule": null,
|
|
82
|
+
"updateRule": null,
|
|
83
|
+
"deleteRule": null,
|
|
84
|
+
"fields": [
|
|
85
|
+
{"name": "user", "type": "text", "options": {"max": 128}},
|
|
86
|
+
{"name": "action", "type": "text", "required": true, "options": {"min": 1, "max": 64}},
|
|
87
|
+
{"name": "bundle_id", "type": "text", "options": {"max": 64}},
|
|
88
|
+
{"name": "channel", "type": "text", "options": {"max": 64}},
|
|
89
|
+
{"name": "details", "type": "json"}
|
|
90
|
+
],
|
|
91
|
+
"indexes": [
|
|
92
|
+
"CREATE INDEX idx_audit_action ON audit_log (action)",
|
|
93
|
+
"CREATE INDEX idx_audit_created ON audit_log (created)"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
''';
|
|
97
|
+
|
|
98
|
+
class _RawAdminClient {
|
|
99
|
+
_RawAdminClient(this.baseUrl);
|
|
100
|
+
final String baseUrl;
|
|
101
|
+
String? _token;
|
|
102
|
+
|
|
103
|
+
Future<String> authenticate(String email, String password) async {
|
|
104
|
+
final res = await http.post(
|
|
105
|
+
Uri.parse('$baseUrl/api/admins/auth-with-password'),
|
|
106
|
+
headers: {'content-type': 'application/json'},
|
|
107
|
+
body: jsonEncode({'identity': email, 'password': password}),
|
|
108
|
+
);
|
|
109
|
+
if (res.statusCode != 200) {
|
|
110
|
+
throw StateError(
|
|
111
|
+
'PocketBase admin auth failed (HTTP ${res.statusCode}). '
|
|
112
|
+
'Is PB running and is the admin email/password correct?',
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
|
116
|
+
final token = body['token'] as String?;
|
|
117
|
+
if (token == null || token.isEmpty) {
|
|
118
|
+
throw StateError('PocketBase returned no admin token.');
|
|
119
|
+
}
|
|
120
|
+
_token = token;
|
|
121
|
+
return token;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Future<List<String>> listCollectionNames() async {
|
|
125
|
+
final res = await http.get(
|
|
126
|
+
Uri.parse('$baseUrl/api/collections'),
|
|
127
|
+
headers: {'authorization': _token ?? ''},
|
|
128
|
+
);
|
|
129
|
+
if (res.statusCode != 200) {
|
|
130
|
+
throw StateError(
|
|
131
|
+
'Failed to list collections (HTTP ${res.statusCode} ${res.body})',
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
|
135
|
+
return ((body['items'] as List?) ?? [])
|
|
136
|
+
.map((c) => (c as Map)['name'] as String? ?? '')
|
|
137
|
+
.where((n) => n.isNotEmpty)
|
|
138
|
+
.toList();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Future<void> createCollection(String schemaJson) async {
|
|
142
|
+
final res = await http.post(
|
|
143
|
+
Uri.parse('$baseUrl/api/collections'),
|
|
144
|
+
headers: {
|
|
145
|
+
'authorization': _token ?? '',
|
|
146
|
+
'content-type': 'application/json',
|
|
147
|
+
},
|
|
148
|
+
body: schemaJson,
|
|
149
|
+
);
|
|
150
|
+
if (res.statusCode != 200 &&
|
|
151
|
+
res.statusCode != 201 &&
|
|
152
|
+
res.statusCode != 400) {
|
|
153
|
+
throw StateError(
|
|
154
|
+
'Failed to create collection (HTTP ${res.statusCode} ${res.body})',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
// 400 here usually means the collection already exists; the installer
|
|
158
|
+
// pre-checks names so this should be a true error.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class PocketBaseSchemaInstallResult {
|
|
163
|
+
const PocketBaseSchemaInstallResult({
|
|
164
|
+
required this.created,
|
|
165
|
+
required this.skipped,
|
|
166
|
+
});
|
|
167
|
+
final List<String> created;
|
|
168
|
+
final List<String> skipped;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Idempotently installs the flutter_ota_kit PocketBase schema.
|
|
172
|
+
class PocketBaseSchemaInstaller {
|
|
173
|
+
PocketBaseSchemaInstaller({
|
|
174
|
+
required this.url,
|
|
175
|
+
required this.adminEmail,
|
|
176
|
+
required this.adminPassword,
|
|
177
|
+
http.Client? httpClient,
|
|
178
|
+
this.bundlesCollection = 'bundles',
|
|
179
|
+
this.channelsCollection = 'channels',
|
|
180
|
+
this.auditLogCollection = 'audit_log',
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
final String url;
|
|
184
|
+
final String adminEmail;
|
|
185
|
+
final String adminPassword;
|
|
186
|
+
final String bundlesCollection;
|
|
187
|
+
final String channelsCollection;
|
|
188
|
+
final String auditLogCollection;
|
|
189
|
+
|
|
190
|
+
Future<PocketBaseSchemaInstallResult> install() async {
|
|
191
|
+
final client = _RawAdminClient(url);
|
|
192
|
+
await client.authenticate(adminEmail, adminPassword);
|
|
193
|
+
final existing = await client.listCollectionNames();
|
|
194
|
+
final created = <String>[];
|
|
195
|
+
final skipped = <String>[];
|
|
196
|
+
for (final spec in _schema()) {
|
|
197
|
+
if (existing.contains(spec.name)) {
|
|
198
|
+
skipped.add(spec.name);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
await client.createCollection(spec.schema);
|
|
202
|
+
created.add(spec.name);
|
|
203
|
+
}
|
|
204
|
+
return PocketBaseSchemaInstallResult(created: created, skipped: skipped);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
List<_CollectionSpec> _schema() {
|
|
208
|
+
// Replace the hard-coded collection names if the user customized them.
|
|
209
|
+
String rename(String json) {
|
|
210
|
+
if (bundlesCollection != 'bundles') {
|
|
211
|
+
json = json.replaceAll('"bundles"', '"$bundlesCollection"');
|
|
212
|
+
}
|
|
213
|
+
if (channelsCollection != 'channels') {
|
|
214
|
+
json = json.replaceAll('"channels"', '"$channelsCollection"');
|
|
215
|
+
}
|
|
216
|
+
if (auditLogCollection != 'audit_log') {
|
|
217
|
+
json = json.replaceAll('"audit_log"', '"$auditLogCollection"');
|
|
218
|
+
}
|
|
219
|
+
return json;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
_CollectionSpec(bundlesCollection, rename(_kBundlesCollection)),
|
|
224
|
+
_CollectionSpec(channelsCollection, rename(_kChannelsCollection)),
|
|
225
|
+
_CollectionSpec(auditLogCollection, rename(_kAuditLogCollection)),
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
class _CollectionSpec {
|
|
231
|
+
const _CollectionSpec(this.name, this.schema);
|
|
232
|
+
final String name;
|
|
233
|
+
final String schema;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Re-export so callers can use the typed client as well.
|
|
237
|
+
typedef PocketBaseBackend = PocketBaseClient;
|
|
@@ -2,6 +2,7 @@ import 'dart:io';
|
|
|
2
2
|
|
|
3
3
|
import 'package:args/args.dart';
|
|
4
4
|
import 'package:args/command_runner.dart';
|
|
5
|
+
|
|
5
6
|
import 'ui/ui.dart';
|
|
6
7
|
import 'verbose.dart';
|
|
7
8
|
|
|
@@ -30,8 +31,10 @@ class FlutterPatcherRunner extends CommandRunner<int> {
|
|
|
30
31
|
maxName = maxName < n.length ? n.length : maxName;
|
|
31
32
|
}
|
|
32
33
|
final buf = StringBuffer();
|
|
33
|
-
buf.writeln(
|
|
34
|
-
|
|
34
|
+
buf.writeln(
|
|
35
|
+
' ${cyan('▶')} ${cyan(bold(executableName))} '
|
|
36
|
+
'${dim('·')} ${bold(description)}',
|
|
37
|
+
);
|
|
35
38
|
buf.writeln(' ${dim('─' * 60)}');
|
|
36
39
|
buf.writeln('');
|
|
37
40
|
buf.writeln(' ${bold('USAGE')}');
|
|
@@ -43,10 +46,14 @@ class FlutterPatcherRunner extends CommandRunner<int> {
|
|
|
43
46
|
buf.writeln(' ${cyan(n.padRight(maxName))} ${dim(c.description)}');
|
|
44
47
|
}
|
|
45
48
|
buf.writeln('');
|
|
46
|
-
buf.writeln(
|
|
49
|
+
buf.writeln(
|
|
50
|
+
' ${dim('Run "$executableName help <command>" for more about a command.')}',
|
|
51
|
+
);
|
|
47
52
|
buf.writeln('');
|
|
48
53
|
buf.writeln(' ${bold('GLOBAL OPTIONS')}');
|
|
49
|
-
buf.writeln(
|
|
54
|
+
buf.writeln(
|
|
55
|
+
' ${cyan('-v, --verbose')} ${dim('Show full error stack traces.')}',
|
|
56
|
+
);
|
|
50
57
|
return buf.toString();
|
|
51
58
|
}
|
|
52
59
|
|
|
@@ -72,35 +79,49 @@ abstract class FlutterPatcherCommand extends Command<int> {
|
|
|
72
79
|
@override
|
|
73
80
|
String get usage {
|
|
74
81
|
final buf = StringBuffer();
|
|
75
|
-
buf.writeln(
|
|
82
|
+
buf.writeln(
|
|
83
|
+
' ${cyan('▶')} ${cyan(bold(name))} '
|
|
84
|
+
'${dim('·')} $description',
|
|
85
|
+
);
|
|
76
86
|
buf.writeln(' ${dim('─' * 60)}');
|
|
77
87
|
buf.writeln('');
|
|
78
88
|
buf.writeln(' ${bold('USAGE')}');
|
|
79
89
|
buf.writeln(' ${_fullName(this)} [arguments]');
|
|
80
90
|
if (subcommands.isNotEmpty) {
|
|
91
|
+
buf.writeln('');
|
|
92
|
+
buf.writeln(' ${bold('COMMANDS')}');
|
|
81
93
|
final names = subcommands.keys.toList()..sort();
|
|
82
94
|
var maxName = 0;
|
|
83
95
|
for (final n in names) {
|
|
84
|
-
|
|
96
|
+
maxName = maxName < n.length ? n.length : maxName;
|
|
85
97
|
}
|
|
86
|
-
buf.writeln('');
|
|
87
|
-
buf.writeln(' ${bold('COMMANDS')}');
|
|
88
98
|
for (final n in names) {
|
|
89
99
|
final c = subcommands[n]!;
|
|
90
100
|
buf.writeln(' ${cyan(n.padRight(maxName))} ${dim(c.description)}');
|
|
91
101
|
}
|
|
92
102
|
buf.writeln('');
|
|
93
|
-
buf.writeln(
|
|
103
|
+
buf.writeln(
|
|
104
|
+
' ${dim('Run "${_fullName(this)} <command>" for more about a command.')}',
|
|
105
|
+
);
|
|
94
106
|
}
|
|
95
107
|
if (argParser.options.isNotEmpty) {
|
|
96
108
|
buf.writeln('');
|
|
97
109
|
buf.writeln(' ${bold('OPTIONS')}');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
final
|
|
103
|
-
|
|
110
|
+
final optLines = argParser.usage
|
|
111
|
+
.split('\n')
|
|
112
|
+
.where((l) => l.trim().isNotEmpty);
|
|
113
|
+
for (final l in optLines) {
|
|
114
|
+
final m = RegExp(r'^\s*(-{1,2}[\w-]+)(.*)$').firstMatch(l);
|
|
115
|
+
if (m != null) {
|
|
116
|
+
buf.writeln(
|
|
117
|
+
' ${cyan(m.group(1)!)}${dim(m.group(2) ?? '')}'.replaceAll(
|
|
118
|
+
'\n',
|
|
119
|
+
'\n ',
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
buf.writeln(' ${dim(l.trim())}');
|
|
124
|
+
}
|
|
104
125
|
}
|
|
105
126
|
}
|
|
106
127
|
return buf.toString();
|
|
@@ -24,7 +24,18 @@ Future<bool> ed25519Verify(
|
|
|
24
24
|
|
|
25
25
|
/// The 12-byte DER prefix of an Ed25519 X.509 SubjectPublicKeyInfo.
|
|
26
26
|
const List<int> _ed25519SpkiPrefix = <int>[
|
|
27
|
-
0x30,
|
|
27
|
+
0x30,
|
|
28
|
+
0x2a,
|
|
29
|
+
0x30,
|
|
30
|
+
0x05,
|
|
31
|
+
0x06,
|
|
32
|
+
0x03,
|
|
33
|
+
0x2b,
|
|
34
|
+
0x65,
|
|
35
|
+
0x70,
|
|
36
|
+
0x03,
|
|
37
|
+
0x21,
|
|
38
|
+
0x00,
|
|
28
39
|
];
|
|
29
40
|
|
|
30
41
|
bool _startsWith(List<int> bytes, List<int> prefix) {
|
|
@@ -44,10 +55,7 @@ Future<(String, String)> generateEd25519KeyPair() async {
|
|
|
44
55
|
final keyPair = await Ed25519().newKeyPair();
|
|
45
56
|
final private = await keyPair.extractPrivateKeyBytes();
|
|
46
57
|
final rawPublic = (await keyPair.extractPublicKey()).bytes;
|
|
47
|
-
final spki = <int>[
|
|
48
|
-
..._ed25519SpkiPrefix,
|
|
49
|
-
...rawPublic,
|
|
50
|
-
];
|
|
58
|
+
final spki = <int>[..._ed25519SpkiPrefix, ...rawPublic];
|
|
51
59
|
return (base64Encode(private), base64Encode(spki));
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -21,9 +21,7 @@ const _kCyan = '\x1b[36m';
|
|
|
21
21
|
const _kGray = '\x1b[90m';
|
|
22
22
|
const _kBold = '\x1b[1m';
|
|
23
23
|
|
|
24
|
-
const List<String> _frames = [
|
|
25
|
-
'⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏',
|
|
26
|
-
];
|
|
24
|
+
const List<String> _frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
27
25
|
|
|
28
26
|
bool get _noColor {
|
|
29
27
|
final v = Platform.environment['NO_COLOR'];
|
|
@@ -61,14 +59,17 @@ String kv(String k, String v) {
|
|
|
61
59
|
|
|
62
60
|
void banner(String name) {
|
|
63
61
|
_echo('');
|
|
64
|
-
_echo(
|
|
62
|
+
_echo(
|
|
63
|
+
' ${cyan('▶')} ${cyan(bold('flutter_ota_kit'))} ${dim('·')} ${bold(name)}',
|
|
64
|
+
);
|
|
65
65
|
_echo(' ${dim('─' * math.min(_cols - 4, 60))}');
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
void step(String msg) => _echo(' ${green('✓')} $msg');
|
|
69
69
|
|
|
70
|
-
void
|
|
71
|
-
|
|
70
|
+
void warn(String msg) => _echo(' ${yellow('!')} ${yellow(msg)}');
|
|
71
|
+
|
|
72
|
+
void err(String msg) => _echo(' ${red('✗')} ${red(msg)}');
|
|
72
73
|
|
|
73
74
|
/// Run [task] while showing a live spinner; replaces the line with a
|
|
74
75
|
/// `${green('✓')} done` summary when finished (or `✗` on error).
|
|
@@ -127,9 +128,7 @@ List<String> _wrap(String text, int inner) {
|
|
|
127
128
|
line += ' $word';
|
|
128
129
|
} else {
|
|
129
130
|
result.add(line);
|
|
130
|
-
line = word.length <= inner
|
|
131
|
-
? word
|
|
132
|
-
: _splitLong(word, inner, result);
|
|
131
|
+
line = word.length <= inner ? word : _splitLong(word, inner, result);
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
134
|
result.add(line);
|
|
@@ -182,8 +181,7 @@ void box(String title, List<String> lines) {
|
|
|
182
181
|
var dashes = math.max(2, width - 2 - tVisible);
|
|
183
182
|
final left = math.max(1, dashes ~/ 2);
|
|
184
183
|
final right = dashes - left;
|
|
185
|
-
final top =
|
|
186
|
-
'╭${'─' * left}$titleText${'─' * right}╮';
|
|
184
|
+
final top = '╭${'─' * left}$titleText${'─' * right}╮';
|
|
187
185
|
_echo(top);
|
|
188
186
|
for (final l in wrapped) {
|
|
189
187
|
final pad = math.max(0, inner - _dispWidth(l));
|
|
@@ -221,8 +219,7 @@ class ProgressBar {
|
|
|
221
219
|
const barW = 28;
|
|
222
220
|
final pct = total > 0 ? (_current / total).clamp(0.0, 1.0) : 1.0;
|
|
223
221
|
final filled = (barW * pct).round();
|
|
224
|
-
final bar =
|
|
225
|
-
'${green('█' * filled)}${gray('·' * (barW - filled))}';
|
|
222
|
+
final bar = '${green('█' * filled)}${gray('·' * (barW - filled))}';
|
|
226
223
|
_spin = (_spin + 1) % _frames.length;
|
|
227
224
|
final pctStr = '${(pct * 100).round()}%'.padLeft(4);
|
|
228
225
|
stdout.write('\r\x1b[K ${cyan(_frames[_spin])} $_label [$bar] $pctStr');
|
|
@@ -30,7 +30,9 @@ Future<String> zipDirectory(String directory) async {
|
|
|
30
30
|
}
|
|
31
31
|
final encoder = ZipEncoder();
|
|
32
32
|
final out = encoder.encode(archive);
|
|
33
|
-
final tmp = File(
|
|
33
|
+
final tmp = File(
|
|
34
|
+
'${Directory.systemTemp.path}/fp_${Random().nextInt(1 << 30)}.zip',
|
|
35
|
+
);
|
|
34
36
|
await tmp.writeAsBytes(out);
|
|
35
37
|
return tmp.path;
|
|
36
38
|
}
|