@_nazmiforreal/flutter-ota 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/bin/flutter-ota-linux-x64 +0 -0
  2. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +1 -1
  3. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +340 -340
  4. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +9 -5
  5. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -2
  6. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +106 -31
  7. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +34 -7
  8. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +12 -4
  9. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +7 -8
  10. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +41 -9
  11. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +124 -13
  12. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +2 -1
  13. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +58 -28
  14. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +11 -5
  15. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +283 -32
  16. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +347 -121
  17. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +18 -9
  18. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +32 -15
  19. package/dart-src/packages/cli-tools/lib/src/config.dart +16 -22
  20. package/dart-src/packages/cli-tools/lib/src/operations.dart +1 -3
  21. package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +26 -17
  22. package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +0 -1
  23. package/dart-src/packages/cli-tools/lib/src/runner.dart +3 -1
  24. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -16
  25. package/dart-src/packages/cli-tools/pubspec.lock +7 -7
  26. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +7 -3
  27. package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +13 -16
  28. package/dart-src/packages/core/CHANGELOG.md +15 -0
  29. package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +2 -0
  30. package/dart-src/packages/core/lib/src/app_update_info.dart +71 -1
  31. package/dart-src/packages/core/lib/src/bundle.dart +59 -17
  32. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +7 -3
  33. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +16 -0
  34. package/dart-src/packages/core/lib/src/changed_asset.dart +33 -3
  35. package/dart-src/packages/core/lib/src/get_bundles_args.dart +24 -2
  36. package/dart-src/packages/core/lib/src/platform.dart +8 -0
  37. package/dart-src/packages/core/lib/src/semver.dart +0 -3
  38. package/dart-src/packages/core/lib/src/status.dart +20 -1
  39. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  40. package/dart-src/packages/core/lib/src/update_bundle_params.dart +15 -0
  41. package/dart-src/packages/core/lib/src/uuid.dart +11 -0
  42. package/dart-src/packages/core/pubspec.yaml +1 -1
  43. package/dart-src/plugins/aws/CHANGELOG.md +18 -0
  44. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +0 -1
  45. package/dart-src/plugins/aws/pubspec.yaml +3 -3
  46. package/dart-src/plugins/cloudflare/CHANGELOG.md +18 -0
  47. package/dart-src/plugins/cloudflare/pubspec.yaml +3 -3
  48. package/dart-src/plugins/plugin-core/CHANGELOG.md +15 -0
  49. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +3 -0
  50. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +17 -0
  51. package/dart-src/plugins/plugin-core/lib/src/types.dart +94 -3
  52. package/dart-src/plugins/plugin-core/pubspec.yaml +2 -2
  53. package/dart-src/plugins/pocketbase/CHANGELOG.md +18 -0
  54. package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +3 -0
  55. package/dart-src/plugins/pocketbase/pubspec.yaml +3 -3
  56. package/dart-src/plugins/postgres/CHANGELOG.md +18 -0
  57. package/dart-src/plugins/postgres/pubspec.yaml +3 -3
  58. package/dart-src/plugins/supabase/CHANGELOG.md +18 -0
  59. package/dart-src/plugins/supabase/pubspec.yaml +3 -3
  60. package/package.json +2 -2
  61. package/scripts/postinstall.js +5 -1
  62. package/bin/flutter-ota.js +0 -36
@@ -18,7 +18,7 @@ import '../pocketbase/schema_installer.dart';
18
18
  import '../ui/ui.dart';
19
19
 
