@_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
|
@@ -28,29 +28,30 @@ class KeysCommand extends FlutterPatcherCommand {
|
|
|
28
28
|
|
|
29
29
|
@override
|
|
30
30
|
Future<int> run() => runGuarded(() async {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
31
|
+
banner('keys');
|
|
32
|
+
final (privateB64, publicB64) = await spinner(
|
|
33
|
+
() => generateEd25519KeyPair(),
|
|
34
|
+
'Generating Ed25519 keypair',
|
|
35
|
+
done: 'Keypair generated',
|
|
36
|
+
);
|
|
37
|
+
box('ed25519 keys', [
|
|
38
|
+
'${bold('private')} (keep secret — use with `deploy --key`):',
|
|
39
|
+
' ${red(privateB64)}',
|
|
40
|
+
'',
|
|
41
|
+
'${bold('public')} (safe to embed / configure on device):',
|
|
42
|
+
' ${green(publicB64)}',
|
|
43
|
+
]);
|
|
44
|
+
if (argResults!['save'] as bool) {
|
|
45
|
+
final file = configCandidates().first;
|
|
46
|
+
final json =
|
|
47
|
+
file.existsSync() && file.readAsStringSync().trim().isNotEmpty
|
|
48
|
+
? (jsonDecode(file.readAsStringSync()) as Map<String, dynamic>)
|
|
49
|
+
: <String, dynamic>{};
|
|
50
|
+
writePath(json, 'publicKey', publicB64);
|
|
51
|
+
_save(json);
|
|
52
|
+
step('Saved public key to ${file.path}');
|
|
53
|
+
}
|
|
54
|
+
});
|
|
54
55
|
|
|
55
56
|
void _save(Map<String, dynamic> json) {
|
|
56
57
|
final file = configCandidates().first;
|
|
@@ -21,14 +21,25 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
21
21
|
@override
|
|
22
22
|
ArgParser get argParser => ArgParser()
|
|
23
23
|
..addOption('backend', abbr: 'b', help: 'Backend provider.')
|
|
24
|
-
..addOption(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
..addOption(
|
|
25
|
+
'database-url',
|
|
26
|
+
help: 'Postgres connection string (or DATABASE_URL env).',
|
|
27
|
+
)
|
|
28
|
+
..addOption(
|
|
29
|
+
'management-key',
|
|
30
|
+
help:
|
|
31
|
+
'Supabase Management API key (or SUPABASE_MANAGEMENT_KEY env) — '
|
|
32
|
+
'runs migrations without a separate Postgres connection.',
|
|
33
|
+
)
|
|
34
|
+
..addOption(
|
|
35
|
+
'migrations-dir',
|
|
36
|
+
help: 'Directory of *.sql migration files (ordered by name).',
|
|
37
|
+
)
|
|
38
|
+
..addFlag(
|
|
39
|
+
'dry-run',
|
|
40
|
+
abbr: 'd',
|
|
41
|
+
help: 'Print migrations instead of applying them.',
|
|
42
|
+
);
|
|
32
43
|
|
|
33
44
|
/// Default migrations dir lives next to the CLI entrypoint; each backend has
|
|
34
45
|
/// its own dialect's migrations (cloudflare = D1 SQLite, postgres = plain
|
|
@@ -49,12 +60,13 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
49
60
|
if (!migrationsDir.existsSync()) {
|
|
50
61
|
throw StateError('Migrations directory not found: $dir');
|
|
51
62
|
}
|
|
52
|
-
final files =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
final files =
|
|
64
|
+
migrationsDir
|
|
65
|
+
.listSync()
|
|
66
|
+
.whereType<File>()
|
|
67
|
+
.where((f) => f.path.endsWith('.sql'))
|
|
68
|
+
.toList()
|
|
69
|
+
..sort((a, b) => a.path.compareTo(b.path));
|
|
58
70
|
if (files.isEmpty) {
|
|
59
71
|
throw StateError('No *.sql files in $dir');
|
|
60
72
|
}
|
|
@@ -71,92 +83,98 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
71
83
|
|
|
72
84
|
@override
|
|
73
85
|
Future<int> run() => runGuarded(() async {
|
|
74
|
-
|
|
86
|
+
final provider = argResults!['backend'] as String? ?? 'supabase';
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// Backends with no relational SQL handled out-of-band (S3 blob store).
|
|
89
|
+
// Short-circuit before any SQL discovery so a missing or empty
|
|
90
|
+
// (README-only) migrations dir does not abort.
|
|
91
|
+
if (provider == 'aws') {
|
|
92
|
+
final dir =
|
|
93
|
+
argResults!['migrations-dir'] as String? ??
|
|
94
|
+
defaultMigrationsDir(provider);
|
|
95
|
+
banner('migrate');
|
|
96
|
+
box('migrate', [
|
|
97
|
+
'Migrations for "$provider" do not run through this command:',
|
|
98
|
+
' • aws → S3 blob store, no relational SQL (see $dir/README.md)',
|
|
99
|
+
'',
|
|
100
|
+
'See the README under $dir for the intended setup.',
|
|
101
|
+
]);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
105
|
+
final dir =
|
|
106
|
+
argResults!['migrations-dir'] as String? ??
|
|
107
|
+
defaultMigrationsDir(provider);
|
|
108
|
+
final files = _listMigrations(dir);
|
|
95
109
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
110
|
+
if (argResults!['dry-run'] as bool) {
|
|
111
|
+
banner('migrate · dry-run');
|
|
112
|
+
for (final f in files) {
|
|
113
|
+
stdout.writeln('--- ${p.basename(f.path)} ---');
|
|
114
|
+
stdout.writeln(f.readAsStringSync());
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
if (provider == 'cloudflare') {
|
|
120
|
+
banner('migrate');
|
|
121
|
+
box('migrate', [
|
|
122
|
+
'Cloudflare (D1 SQLite) migrations run via `wrangler d1 execute`:',
|
|
123
|
+
' wrangler d1 execute <DB> --file=$dir/0001_hot-updater_init.sql',
|
|
124
|
+
' (apply each *.sql under $dir in filename order)',
|
|
125
|
+
'',
|
|
126
|
+
'The SQL files under $dir show the intended D1 schema.',
|
|
127
|
+
]);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
if (provider == 'supabase') {
|
|
132
|
+
final cfg =
|
|
133
|
+
loadConfig() ??
|
|
119
134
|
FlutterPatcherConfig(
|
|
120
135
|
provider: 'supabase',
|
|
121
136
|
supabase: const SupabaseConfigJson(),
|
|
122
137
|
);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
final mgmtKey =
|
|
139
|
+
argResults!['management-key'] as String? ??
|
|
140
|
+
Platform.environment['SUPABASE_MANAGEMENT_KEY'] ??
|
|
141
|
+
cfg.supabase.managementKey;
|
|
142
|
+
final pgUrl =
|
|
143
|
+
argResults!['database-url'] as String? ??
|
|
144
|
+
Platform.environment['DATABASE_URL'] ??
|
|
145
|
+
cfg.supabase.databaseUrl;
|
|
146
|
+
if (mgmtKey != null && mgmtKey.isNotEmpty) {
|
|
147
|
+
await _runViaManagementApi(mgmtKey, files);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (pgUrl != null && pgUrl.isNotEmpty) {
|
|
151
|
+
await _runViaPostgres(pgUrl, files);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
throw StateError(
|
|
155
|
+
'For the supabase backend, provide either:\n'
|
|
156
|
+
' • --management-key (or SUPABASE_MANAGEMENT_KEY, or set it via '
|
|
157
|
+
'`flutter-ota init`) — a Supabase Management API key, OR\n'
|
|
158
|
+
' • --database-url (or DATABASE_URL, or set it via `init`) — a '
|
|
159
|
+
'Postgres connection string.',
|
|
160
|
+
);
|
|
161
|
+
}
|
|
145
162
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
163
|
+
if (provider == 'postgres') {
|
|
164
|
+
final pgUrl =
|
|
165
|
+
argResults!['database-url'] as String? ??
|
|
166
|
+
Platform.environment['DATABASE_URL'];
|
|
167
|
+
if (pgUrl == null || pgUrl.isEmpty) {
|
|
168
|
+
throw StateError(
|
|
169
|
+
'For the postgres backend, provide --database-url or set DATABASE_URL.',
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
await _runViaPostgres(pgUrl, files);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
157
175
|
|
|
158
|
-
|
|
159
|
-
|
|
176
|
+
throw StateError('Unknown backend provider: "$provider".');
|
|
177
|
+
});
|
|
160
178
|
|
|
161
179
|
/// Run migrations through the Supabase Management API (no Postgres creds
|
|
162
180
|
/// needed). Derived from the Supabase project ref in the configured URL.
|
|
@@ -169,8 +187,7 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
169
187
|
),
|
|
170
188
|
);
|
|
171
189
|
final ref = Uri.parse(cfg.supabaseUrl).host.split('.').first;
|
|
172
|
-
final endpoint =
|
|
173
|
-
'https://api.supabase.com/v1/projects/$ref/database/query';
|
|
190
|
+
final endpoint = 'https://api.supabase.com/v1/projects/$ref/database/query';
|
|
174
191
|
banner('migrate · supabase (management api)');
|
|
175
192
|
for (final file in files) {
|
|
176
193
|
final sql = file.readAsStringSync();
|
|
@@ -206,8 +223,8 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
206
223
|
if (storage.supabaseServiceRoleKey == null) {
|
|
207
224
|
box('migrate', [
|
|
208
225
|
'Skipped storage bucket creation — set supabase.serviceRoleKey '
|
|
209
|
-
|
|
210
|
-
|
|
226
|
+
'(or SUPABASE_SERVICE_ROLE_KEY) to auto-create the '
|
|
227
|
+
'"${storage.bucketName}" bucket.',
|
|
211
228
|
]);
|
|
212
229
|
return;
|
|
213
230
|
}
|
|
@@ -218,10 +235,7 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
218
235
|
'apikey': storage.supabaseServiceRoleKey!,
|
|
219
236
|
'Content-Type': 'application/json',
|
|
220
237
|
},
|
|
221
|
-
body: jsonEncode({
|
|
222
|
-
'name': storage.bucketName,
|
|
223
|
-
'public': true,
|
|
224
|
-
}),
|
|
238
|
+
body: jsonEncode({'name': storage.bucketName, 'public': true}),
|
|
225
239
|
);
|
|
226
240
|
if (res.statusCode >= 400) {
|
|
227
241
|
final body = jsonDecode(res.body) as Map<String, dynamic>;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/// `flutter_ota_kit pocketbase` — manage a local PocketBase instance.
|
|
2
|
+
library;
|
|
3
|
+
|
|
4
|
+
import 'dart:async';
|
|
5
|
+
import 'dart:io';
|
|
6
|
+
|
|
7
|
+
import 'package:args/args.dart';
|
|
8
|
+
import 'package:path/path.dart' as p;
|
|
9
|
+
|
|
10
|
+
import '../cli_base.dart';
|
|
11
|
+
import '../pocketbase/data_bootstrap.dart';
|
|
12
|
+
import '../pocketbase/installer.dart';
|
|
13
|
+
import '../pocketbase/process_manager.dart';
|
|
14
|
+
import '../pocketbase/schema_installer.dart';
|
|
15
|
+
import '../ui/ui.dart';
|
|
16
|
+
|
|
17
|
+
class PocketBaseCommand extends FlutterPatcherCommand {
|
|
18
|
+
PocketBaseCommand() {
|
|
19
|
+
addSubcommand(PocketBaseInstallCommand());
|
|
20
|
+
addSubcommand(PocketBaseServeCommand());
|
|
21
|
+
addSubcommand(PocketBaseStopCommand());
|
|
22
|
+
addSubcommand(PocketBaseStatusCommand());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
String get name => 'pocketbase';
|
|
27
|
+
|
|
28
|
+
@override
|
|
29
|
+
String get description =>
|
|
30
|
+
'Manage a local PocketBase instance (install / serve / stop / status).';
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
ArgParser get argParser => ArgParser();
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
Future<int> run() => runGuarded(() async {
|
|
37
|
+
err('Missing subcommand. Use: install | serve | stop | status');
|
|
38
|
+
return;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class PocketBaseInstallCommand extends FlutterPatcherCommand {
|
|
43
|
+
@override
|
|
44
|
+
String get name => 'install';
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
String get description => 'Download + extract the PocketBase binary.';
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
ArgParser get argParser =>
|
|
51
|
+
ArgParser()..addOption('version', help: 'PocketBase version to install.');
|
|
52
|
+
|
|
53
|
+
@override
|
|
54
|
+
Future<int> run() => runGuarded(() async {
|
|
55
|
+
final version =
|
|
56
|
+
argResults!['version'] as String? ?? kDefaultPocketBaseVersion;
|
|
57
|
+
final installer = PocketBaseInstaller(version: version);
|
|
58
|
+
final paths = installer.paths();
|
|
59
|
+
step('Installing PocketBase v$version to ${paths.installDir.path}...');
|
|
60
|
+
final result = await installer.ensureInstalled(
|
|
61
|
+
paths: paths,
|
|
62
|
+
onProgress: (p) {
|
|
63
|
+
stdout.write('\r ${(p * 100).toStringAsFixed(0)}% ');
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
stdout.writeln();
|
|
67
|
+
if (result.alreadyInstalled) {
|
|
68
|
+
step('Already installed at ${paths.binaryPath.path}');
|
|
69
|
+
} else {
|
|
70
|
+
step(
|
|
71
|
+
'Downloaded ${(result.bytesDownloaded / 1024 / 1024).toStringAsFixed(1)} MB',
|
|
72
|
+
);
|
|
73
|
+
step('Binary ready at ${paths.binaryPath.path}');
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class PocketBaseServeCommand extends FlutterPatcherCommand {
|
|
80
|
+
@override
|
|
81
|
+
String get name => 'serve';
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
String get description =>
|
|
85
|
+
'Start a local PocketBase + install the flutter_ota_kit schema.';
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
ArgParser get argParser => ArgParser()
|
|
89
|
+
..addOption('version', help: 'PocketBase version.')
|
|
90
|
+
..addOption('port', help: 'PB HTTP port.', defaultsTo: '8090')
|
|
91
|
+
..addOption('host', help: 'PB bind address.', defaultsTo: '127.0.0.1')
|
|
92
|
+
..addOption('data-dir', help: 'PB data directory.')
|
|
93
|
+
..addOption('admin-email', help: 'Bootstrap admin email.')
|
|
94
|
+
..addOption('admin-password', help: 'Bootstrap admin password.')
|
|
95
|
+
..addFlag(
|
|
96
|
+
'install-hooks',
|
|
97
|
+
help: 'Copy bundled JS hooks into the data dir on start.',
|
|
98
|
+
defaultsTo: true,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
@override
|
|
102
|
+
Future<int> run() => runGuarded(() async {
|
|
103
|
+
final r = argResults!;
|
|
104
|
+
final version = r['version'] as String? ?? kDefaultPocketBaseVersion;
|
|
105
|
+
final port = int.tryParse(r['port'] as String? ?? '8090') ?? 8090;
|
|
106
|
+
final host = r['host'] as String? ?? '127.0.0.1';
|
|
107
|
+
final dataDirOverride = r['data-dir'] as String?;
|
|
108
|
+
final adminEmail =
|
|
109
|
+
r['admin-email'] as String? ??
|
|
110
|
+
Platform.environment['POCKETBASE_ADMIN_EMAIL'];
|
|
111
|
+
final adminPassword =
|
|
112
|
+
r['admin-password'] as String? ??
|
|
113
|
+
Platform.environment['POCKETBASE_ADMIN_PASSWORD'];
|
|
114
|
+
final installHooks = r['install-hooks'] as bool? ?? true;
|
|
115
|
+
|
|
116
|
+
final paths = PocketBaseInstallPaths.resolve(version: version);
|
|
117
|
+
final dataDir = dataDirOverride != null
|
|
118
|
+
? Directory(dataDirOverride)
|
|
119
|
+
: paths.installDir;
|
|
120
|
+
final installer = PocketBaseInstaller(version: version);
|
|
121
|
+
step('Ensuring PocketBase v$version is installed...');
|
|
122
|
+
await installer.ensureInstalled(paths: paths);
|
|
123
|
+
step('Binary at ${paths.binaryPath.path}');
|
|
124
|
+
|
|
125
|
+
if (installHooks) {
|
|
126
|
+
final bootstrap = PocketBaseDataBootstrap(
|
|
127
|
+
dataDir: dataDir,
|
|
128
|
+
hooksSourceDir: _hooksSourceDir(),
|
|
129
|
+
);
|
|
130
|
+
final installed = await bootstrap.install();
|
|
131
|
+
if (installed.isNotEmpty) {
|
|
132
|
+
step('Installed hooks: ${installed.join(', ')}');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
final manager = PocketBaseProcessManager(
|
|
137
|
+
binaryPath: paths.binaryPath,
|
|
138
|
+
dataDir: dataDir,
|
|
139
|
+
port: port,
|
|
140
|
+
host: host,
|
|
141
|
+
);
|
|
142
|
+
step(
|
|
143
|
+
'Starting PocketBase on http://$host:$port (data: ${dataDir.path})...',
|
|
144
|
+
);
|
|
145
|
+
await manager.start(adminEmail: adminEmail, adminPassword: adminPassword);
|
|
146
|
+
step('PocketBase is running (pid ${manager.process!.pid}).');
|
|
147
|
+
|
|
148
|
+
if (adminEmail != null && adminPassword != null) {
|
|
149
|
+
step('Installing flutter_ota_kit schema...');
|
|
150
|
+
final schemaInstaller = PocketBaseSchemaInstaller(
|
|
151
|
+
url: 'http://$host:$port',
|
|
152
|
+
adminEmail: adminEmail,
|
|
153
|
+
adminPassword: adminPassword,
|
|
154
|
+
);
|
|
155
|
+
try {
|
|
156
|
+
final res = await schemaInstaller.install();
|
|
157
|
+
if (res.created.isNotEmpty) {
|
|
158
|
+
step('Created collections: ${res.created.join(', ')}');
|
|
159
|
+
}
|
|
160
|
+
if (res.skipped.isNotEmpty) {
|
|
161
|
+
step('Already present: ${res.skipped.join(', ')}');
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
warn('Schema install failed: $e');
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
step('Skipping schema install (no --admin-email/--admin-password).');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
banner('pocketbase');
|
|
171
|
+
box('flutter_ota_kit pocketbase serve', [
|
|
172
|
+
'Local PocketBase is running:',
|
|
173
|
+
'',
|
|
174
|
+
kv('url', 'http://$host:$port'),
|
|
175
|
+
kv('admin-ui', 'http://$host:$port/_/'),
|
|
176
|
+
kv('data', dataDir.path),
|
|
177
|
+
kv('pid', '${manager.process!.pid}'),
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
// Wait for SIGINT/SIGTERM.
|
|
181
|
+
final done = Completer<void>();
|
|
182
|
+
ProcessSignal.sigint.watch().listen((_) => done.complete());
|
|
183
|
+
if (!Platform.isWindows) {
|
|
184
|
+
ProcessSignal.sigterm.watch().listen((_) => done.complete());
|
|
185
|
+
}
|
|
186
|
+
await done.future;
|
|
187
|
+
step('Shutting down PocketBase...');
|
|
188
|
+
await manager.stop();
|
|
189
|
+
step('PocketBase stopped.');
|
|
190
|
+
return;
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
static Directory _hooksSourceDir() {
|
|
194
|
+
final exe = Platform.script.toFilePath();
|
|
195
|
+
final pkgRoot = Directory(p.dirname(p.dirname(p.dirname(exe))));
|
|
196
|
+
return Directory(p.join(pkgRoot.path, 'lib', 'src', 'pocketbase', 'hooks'));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
class PocketBaseStopCommand extends FlutterPatcherCommand {
|
|
201
|
+
@override
|
|
202
|
+
String get name => 'stop';
|
|
203
|
+
|
|
204
|
+
@override
|
|
205
|
+
String get description =>
|
|
206
|
+
'Hint to stop a running PocketBase (Ctrl+C in serve).';
|
|
207
|
+
|
|
208
|
+
@override
|
|
209
|
+
Future<int> run() => runGuarded(() async {
|
|
210
|
+
step('Use Ctrl+C on the running serve process to stop PocketBase.');
|
|
211
|
+
return;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
class PocketBaseStatusCommand extends FlutterPatcherCommand {
|
|
216
|
+
@override
|
|
217
|
+
String get name => 'status';
|
|
218
|
+
|
|
219
|
+
@override
|
|
220
|
+
String get description => 'Show the installed PocketBase version + path.';
|
|
221
|
+
|
|
222
|
+
@override
|
|
223
|
+
Future<int> run() => runGuarded(() async {
|
|
224
|
+
final paths = PocketBaseInstallPaths.resolve();
|
|
225
|
+
final exists = await paths.binaryPath.exists();
|
|
226
|
+
if (exists) {
|
|
227
|
+
step(
|
|
228
|
+
'PocketBase v${paths.version}: installed at ${paths.binaryPath.path}',
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
step(
|
|
232
|
+
'PocketBase: not installed. Run `flutter_ota_kit pocketbase install`.',
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
@@ -22,44 +22,53 @@ class RollbackCommand extends FlutterPatcherCommand {
|
|
|
22
22
|
ArgParser get argParser => ArgParser()
|
|
23
23
|
..addOption('backend', abbr: 'b', help: 'Backend provider.')
|
|
24
24
|
..addOption('channel', abbr: 'c', help: 'Channel to roll back.')
|
|
25
|
-
..addOption(
|
|
25
|
+
..addOption(
|
|
26
|
+
'bundle-id',
|
|
27
|
+
abbr: 'i',
|
|
28
|
+
help: 'Roll back to this specific bundle id.',
|
|
29
|
+
)
|
|
26
30
|
..addOption('platform', abbr: 'p', help: 'Platform filter (e.g. android).');
|
|
27
31
|
|
|
28
32
|
@override
|
|
29
33
|
Future<int> run() => runGuarded(() async {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
final channel = argResults!['channel'] as String?;
|
|
35
|
+
if (channel == null || channel.isEmpty) {
|
|
36
|
+
throw PackException(
|
|
37
|
+
'Usage: flutter_ota_kit rollback --channel <channel> [--bundle-id <id>]',
|
|
38
|
+
64,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
final bundleId = argResults!['bundle-id'] as String?;
|
|
42
|
+
final platformRaw = argResults!['platform'] as String?;
|
|
43
|
+
final platform = platformRaw == null
|
|
44
|
+
? null
|
|
45
|
+
: Platform.fromValue(platformRaw);
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
48
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
banner('rollback');
|
|
51
|
+
final (disabled, live) = await spinner(
|
|
52
|
+
() async {
|
|
53
|
+
if (bundleId != null && bundleId.isNotEmpty) {
|
|
54
|
+
return rollbackToBundle(
|
|
55
|
+
backend,
|
|
56
|
+
channel,
|
|
57
|
+
bundleId,
|
|
58
|
+
platform: platform,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
final id = await rollbackChannel(backend, channel);
|
|
62
|
+
final nowLive = (await getChannel(backend, channel))?.id ?? '';
|
|
63
|
+
return (<String>[id], nowLive);
|
|
64
|
+
},
|
|
65
|
+
'Rolling back channel "$channel"',
|
|
66
|
+
done: 'Rolled back',
|
|
67
|
+
);
|
|
68
|
+
box('rollback', [
|
|
69
|
+
kv('channel', channel),
|
|
70
|
+
kv('now live', cyan(live)),
|
|
71
|
+
kv('disabled', disabled.isEmpty ? dim('(none)') : disabled.join(', ')),
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
65
74
|
}
|