20
20
  class PocketBaseCommand extends FlutterPatcherCommand {
21
- PocketBaseCommand() {
21
+ PocketBaseCommand() {
22
22
  addSubcommand(PocketBaseInstallCommand());
23
23
  addSubcommand(PocketBaseServeCommand());
24
24
  addSubcommand(PocketBaseStopCommand());
@@ -46,7 +46,7 @@ class PocketBaseCommand extends FlutterPatcherCommand {
46
46
  String get description =>
47
47
  'Manage a local PocketBase instance (install / serve / stop / '
48
48
  'status / backup / export / import / admin / migrate / health / '
49
- 'logs / records / collections / settings / sql / crons).';
49
+ 'logs / records / collections / settings / sql / crons / query / config).';
50
50
  }
51
51
 
52
52
  class PocketBaseInstallCommand extends FlutterPatcherCommand {
@@ -91,7 +91,11 @@ class PocketBaseServeCommand extends FlutterPatcherCommand {
91
91
  PocketBaseServeCommand() {
92
92
  argParser.addOption('version', help: 'PocketBase version.');
93
93
  argParser.addOption('port', help: 'PB HTTP port.', defaultsTo: '8090');
94
- argParser.addOption('host', help: 'PB bind address.', defaultsTo: '127.0.0.1');
94
+ argParser.addOption(
95
+ 'host',
96
+ help: 'PB bind address.',
97
+ defaultsTo: '127.0.0.1',
98
+ );
95
99
  argParser.addOption('data-dir', help: 'PB data directory.');
96
100
  argParser.addOption('admin-email', help: 'Bootstrap admin email.');
97
101
  argParser.addOption('admin-password', help: 'Bootstrap admin password.');
@@ -225,18 +229,36 @@ class PocketBaseServeCommand extends FlutterPatcherCommand {
225
229
  }
226
230
 
227
231
  class PocketBaseStopCommand extends FlutterPatcherCommand {
232
+ PocketBaseStopCommand() {
233
+ argParser.addOption('port', help: 'HTTP port.', defaultsTo: '8090');
234
+ argParser.addOption('host', help: 'Bind address.', defaultsTo: '127.0.0.1');
235
+ }
236
+
228
237
  @override
229
238
  String get name => 'stop';
230
239
 
231
240
  @override
232
- String get description =>
233
- 'Hint to stop a running PocketBase (Ctrl+C in serve).';
241
+ String get description => 'Stop a running PocketBase instance.';
234
242
 
235
243
  @override
236
244
  Future<int> run() => runGuarded(() async {
237
245
  banner('pocketbase · stop');
238
- step('Use Ctrl+C on the running serve process to stop PocketBase.');
239
- return;
246
+ final port = int.tryParse(argResults!['port'] as String? ?? '8090') ?? 8090;
247
+ final host = argResults!['host'] as String? ?? '127.0.0.1';
248
+ final mgr = PocketBaseProcessManager(
249
+ binaryPath: PocketBaseInstallPaths.resolve().binaryPath,
250
+ dataDir: PocketBaseInstallPaths.resolve().installDir,
251
+ port: port,
252
+ host: host,
253
+ );
254
+ final pid = mgr.readPid();
255
+ if (pid <= 0) {
256
+ step('No PocketBase process found.');
257
+ return;
258
+ }
259
+ step('Stopping PocketBase (PID $pid)...');
260
+ await mgr.stop();
261
+ step('PocketBase stopped.');
240
262
  });
241
263
  }
242
264
 
@@ -281,7 +303,10 @@ class PocketBaseStatusCommand extends FlutterPatcherCommand {
281
303
  box('status', [
282
304
  kv('version', paths.version),
283
305
  kv('binary', exists ? green('installed') : red('not installed')),
284
- kv('pid', pidAlive ? green('$pid') : (pid > 0 ? yellow('$pid (dead)') : dim('-'))),
306
+ kv(
307
+ 'pid',
308
+ pidAlive ? green('$pid') : (pid > 0 ? yellow('$pid (dead)') : dim('-')),
309
+ ),
285
310
  kv('health', healthy ? green('healthy') : red('unreachable')),
286
311
  kv('url', 'http://$host:$port'),
287
312
  ]);
@@ -300,7 +325,14 @@ void _addBackendOptions(ArgParser parser) {
300
325
  parser.addOption('data-dir', help: 'PB data directory.');
301
326
  }
302
327
 
303
- (String url, String adminEmail, String adminPassword) _resolveBackend(ArgResults r) {
328
+ String _safeSubstring(String? s, int length) {
329
+ if (s == null || s.isEmpty) return '';
330
+ return s.length > length ? s.substring(0, length) : s;
331
+ }
332
+
333
+ (String url, String adminEmail, String adminPassword) _resolveBackend(
334
+ ArgResults r,
335
+ ) {
304
336
  final url = r['url'] as String? ?? _detectRunningUrl(r);
305
337
  if (url == null || url.isEmpty) {
306
338
  throw const PackException(
@@ -308,10 +340,12 @@ void _addBackendOptions(ArgParser parser) {
308
340
  64,
309
341
  );
310
342
  }
311
- final admin = r['admin-email'] as String? ??
343
+ final admin =
344
+ r['admin-email'] as String? ??
312
345
  Platform.environment['POCKETBASE_ADMIN_EMAIL'] ??
313
346
  '';
314
- final pass = r['admin-password'] as String? ??
347
+ final pass =
348
+ r['admin-password'] as String? ??
315
349
  Platform.environment['POCKETBASE_ADMIN_PASSWORD'] ??
316
350
  '';
317
351
  if (admin.isEmpty || pass.isEmpty) {
@@ -464,7 +498,11 @@ class PocketBaseBackupDeleteCommand extends FlutterPatcherCommand {
464
498
  class PocketBaseBackupRestoreCommand extends FlutterPatcherCommand {
465
499
  PocketBaseBackupRestoreCommand() {
466
500
  _addBackendOptions(argParser);
467
- argParser.addOption('name', help: 'Backup key to restore.', mandatory: true);
501
+ argParser.addOption(
502
+ 'name',
503
+ help: 'Backup key to restore.',
504
+ mandatory: true,
505
+ );
468
506
  }
469
507
 
470
508
  @override
@@ -485,7 +523,11 @@ class PocketBaseBackupRestoreCommand extends FlutterPatcherCommand {
485
523
  class PocketBaseBackupDownloadCommand extends FlutterPatcherCommand {
486
524
  PocketBaseBackupDownloadCommand() {
487
525
  _addBackendOptions(argParser);
488
- argParser.addOption('name', help: 'Backup key to download.', mandatory: true);
526
+ argParser.addOption(
527
+ 'name',
528
+ help: 'Backup key to download.',
529
+ mandatory: true,
530
+ );
489
531
  argParser.addOption('output', help: 'Output directory.', defaultsTo: '.');
490
532
  }
491
533
 
@@ -500,16 +542,20 @@ class PocketBaseBackupDownloadCommand extends FlutterPatcherCommand {
500
542
  banner('pocketbase · backup · download');
501
543
  final key = argResults!['name'] as String;
502
544
  final output = argResults!['output'] as String;
503
- final token = await _pbStep('Getting file token', (c) => c.getFileToken('', ''), argResults!);
504
- final (url, _, _) = _resolveBackend(argResults!);
545
+ final (url, admin, pass) = _resolveBackend(argResults!);
505
546
  final dlClient = PocketBaseClient(url);
506
- final url2 = dlClient.backupDownloadUrl(key, token);
507
- final bytes = await dlClient.downloadFileUnauth(url2);
508
- dlClient.close();
509
- final out = File(p.join(output, key));
510
- await out.parent.create(recursive: true);
511
- await out.writeAsBytes(bytes);
512
- step(green('Saved to ${out.path}'));
547
+ try {
548
+ await dlClient.authenticate(admin, pass);
549
+ final token = dlClient.authToken;
550
+ final dlUrl = dlClient.backupDownloadUrl(key, token);
551
+ final bytes = await dlClient.downloadFileUnauth(dlUrl);
552
+ final out = File(p.join(output, key));
553
+ await out.parent.create(recursive: true);
554
+ await out.writeAsBytes(bytes);
555
+ step(green('Saved to ${out.path}'));
556
+ } finally {
557
+ dlClient.close();
558
+ }
513
559
  });
514
560
  }
515
561
 
@@ -566,39 +612,48 @@ class PocketBaseExportCommand extends FlutterPatcherCommand {
566
612
  final collection = r['collection'] as String?;
567
613
  final output = r['output'] as String;
568
614
 
569
- final client = PocketBaseClient(_resolveBackend(r).$1);
570
- await client.authenticate(_resolveBackend(r).$2, _resolveBackend(r).$3);
571
-
572
- final collections = <String>[];
573
- if (collection != null && collection.isNotEmpty) {
574
- collections.add(collection);
575
- } else {
576
- final all = await client.listCollections();
577
- for (final c in all) {
578
- final name = c['name'] as String? ?? '';
579
- if (name.isNotEmpty && !name.startsWith('_') && c['system'] != true) {
580
- collections.add(name);
615
+ final (url, admin, pass) = _resolveBackend(r);
616
+ final client = PocketBaseClient(url);
617
+ try {
618
+ await client.authenticate(admin, pass);
619
+
620
+ final collections = <String>[];
621
+ if (collection != null && collection.isNotEmpty) {
622
+ collections.add(collection);
623
+ } else {
624
+ final all = await client.listCollections();
625
+ for (final c in all) {
626
+ final name = c['name'] as String? ?? '';
627
+ if (name.isNotEmpty && !name.startsWith('_') && c['system'] != true) {
628
+ collections.add(name);
629
+ }
630
+ }
631
+ if (collections.isEmpty) {
632
+ collections.addAll([
633
+ 'bundles',
634
+ 'channels',
635
+ 'audit_log',
636
+ 'bundles_patches',
637
+ ]);
581
638
  }
582
639
  }
583
- if (collections.isEmpty) {
584
- collections.addAll(['bundles', 'channels', 'audit_log', 'bundles_patches']);
585
- }
586
- }
587
640
 
588
- for (final c in collections) {
589
- try {
590
- final records = await client.exportCollection(c);
591
- final outFile = File(p.join(output, '$c.json'));
592
- await outFile.parent.create(recursive: true);
593
- await outFile.writeAsString(
594
- const JsonEncoder.withIndent(' ').convert(records),
595
- );
596
- step('${records.length} records → ${outFile.path}');
597
- } catch (e) {
598
- warn('Failed to export $c: $e');
641
+ for (final c in collections) {
642
+ try {
643
+ final records = await client.exportCollection(c);
644
+ final outFile = File(p.join(output, '$c.json'));
645
+ await outFile.parent.create(recursive: true);
646
+ await outFile.writeAsString(
647
+ const JsonEncoder.withIndent(' ').convert(records),
648
+ );
649
+ step('${records.length} records → ${outFile.path}');
650
+ } catch (e) {
651
+ warn('Failed to export $c: $e');
652
+ }
599
653
  }
654
+ } finally {
655
+ client.close();
600
656
  }
601
- client.close();
602
657
  });
603
658
  }
604
659
 
@@ -613,7 +668,8 @@ class PocketBaseImportCommand extends FlutterPatcherCommand {
613
668
  String get name => 'import';
614
669
 
615
670
  @override
616
- String get description => 'Import records from a JSON file into a collection.';
671
+ String get description =>
672
+ 'Import records from a JSON file into a collection.';
617
673
 
618
674
  @override
619
675
  Future<int> run() => runGuarded(() async {
@@ -674,10 +730,14 @@ class PocketBaseAdminCreateCommand extends FlutterPatcherCommand {
674
730
  final password = r['password'] as String;
675
731
  final result = await _pbStep(
676
732
  'Creating admin $email',
677
- (c) => c.createAdmin(email: email, password: password, passwordConfirm: password),
733
+ (c) => c.createAdmin(
734
+ email: email,
735
+ password: password,
736
+ passwordConfirm: password,
737
+ ),
678
738
  r,
679
739
  );
680
- box('admin', [kv('id', cyan(result['id'] ?? '?')), kv('email', email)]);
740
+ stdout.writeln(' ${dim('→')} admin ${cyan(result['id'] ?? '?')} $email');
681
741
  });
682
742
  }
683
743
 
@@ -695,13 +755,22 @@ class PocketBaseAdminListCommand extends FlutterPatcherCommand {
695
755
  @override
696
756
  Future<int> run() => runGuarded(() async {
697
757
  banner('pocketbase · admin · list');
698
- final admins = await _pbStep('Listing admins', (c) => c.listAdmins(), argResults!);
758
+ final admins = await _pbStep(
759
+ 'Listing admins',
760
+ (c) => c.listAdmins(),
761
+ argResults!,
762
+ );
699
763
  if (admins.isEmpty) {
700
764
  warn('No admins found.');
701
765
  return;
702
766
  }
703
767
  final rows = admins
704
- .map((a) => [cyan(a['id']?.toString() ?? '?'), a['email']?.toString() ?? '?'])
768
+ .map(
769
+ (a) => [
770
+ cyan(a['id']?.toString() ?? '?'),
771
+ a['email']?.toString() ?? '?',
772
+ ],
773
+ )
705
774
  .toList();
706
775
  table('${admins.length} admins', ['ID', 'EMAIL'], rows);
707
776
  });
@@ -711,7 +780,11 @@ class PocketBaseAdminUpdateCommand extends FlutterPatcherCommand {
711
780
  PocketBaseAdminUpdateCommand() {
712
781
  _addBackendOptions(argParser);
713
782
  argParser.addOption('id', help: 'Admin ID to update.', mandatory: true);
714
- argParser.addOption('body', help: 'JSON fields to update.', mandatory: true);
783
+ argParser.addOption(
784
+ 'body',
785
+ help: 'JSON fields to update.',
786
+ mandatory: true,
787
+ );
715
788
  }
716
789
 
717
790
  @override
@@ -804,15 +877,18 @@ class PocketBaseHealthCommand extends FlutterPatcherCommand {
804
877
  @override
805
878
  Future<int> run() => runGuarded(() async {
806
879
  banner('pocketbase · health');
807
- final health = await _pbStep('health', (c) => c.healthDetailed(), argResults!);
880
+ final health = await _pbStep(
881
+ 'health',
882
+ (c) => c.healthDetailed(),
883
+ argResults!,
884
+ );
808
885
  final code = health['code'] as int? ?? 0;
809
886
  final message = health['message'] as String? ?? '';
810
887
  final data = health['data'] as Map<String, dynamic>? ?? {};
811
- box('health', [
812
- kv('status', code == 200 ? green('healthy') : red('unhealthy')),
813
- kv('message', message),
814
- if (data.isNotEmpty) kv('canBackup', '${data['canBackup'] ?? false}'),
815
- ]);
888
+ final status = code == 200 ? green('healthy') : red('unhealthy');
889
+ step('status $status');
890
+ step('message $message');
891
+ step('canBackup ${data['canBackup'] ?? false}');
816
892
  });
817
893
  }
818
894
 
@@ -871,11 +947,15 @@ class PocketBaseLogsListCommand extends FlutterPatcherCommand {
871
947
  }
872
948
  final rows = items.map((log) {
873
949
  final level = log['level'] as int? ?? 0;
874
- final lvl = level == 0 ? green('INFO') : level == 1 ? yellow('WARN') : red('ERROR');
950
+ final lvl = level == 0
951
+ ? green('INFO')
952
+ : level == 1
953
+ ? yellow('WARN')
954
+ : red('ERROR');
875
955
  final msg = (log['message'] as String? ?? '').length > 60
876
956
  ? '${(log['message'] as String).substring(0, 57)}...'
877
957
  : log['message'] as String? ?? '';
878
- return [lvl, log['created']?.toString().substring(0, 19) ?? '', msg];
958
+ return [lvl, _safeSubstring(log['created']?.toString(), 19), msg];
879
959
  }).toList();
880
960
  table('$total logs', ['LEVEL', 'TIME', 'MESSAGE'], rows);
881
961
  });
@@ -905,10 +985,14 @@ class PocketBaseLogsStatsCommand extends FlutterPatcherCommand {
905
985
  warn('No stats available.');
906
986
  return;
907
987
  }
908
- final rows = stats.map((s) => [
909
- s['date']?.toString().substring(0, 16) ?? '',
910
- '${s['total'] ?? 0}',
911
- ]).toList();
988
+ final rows = stats
989
+ .map(
990
+ (s) => [
991
+ _safeSubstring(s['date']?.toString(), 16),
992
+ '${s['total'] ?? 0}',
993
+ ],
994
+ )
995
+ .toList();
912
996
  table('${stats.length} entries', ['TIME', 'COUNT'], rows);
913
997
  });
914
998
  }
@@ -956,7 +1040,11 @@ class PocketBaseRecordsCommand extends FlutterPatcherCommand {
956
1040
  class PocketBaseRecordsListCommand extends FlutterPatcherCommand {
957
1041
  PocketBaseRecordsListCommand() {
958
1042
  _addBackendOptions(argParser);
959
- argParser.addOption('collection', help: 'Collection name.', mandatory: true);
1043
+ argParser.addOption(
1044
+ 'collection',
1045
+ help: 'Collection name.',
1046
+ mandatory: true,
1047
+ );
960
1048
  argParser.addOption('filter', help: 'PB filter expression.');
961
1049
  argParser.addOption('sort', help: 'Sort field.');
962
1050
  argParser.addOption('page', help: 'Page number.', defaultsTo: '1');
@@ -991,16 +1079,25 @@ class PocketBaseRecordsListCommand extends FlutterPatcherCommand {
991
1079
  }
992
1080
  final rows = data.items.map((r) {
993
1081
  final m = r as Map<String, dynamic>;
994
- return [cyan(m['id']?.toString() ?? '?'), m.values.first?.toString() ?? ''];
1082
+ final label =
1083
+ m['name']?.toString() ??
1084
+ m['updated']?.toString() ??
1085
+ m['created']?.toString() ??
1086
+ '';
1087
+ return [cyan(m['id']?.toString() ?? '?'), label];
995
1088
  }).toList();
996
- table('${data.totalItems} records', ['ID', 'FIRST FIELD'], rows);
1089
+ table('${data.totalItems} records', ['ID', 'LABEL'], rows);
997
1090
  });
998
1091
  }
999
1092
 
1000
1093
  class PocketBaseRecordsGetCommand extends FlutterPatcherCommand {
1001
1094
  PocketBaseRecordsGetCommand() {
1002
1095
  _addBackendOptions(argParser);
1003
- argParser.addOption('collection', help: 'Collection name.', mandatory: true);
1096
+ argParser.addOption(
1097
+ 'collection',
1098
+ help: 'Collection name.',
1099
+ mandatory: true,
1100
+ );
1004
1101
  argParser.addOption('id', help: 'Record ID.', mandatory: true);
1005
1102
  }
1006
1103
 
@@ -1016,7 +1113,11 @@ class PocketBaseRecordsGetCommand extends FlutterPatcherCommand {
1016
1113
  final r = argResults!;
1017
1114
  final record = await _pbStep(
1018
1115
  'Getting ${r['collection']}/${r['id']}',
1019
- (c) => c.getRecord<dynamic>(r['collection'] as String, r['id'] as String, (j) => j),
1116
+ (c) => c.getRecord<dynamic>(
1117
+ r['collection'] as String,
1118
+ r['id'] as String,
1119
+ (j) => j,
1120
+ ),
1020
1121
  r,
1021
1122
  );
1022
1123
  if (record == null) {
@@ -1030,7 +1131,11 @@ class PocketBaseRecordsGetCommand extends FlutterPatcherCommand {
1030
1131
  class PocketBaseRecordsCreateCommand extends FlutterPatcherCommand {
1031
1132
  PocketBaseRecordsCreateCommand() {
1032
1133
  _addBackendOptions(argParser);
1033
- argParser.addOption('collection', help: 'Collection name.', mandatory: true);
1134
+ argParser.addOption(
1135
+ 'collection',
1136
+ help: 'Collection name.',
1137
+ mandatory: true,
1138
+ );
1034
1139
  argParser.addOption('body', help: 'JSON body.', mandatory: true);
1035
1140
  }
1036
1141
 
@@ -1050,14 +1155,20 @@ class PocketBaseRecordsCreateCommand extends FlutterPatcherCommand {
1050
1155
  (c) => c.createRecord<dynamic>(r['collection'] as String, body, (j) => j),
1051
1156
  r,
1052
1157
  );
1053
- box('record', [kv('id', cyan((record as Map)['id']?.toString() ?? '?'))]);
1158
+ stdout.writeln(
1159
+ ' ${dim('→')} record ${cyan((record as Map)['id']?.toString() ?? '?')}',
1160
+ );
1054
1161
  });
1055
1162
  }
1056
1163
 
1057
1164
  class PocketBaseRecordsUpdateCommand extends FlutterPatcherCommand {
1058
1165
  PocketBaseRecordsUpdateCommand() {
1059
1166
  _addBackendOptions(argParser);
1060
- argParser.addOption('collection', help: 'Collection name.', mandatory: true);
1167
+ argParser.addOption(
1168
+ 'collection',
1169
+ help: 'Collection name.',
1170
+ mandatory: true,
1171
+ );
1061
1172
  argParser.addOption('id', help: 'Record ID.', mandatory: true);
1062
1173
  argParser.addOption('body', help: 'JSON body.', mandatory: true);
1063
1174
  }
@@ -1075,7 +1186,12 @@ class PocketBaseRecordsUpdateCommand extends FlutterPatcherCommand {
1075
1186
  final body = jsonDecode(r['body'] as String) as Map<String, dynamic>;
1076
1187
  await _pbStep(
1077
1188
  'Updating ${r['collection']}/${r['id']}',
1078
- (c) => c.updateRecord<dynamic>(r['collection'] as String, r['id'] as String, body, (j) => j),
1189
+ (c) => c.updateRecord<dynamic>(
1190
+ r['collection'] as String,
1191
+ r['id'] as String,
1192
+ body,
1193
+ (j) => j,
1194
+ ),
1079
1195
  r,
1080
1196
  );
1081
1197
  step(green('Updated ${r['collection']}/${r['id']}'));
@@ -1085,7 +1201,11 @@ class PocketBaseRecordsUpdateCommand extends FlutterPatcherCommand {
1085
1201
  class PocketBaseRecordsDeleteCommand extends FlutterPatcherCommand {
1086
1202
  PocketBaseRecordsDeleteCommand() {
1087
1203
  _addBackendOptions(argParser);
1088
- argParser.addOption('collection', help: 'Collection name.', mandatory: true);
1204
+ argParser.addOption(
1205
+ 'collection',
1206
+ help: 'Collection name.',
1207
+ mandatory: true,
1208
+ );
1089
1209
  argParser.addOption('id', help: 'Record ID.', mandatory: true);
1090
1210
  }
1091
1211
 
@@ -1111,19 +1231,25 @@ class PocketBaseRecordsDeleteCommand extends FlutterPatcherCommand {
1111
1231
  class PocketBaseRecordsBatchCommand extends FlutterPatcherCommand {
1112
1232
  PocketBaseRecordsBatchCommand() {
1113
1233
  _addBackendOptions(argParser);
1114
- argParser.addOption('requests', help: 'JSON array of request objects.', mandatory: true);
1234
+ argParser.addOption(
1235
+ 'requests',
1236
+ help: 'JSON array of request objects.',
1237
+ mandatory: true,
1238
+ );
1115
1239
  }
1116
1240
 
1117
1241
  @override
1118
1242
  String get name => 'batch';
1119
1243
 
1120
1244
  @override
1121
- String get description => 'Execute multiple record operations in a single transaction.';
1245
+ String get description =>
1246
+ 'Execute multiple record operations in a single transaction.';
1122
1247
 
1123
1248
  @override
1124
1249
  Future<int> run() => runGuarded(() async {
1125
1250
  banner('pocketbase · records · batch');
1126
- final requests = jsonDecode(argResults!['requests'] as String) as List<dynamic>;
1251
+ final requests =
1252
+ jsonDecode(argResults!['requests'] as String) as List<dynamic>;
1127
1253
  final body = requests.cast<Map<String, dynamic>>();
1128
1254
  final results = await _pbStep(
1129
1255
  'Executing ${body.length} batch requests',
@@ -1171,16 +1297,24 @@ class PocketBaseCollectionsListCommand extends FlutterPatcherCommand {
1171
1297
  @override
1172
1298
  Future<int> run() => runGuarded(() async {
1173
1299
  banner('pocketbase · collections · list');
1174
- final cols = await _pbStep('Listing collections', (c) => c.listCollections(), argResults!);
1300
+ final cols = await _pbStep(
1301
+ 'Listing collections',
1302
+ (c) => c.listCollections(),
1303
+ argResults!,
1304
+ );
1175
1305
  if (cols.isEmpty) {
1176
1306
  warn('No collections found.');
1177
1307
  return;
1178
1308
  }
1179
- final rows = cols.map((c) => [
1180
- cyan(c['name']?.toString() ?? '?'),
1181
- c['type']?.toString() ?? '?',
1182
- '${(c['fields'] as List?)?.length ?? 0} fields',
1183
- ]).toList();
1309
+ final rows = cols
1310
+ .map(
1311
+ (c) => [
1312
+ cyan(c['name']?.toString() ?? '?'),
1313
+ c['type']?.toString() ?? '?',
1314
+ '${(c['fields'] as List?)?.length ?? 0} fields',
1315
+ ],
1316
+ )
1317
+ .toList();
1184
1318
  table('${cols.length} collections', ['NAME', 'TYPE', 'FIELDS'], rows);
1185
1319
  });
1186
1320
  }
@@ -1188,7 +1322,11 @@ class PocketBaseCollectionsListCommand extends FlutterPatcherCommand {
1188
1322
  class PocketBaseCollectionsViewCommand extends FlutterPatcherCommand {
1189
1323
  PocketBaseCollectionsViewCommand() {
1190
1324
  _addBackendOptions(argParser);
1191
- argParser.addOption('name', help: 'Collection name or ID.', mandatory: true);
1325
+ argParser.addOption(
1326
+ 'name',
1327
+ help: 'Collection name or ID.',
1328
+ mandatory: true,
1329
+ );
1192
1330
  }
1193
1331
 
1194
1332
  @override
@@ -1201,7 +1339,11 @@ class PocketBaseCollectionsViewCommand extends FlutterPatcherCommand {
1201
1339
  Future<int> run() => runGuarded(() async {
1202
1340
  banner('pocketbase · collections · view');
1203
1341
  final name = argResults!['name'] as String;
1204
- final col = await _pbStep('Viewing $name', (c) => c.getCollection(name), argResults!);
1342
+ final col = await _pbStep(
1343
+ 'Viewing $name',
1344
+ (c) => c.getCollection(name),
1345
+ argResults!,
1346
+ );
1205
1347
  stdout.writeln(const JsonEncoder.withIndent(' ').convert(col));
1206
1348
  });
1207
1349
  }
@@ -1209,7 +1351,11 @@ class PocketBaseCollectionsViewCommand extends FlutterPatcherCommand {
1209
1351
  class PocketBaseCollectionsCreateCommand extends FlutterPatcherCommand {
1210
1352
  PocketBaseCollectionsCreateCommand() {
1211
1353
  _addBackendOptions(argParser);
1212
- argParser.addOption('body', help: 'JSON collection definition.', mandatory: true);
1354
+ argParser.addOption(
1355
+ 'body',
1356
+ help: 'JSON collection definition.',
1357
+ mandatory: true,
1358
+ );
1213
1359
  }
1214
1360
 
1215
1361
  @override
@@ -1221,9 +1367,13 @@ class PocketBaseCollectionsCreateCommand extends FlutterPatcherCommand {
1221
1367
  @override
1222
1368
  Future<int> run() => runGuarded(() async {
1223
1369
  banner('pocketbase · collections · create');
1224
- final body = jsonDecode(argResults!['body'] as String) as Map<String, dynamic>;
1225
- await _pbStep('Creating ${body['name'] ?? 'collection'}',
1226
- (c) => c.createCollection(body), argResults!);
1370
+ final body =
1371
+ jsonDecode(argResults!['body'] as String) as Map<String, dynamic>;
1372
+ await _pbStep(
1373
+ 'Creating ${body['name'] ?? 'collection'}',
1374
+ (c) => c.createCollection(body),
1375
+ argResults!,
1376
+ );
1227
1377
  step(green('Created ${body['name'] ?? 'collection'}'));
1228
1378
  });
1229
1379
  }
@@ -1231,8 +1381,16 @@ class PocketBaseCollectionsCreateCommand extends FlutterPatcherCommand {
1231
1381
  class PocketBaseCollectionsUpdateCommand extends FlutterPatcherCommand {
1232
1382
  PocketBaseCollectionsUpdateCommand() {
1233
1383
  _addBackendOptions(argParser);
1234
- argParser.addOption('name', help: 'Collection name or ID.', mandatory: true);
1235
- argParser.addOption('body', help: 'JSON fields to update.', mandatory: true);
1384
+ argParser.addOption(
1385
+ 'name',
1386
+ help: 'Collection name or ID.',
1387
+ mandatory: true,
1388
+ );
1389
+ argParser.addOption(
1390
+ 'body',
1391
+ help: 'JSON fields to update.',
1392
+ mandatory: true,
1393
+ );
1236
1394
  }
1237
1395
 
1238
1396
  @override
@@ -1255,7 +1413,11 @@ class PocketBaseCollectionsUpdateCommand extends FlutterPatcherCommand {
1255
1413
  class PocketBaseCollectionsDeleteCommand extends FlutterPatcherCommand {
1256
1414
  PocketBaseCollectionsDeleteCommand() {
1257
1415
  _addBackendOptions(argParser);
1258
- argParser.addOption('name', help: 'Collection name or ID.', mandatory: true);
1416
+ argParser.addOption(
1417
+ 'name',
1418
+ help: 'Collection name or ID.',
1419
+ mandatory: true,
1420
+ );
1259
1421
  }
1260
1422
 
1261
1423
  @override
@@ -1268,7 +1430,11 @@ class PocketBaseCollectionsDeleteCommand extends FlutterPatcherCommand {
1268
1430
  Future<int> run() => runGuarded(() async {
1269
1431
  banner('pocketbase · collections · delete');
1270
1432
  final name = argResults!['name'] as String;
1271
- await _pbStep('Deleting $name', (c) => c.deleteCollection(name), argResults!);
1433
+ await _pbStep(
1434
+ 'Deleting $name',
1435
+ (c) => c.deleteCollection(name),
1436
+ argResults!,
1437
+ );
1272
1438
  step(green('Deleted collection $name'));
1273
1439
  });
1274
1440
  }
@@ -1276,7 +1442,11 @@ class PocketBaseCollectionsDeleteCommand extends FlutterPatcherCommand {
1276
1442
  class PocketBaseCollectionsTruncateCommand extends FlutterPatcherCommand {
1277
1443
  PocketBaseCollectionsTruncateCommand() {
1278
1444
  _addBackendOptions(argParser);
1279
- argParser.addOption('name', help: 'Collection name or ID.', mandatory: true);
1445
+ argParser.addOption(
1446
+ 'name',
1447
+ help: 'Collection name or ID.',
1448
+ mandatory: true,
1449
+ );
1280
1450
  }
1281
1451
 
1282
1452
  @override
@@ -1289,7 +1459,11 @@ class PocketBaseCollectionsTruncateCommand extends FlutterPatcherCommand {
1289
1459
  Future<int> run() => runGuarded(() async {
1290
1460
  banner('pocketbase · collections · truncate');
1291
1461
  final name = argResults!['name'] as String;
1292
- await _pbStep('Truncating $name', (c) => c.truncateCollection(name), argResults!);
1462
+ await _pbStep(
1463
+ 'Truncating $name',
1464
+ (c) => c.truncateCollection(name),
1465
+ argResults!,
1466
+ );
1293
1467
  step(green('Truncated $name'));
1294
1468
  });
1295
1469
  }
@@ -1297,8 +1471,16 @@ class PocketBaseCollectionsTruncateCommand extends FlutterPatcherCommand {
1297
1471
  class PocketBaseCollectionsImportCommand extends FlutterPatcherCommand {
1298
1472
  PocketBaseCollectionsImportCommand() {
1299
1473
  _addBackendOptions(argParser);
1300
- argParser.addOption('file', help: 'JSON file with collections config.', mandatory: true);
1301
- argParser.addFlag('delete-missing', help: 'Delete collections not in file.', defaultsTo: false);
1474
+ argParser.addOption(
1475
+ 'file',
1476
+ help: 'JSON file with collections config.',
1477
+ mandatory: true,
1478
+ );
1479
+ argParser.addFlag(
1480
+ 'delete-missing',
1481
+ help: 'Delete collections not in file.',
1482
+ defaultsTo: false,
1483
+ );
1302
1484
  }
1303
1485
 
1304
1486
  @override
@@ -1318,7 +1500,8 @@ class PocketBaseCollectionsImportCommand extends FlutterPatcherCommand {
1318
1500
  final cols = json.cast<Map<String, dynamic>>();
1319
1501
  await _pbStep(
1320
1502
  'Importing ${cols.length} collections',
1321
- (c) => c.importCollections(cols, deleteMissing: r['delete-missing'] as bool),
1503
+ (c) =>
1504
+ c.importCollections(cols, deleteMissing: r['delete-missing'] as bool),
1322
1505
  r,
1323
1506
  );
1324
1507
  step(green('Imported ${cols.length} collections'));
@@ -1358,7 +1541,11 @@ class PocketBaseSettingsListCommand extends FlutterPatcherCommand {
1358
1541
  @override
1359
1542
  Future<int> run() => runGuarded(() async {
1360
1543
  banner('pocketbase · settings · list');
1361
- final settings = await _pbStep('Fetching settings', (c) => c.listSettings(), argResults!);
1544
+ final settings = await _pbStep(
1545
+ 'Fetching settings',
1546
+ (c) => c.listSettings(),
1547
+ argResults!,
1548
+ );
1362
1549
  stdout.writeln(const JsonEncoder.withIndent(' ').convert(settings));
1363
1550
  });
1364
1551
  }
@@ -1366,7 +1553,11 @@ class PocketBaseSettingsListCommand extends FlutterPatcherCommand {
1366
1553
  class PocketBaseSettingsUpdateCommand extends FlutterPatcherCommand {
1367
1554
  PocketBaseSettingsUpdateCommand() {
1368
1555
  _addBackendOptions(argParser);
1369
- argParser.addOption('body', help: 'JSON object with settings to update.', mandatory: true);
1556
+ argParser.addOption(
1557
+ 'body',
1558
+ help: 'JSON object with settings to update.',
1559
+ mandatory: true,
1560
+ );
1370
1561
  }
1371
1562
 
1372
1563
  @override
@@ -1378,8 +1569,13 @@ class PocketBaseSettingsUpdateCommand extends FlutterPatcherCommand {
1378
1569
  @override
1379
1570
  Future<int> run() => runGuarded(() async {
1380
1571
  banner('pocketbase · settings · update');
1381
- final body = jsonDecode(argResults!['body'] as String) as Map<String, dynamic>;
1382
- await _pbStep('Updating settings', (c) => c.updateSettings(body), argResults!);
1572
+ final body =
1573
+ jsonDecode(argResults!['body'] as String) as Map<String, dynamic>;
1574
+ await _pbStep(
1575
+ 'Updating settings',
1576
+ (c) => c.updateSettings(body),
1577
+ argResults!,
1578
+ );
1383
1579
  step(green('Settings updated'));
1384
1580
  });
1385
1581
  }
@@ -1387,7 +1583,11 @@ class PocketBaseSettingsUpdateCommand extends FlutterPatcherCommand {
1387
1583
  class PocketBaseSettingsTestS3Command extends FlutterPatcherCommand {
1388
1584
  PocketBaseSettingsTestS3Command() {
1389
1585
  _addBackendOptions(argParser);
1390
- argParser.addOption('filesystem', help: 'Storage filesystem to test.', defaultsTo: 'storage');
1586
+ argParser.addOption(
1587
+ 'filesystem',
1588
+ help: 'Storage filesystem to test.',
1589
+ defaultsTo: 'storage',
1590
+ );
1391
1591
  }
1392
1592
 
1393
1593
  @override
@@ -1443,7 +1643,11 @@ class PocketBaseSettingsTestEmailCommand extends FlutterPatcherCommand {
1443
1643
  class PocketBaseSqlCommand extends FlutterPatcherCommand {
1444
1644
  PocketBaseSqlCommand() {
1445
1645
  _addBackendOptions(argParser);
1446
- argParser.addOption('query', help: 'SQL query to execute.', mandatory: true);
1646
+ argParser.addOption(
1647
+ 'query',
1648
+ help: 'SQL query to execute.',
1649
+ mandatory: true,
1650
+ );
1447
1651
  }
1448
1652
 
1449
1653
  @override
@@ -1456,8 +1660,13 @@ class PocketBaseSqlCommand extends FlutterPatcherCommand {
1456
1660
  Future<int> run() => runGuarded(() async {
1457
1661
  banner('pocketbase · sql');
1458
1662
  final query = argResults!['query'] as String;
1459
- final result = await _pbStep('Executing query', (c) => c.runSql(query), argResults!);
1460
- final columns = (result['columns'] as List? ?? []).cast<Map<String, dynamic>>();
1663
+ final result = await _pbStep(
1664
+ 'Executing query',
1665
+ (c) => c.runSql(query),
1666
+ argResults!,
1667
+ );
1668
+ final columns = (result['columns'] as List? ?? [])
1669
+ .cast<Map<String, dynamic>>();
1461
1670
  final rows = (result['rows'] as List? ?? []).cast<List<dynamic>>();
1462
1671
  if (rows.isNotEmpty) {
1463
1672
  final colNames = columns.map((c) => '${c['name']}').toList();
@@ -1500,15 +1709,23 @@ class PocketBaseCronsListCommand extends FlutterPatcherCommand {
1500
1709
  @override
1501
1710
  Future<int> run() => runGuarded(() async {
1502
1711
  banner('pocketbase · crons · list');
1503
- final crons = await _pbStep('Fetching cron jobs', (c) => c.listCrons(), argResults!);
1712
+ final crons = await _pbStep(
1713
+ 'Fetching cron jobs',
1714
+ (c) => c.listCrons(),
1715
+ argResults!,
1716
+ );
1504
1717
  if (crons.isEmpty) {
1505
1718
  warn('No cron jobs registered.');
1506
1719
  return;
1507
1720
  }
1508
- final rows = crons.map((c) => [
1509
- cyan(c['id']?.toString() ?? '?'),
1510
- c['expression']?.toString() ?? '?',
1511
- ]).toList();
1721
+ final rows = crons
1722
+ .map(
1723
+ (c) => [
1724
+ cyan(c['id']?.toString() ?? '?'),
1725
+ c['expression']?.toString() ?? '?',
1726
+ ],
1727
+ )
1728
+ .toList();
1512
1729
  table('${crons.length} cron jobs', ['ID', 'EXPRESSION'], rows);
1513
1730
  });
1514
1731
  }
@@ -1563,11 +1780,17 @@ class PocketBaseQueryCommand extends FlutterPatcherCommand {
1563
1780
  if (bodyStr != null && bodyStr.isNotEmpty) {
1564
1781
  body = jsonDecode(bodyStr) as Map<String, dynamic>;
1565
1782
  }
1566
- final client = PocketBaseClient(_resolveBackend(r).$1);
1567
- await client.authenticate(_resolveBackend(r).$2, _resolveBackend(r).$3);
1568
- final res = await client.rawRequest(method, path, body: body);
1569
- client.close();
1570
- stdout.writeln(const JsonEncoder.withIndent(' ').convert(jsonDecode(res.body)));
1783
+ final (url, admin, pass) = _resolveBackend(r);
1784
+ final client = PocketBaseClient(url);
1785
+ try {
1786
+ await client.authenticate(admin, pass);
1787
+ final res = await client.rawRequest(method, path, body: body);
1788
+ stdout.writeln(
1789
+ const JsonEncoder.withIndent(' ').convert(jsonDecode(res.body)),
1790
+ );
1791
+ } finally {
1792
+ client.close();
1793
+ }
1571
1794
  });
1572
1795
  }
1573
1796
 
@@ -1596,7 +1819,10 @@ class PocketBaseConfigCommand extends FlutterPatcherCommand {
1596
1819
  kv('url', cyan(url)),
1597
1820
  kv('admin', admin),
1598
1821
  kv('version', paths.version),
1599
- kv('binary', exists ? green(paths.binaryPath.path) : red('not installed')),
1822
+ kv(
1823
+ 'binary',
1824
+ exists ? green(paths.binaryPath.path) : red('not installed'),
1825
+ ),
1600
1826
  ]);
1601
1827
  });
1602
1828
  }