@_nazmiforreal/flutter-ota 0.1.12 → 0.1.14

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 (177) hide show
  1. package/README.md +1 -1
  2. package/bin/flutter-ota-linux-x64 +0 -0
  3. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
  4. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
  5. package/dart-src/packages/cli-tools/README.md +1 -1
  6. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
  7. package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
  8. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
  9. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
  10. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
  11. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
  12. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
  13. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
  14. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
  15. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
  16. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
  17. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +290 -181
  18. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
  19. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
  20. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
  21. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
  22. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
  23. package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
  24. package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
  25. package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
  26. package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
  27. package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
  28. package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
  29. package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
  30. package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
  31. package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
  32. package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
  33. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
  34. package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
  35. package/dart-src/packages/cli-tools/pubspec.lock +69 -116
  36. package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
  37. package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
  38. package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
  39. package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
  40. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
  41. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
  42. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
  43. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
  44. package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
  45. package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
  46. package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
  47. package/dart-src/packages/core/CHANGELOG.md +9 -0
  48. package/dart-src/packages/core/README.md +2 -2
  49. package/dart-src/packages/core/example/lib/main.dart +69 -0
  50. package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
  51. package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
  52. package/dart-src/packages/core/lib/src/bundle.dart +102 -94
  53. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
  54. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
  55. package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
  56. package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
  57. package/dart-src/packages/core/lib/src/metadata.dart +9 -7
  58. package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
  59. package/dart-src/packages/core/lib/src/platform.dart +4 -3
  60. package/dart-src/packages/core/lib/src/rollout.dart +14 -11
  61. package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
  62. package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
  63. package/dart-src/packages/core/lib/src/status.dart +4 -3
  64. package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
  65. package/dart-src/packages/core/lib/src/uuid.dart +1 -2
  66. package/dart-src/packages/core/pubspec.yaml +6 -7
  67. package/dart-src/packages/core/test/rollout_test.dart +15 -8
  68. package/dart-src/packages/core/test/semver_test.dart +5 -2
  69. package/dart-src/packages/core/test/types_test.dart +26 -24
  70. package/dart-src/packages/core/test/uuid_test.dart +3 -1
  71. package/dart-src/packages/core/tool/dbg.dart +1 -0
  72. package/dart-src/plugins/aws/CHANGELOG.md +15 -0
  73. package/dart-src/plugins/aws/example/lib/main.dart +60 -0
  74. package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
  75. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
  76. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
  77. package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
  78. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
  79. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
  80. package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
  81. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
  82. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
  83. package/dart-src/plugins/aws/pubspec.yaml +15 -10
  84. package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
  85. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
  86. package/dart-src/plugins/cloudflare/CHANGELOG.md +15 -0
  87. package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
  88. package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
  89. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
  90. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
  91. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
  92. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
  93. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
  94. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
  95. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
  96. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
  97. package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
  98. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
  99. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
  100. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
  101. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
  102. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
  103. package/dart-src/plugins/plugin-core/CHANGELOG.md +15 -1
  104. package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
  105. package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
  106. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
  107. package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
  108. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
  109. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
  110. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
  111. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
  112. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
  113. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
  114. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
  115. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
  116. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
  117. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
  118. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
  119. package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
  120. package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
  121. package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
  122. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
  123. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
  124. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
  125. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
  126. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
  127. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
  128. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
  129. package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
  130. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
  131. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
  132. package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
  133. package/dart-src/plugins/pocketbase/LICENSE +21 -0
  134. package/dart-src/plugins/pocketbase/README.md +54 -0
  135. package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
  136. package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
  137. package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
  138. package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
  139. package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
  140. package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
  141. package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
  142. package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
  143. package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
  144. package/dart-src/plugins/postgres/CHANGELOG.md +16 -0
  145. package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
  146. package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
  147. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
  148. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
  149. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
  150. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
  151. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
  152. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
  153. package/dart-src/plugins/postgres/pubspec.yaml +14 -9
  154. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
  155. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
  156. package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
  157. package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
  158. package/dart-src/plugins/supabase/CHANGELOG.md +20 -0
  159. package/dart-src/plugins/supabase/README.md +2 -2
  160. package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
  161. package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
  162. package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
  163. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
  164. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
  165. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
  166. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
  167. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
  168. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
  169. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
  170. package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
  171. package/dart-src/plugins/supabase/pubspec.yaml +14 -9
  172. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
  173. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
  174. package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
  175. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
  176. package/package.json +3 -3
  177. package/scripts/vendor.sh +1 -1
@@ -9,9 +9,15 @@ class ChannelCommand extends FlutterPatcherCommand {
9
9
  final Backend? backendOverride;
10
10
 
11
11
  ChannelCommand({this.config, this.backendOverride}) {
12
- addSubcommand(ChannelListCommand(config: config, backendOverride: backendOverride));
13
- addSubcommand(ChannelGetCommand(config: config, backendOverride: backendOverride));
14
- addSubcommand(ChannelSetCommand(config: config, backendOverride: backendOverride));
12
+ addSubcommand(
13
+ ChannelListCommand(config: config, backendOverride: backendOverride),
14
+ );
15
+ addSubcommand(
16
+ ChannelGetCommand(config: config, backendOverride: backendOverride),
17
+ );
18
+ addSubcommand(
19
+ ChannelSetCommand(config: config, backendOverride: backendOverride),
20
+ );
15
21
  }
16
22
 
17
23
  @override
@@ -22,13 +28,13 @@ class ChannelCommand extends FlutterPatcherCommand {
22
28
 
23
29
  @override
24
30
  Future<int> run() => runGuarded(() async {
25
- print(description);
26
- print('');
27
- print('Subcommands:');
28
- print(' list List channels');
29
- print(' get <channel> Show the live bundle for a channel');
30
- print(' set <c> <id> Promote a bundle to a channel');
31
- });
31
+ print(description);
32
+ print('');
33
+ print('Subcommands:');
34
+ print(' list List channels');
35
+ print(' get <channel> Show the live bundle for a channel');
36
+ print(' set <c> <id> Promote a bundle to a channel');
37
+ });
32
38
  }
33
39
 
34
40
  class ChannelListCommand extends FlutterPatcherCommand {
@@ -44,21 +50,21 @@ class ChannelListCommand extends FlutterPatcherCommand {
44
50
  String get description => 'List channels.';
45
51
 
46
52
  @override
47
- ArgParser get argParser => ArgParser()
48
- ..addOption('backend', abbr: 'b', help: 'Backend provider.');
53
+ ArgParser get argParser =>
54
+ ArgParser()..addOption('backend', abbr: 'b', help: 'Backend provider.');
49
55
 
50
56
  @override
51
57
  Future<int> run() => runGuarded(() async {
52
- final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
53
- final backend = requireBackend(cfg, override: backendOverride);
54
- banner('channel · list');
55
- final channels = await listChannels(backend);
56
- if (channels.isEmpty) {
57
- step('(no channels)');
58
- return;
59
- }
60
- box('${channels.length} channels', channels.map((c) => ' $c').toList());
61
- });
58
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
59
+ final backend = requireBackend(cfg, override: backendOverride);
60
+ banner('channel · list');
61
+ final channels = await listChannels(backend);
62
+ if (channels.isEmpty) {
63
+ step('(no channels)');
64
+ return;
65
+ }
66
+ box('${channels.length} channels', channels.map((c) => ' $c').toList());
67
+ });
62
68
  }
63
69
 
64
70
  class ChannelGetCommand extends FlutterPatcherCommand {
@@ -80,26 +86,32 @@ class ChannelGetCommand extends FlutterPatcherCommand {
80
86
 
81
87
  @override
82
88
  Future<int> run() => runGuarded(() async {
83
- final channel = argResults!['channel'] as String?;
84
- if (channel == null || channel.isEmpty) {
85
- throw PackException('Usage: flutter_ota_kit channel get --channel <channel>', 64);
86
- }
87
- final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
88
- final backend = requireBackend(cfg, override: backendOverride);
89
- banner('channel · get');
90
- final bundle = await getChannel(backend, channel);
91
- if (bundle == null) {
92
- err('no live bundle on channel "$channel"');
93
- return;
94
- }
95
- box('channel "$channel"', [
96
- kv('live bundle', cyan(bundle.id)),
97
- kv('platform', bundle.platform.value),
98
- kv('enabled', bundle.enabled ? green('yes') : yellow('no')),
99
- kv('target', bundle.targetAppVersion ?? bundle.fingerprintHash ?? dim('-')),
100
- if (bundle.message != null) kv('message', bundle.message!),
101
- ]);
102
- });
89
+ final channel = argResults!['channel'] as String?;
90
+ if (channel == null || channel.isEmpty) {
91
+ throw PackException(
92
+ 'Usage: flutter_ota_kit channel get --channel <channel>',
93
+ 64,
94
+ );
95
+ }
96
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
97
+ final backend = requireBackend(cfg, override: backendOverride);
98
+ banner('channel · get');
99
+ final bundle = await getChannel(backend, channel);
100
+ if (bundle == null) {
101
+ err('no live bundle on channel "$channel"');
102
+ return;
103
+ }
104
+ box('channel "$channel"', [
105
+ kv('live bundle', cyan(bundle.id)),
106
+ kv('platform', bundle.platform.value),
107
+ kv('enabled', bundle.enabled ? green('yes') : yellow('no')),
108
+ kv(
109
+ 'target',
110
+ bundle.targetAppVersion ?? bundle.fingerprintHash ?? dim('-'),
111
+ ),
112
+ if (bundle.message != null) kv('message', bundle.message!),
113
+ ]);
114
+ });
103
115
  }
104
116
 
105
117
  class ChannelSetCommand extends FlutterPatcherCommand {
@@ -122,28 +134,25 @@ class ChannelSetCommand extends FlutterPatcherCommand {
122
134
 
123
135
  @override
124
136
  Future<int> run() => runGuarded(() async {
125
- final channel = argResults!['channel'] as String?;
126
- final bundleId = argResults!['bundle-id'] as String?;
127
- if (channel == null ||
128
- channel.isEmpty ||
129
- bundleId == null ||
130
- bundleId.isEmpty) {
131
- throw PackException(
132
- 'Usage: flutter_ota_kit channel set --channel <c> --bundle-id <id>',
133
- 64,
134
- );
135
- }
136
- final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
137
- final backend = requireBackend(cfg, override: backendOverride);
138
- banner('channel · set');
139
- await spinner(
140
- () => promoteBundle(backend, bundleId, channel),
141
- 'Promoting $bundleId to $channel',
142
- done: 'Channel set',
143
- );
144
- box('channel set', [
145
- kv('channel', channel),
146
- kv('bundle', cyan(bundleId)),
147
- ]);
148
- });
137
+ final channel = argResults!['channel'] as String?;
138
+ final bundleId = argResults!['bundle-id'] as String?;
139
+ if (channel == null ||
140
+ channel.isEmpty ||
141
+ bundleId == null ||
142
+ bundleId.isEmpty) {
143
+ throw PackException(
144
+ 'Usage: flutter_ota_kit channel set --channel <c> --bundle-id <id>',
145
+ 64,
146
+ );
147
+ }
148
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
149
+ final backend = requireBackend(cfg, override: backendOverride);
150
+ banner('channel · set');
151
+ await spinner(
152
+ () => promoteBundle(backend, bundleId, channel),
153
+ 'Promoting $bundleId to $channel',
154
+ done: 'Channel set',
155
+ );
156
+ box('channel set', [kv('channel', channel), kv('bundle', cyan(bundleId))]);
157
+ });
149
158
  }
@@ -22,10 +22,14 @@ class ConfigCommand extends FlutterPatcherCommand {
22
22
 
23
23
  @override
24
24
  Future<int> run() => runGuarded(() async {
25
- // No subcommand selected.
26
- print(description);
27
- print(''); print('Subcommands:'); print(' get <key>'); print(' set <key> <value>'); print(' list');
28
- });
25
+ // No subcommand selected.
26
+ print(description);
27
+ print('');
28
+ print('Subcommands:');
29
+ print(' get <key>');
30
+ print(' set <key> <value>');
31
+ print(' list');
32
+ });
29
33
  }
30
34
 
31
35
  Map<String, dynamic> _loadProjectJson() {
@@ -39,9 +43,7 @@ Map<String, dynamic> _loadProjectJson() {
39
43
  void _saveProjectJson(Map<String, dynamic> json) {
40
44
  final file = configCandidates().first;
41
45
  file.parent.createSync(recursive: true);
42
- file.writeAsStringSync(
43
- const JsonEncoder.withIndent(' ').convert(json),
44
- );
46
+ file.writeAsStringSync(const JsonEncoder.withIndent(' ').convert(json));
45
47
  }
46
48
 
47
49
  class ConfigGetCommand extends FlutterPatcherCommand {
@@ -49,26 +51,28 @@ class ConfigGetCommand extends FlutterPatcherCommand {
49
51
  String get name => 'get';
50
52
 
51
53
  @override
52
- String get description => 'Print a config value by dot-path (e.g. supabase.url).';
54
+ String get description =>
55
+ 'Print a config value by dot-path (e.g. supabase.url).';
53
56
 
54
57
  @override
55
- ArgParser get argParser => ArgParser()..addOption('key', abbr: 'k', help: 'Dot-path key.');
58
+ ArgParser get argParser =>
59
+ ArgParser()..addOption('key', abbr: 'k', help: 'Dot-path key.');
56
60
 
57
61
  @override
58
62
  Future<int> run() => runGuarded(() async {
59
- final key = argResults!['key'] as String?;
60
- if (key == null || key.isEmpty) {
61
- throw StateError('Usage: flutter_ota_kit config get <key>');
62
- }
63
- final value = readPath(_loadProjectJson(), key);
64
- if (value == null) {
65
- stderr.writeln('(not set)');
66
- return;
67
- }
68
- stdout.writeln(
69
- value is String ? value : const JsonEncoder().convert(value),
70
- );
71
- });
63
+ final key = argResults!['key'] as String?;
64
+ if (key == null || key.isEmpty) {
65
+ throw StateError('Usage: flutter_ota_kit config get <key>');
66
+ }
67
+ final value = readPath(_loadProjectJson(), key);
68
+ if (value == null) {
69
+ stderr.writeln('(not set)');
70
+ return;
71
+ }
72
+ stdout.writeln(
73
+ value is String ? value : const JsonEncoder().convert(value),
74
+ );
75
+ });
72
76
  }
73
77
 
74
78
  class ConfigSetCommand extends FlutterPatcherCommand {
@@ -85,16 +89,16 @@ class ConfigSetCommand extends FlutterPatcherCommand {
85
89
 
86
90
  @override
87
91
  Future<int> run() => runGuarded(() async {
88
- final key = argResults!['key'] as String?;
89
- final value = argResults!['value'] as String?;
90
- if (key == null || key.isEmpty || value == null) {
91
- throw StateError('Usage: flutter_ota_kit config set <key> <value>');
92
- }
93
- final json = _loadProjectJson();
94
- writePath(json, key, value);
95
- _saveProjectJson(json);
96
- stdout.writeln('Set $key = $value');
97
- });
92
+ final key = argResults!['key'] as String?;
93
+ final value = argResults!['value'] as String?;
94
+ if (key == null || key.isEmpty || value == null) {
95
+ throw StateError('Usage: flutter_ota_kit config set <key> <value>');
96
+ }
97
+ final json = _loadProjectJson();
98
+ writePath(json, key, value);
99
+ _saveProjectJson(json);
100
+ stdout.writeln('Set $key = $value');
101
+ });
98
102
  }
99
103
 
100
104
  class ConfigListCommand extends FlutterPatcherCommand {
@@ -106,7 +110,7 @@ class ConfigListCommand extends FlutterPatcherCommand {
106
110
 
107
111
  @override
108
112
  Future<int> run() => runGuarded(() async {
109
- final json = _loadProjectJson();
110
- stdout.writeln(const JsonEncoder.withIndent(' ').convert(json));
111
- });
113
+ final json = _loadProjectJson();
114
+ stdout.writeln(const JsonEncoder.withIndent(' ').convert(json));
115
+ });
112
116
  }
@@ -16,8 +16,11 @@ class ConsoleCommand extends FlutterPatcherCommand {
16
16
 
17
17
  @override
18
18
  ArgParser get argParser => ArgParser()
19
- ..addFlag('open',
20
- help: 'Launch the console with `flutter run -d chrome` (requires Flutter).');
19
+ ..addFlag(
20
+ 'open',
21
+ help:
22
+ 'Launch the console with `flutter run -d chrome` (requires Flutter).',
23
+ );
21
24
 
22
25
  Directory? _findConsoleDir() {
23
26
  var dir = Directory(p.dirname(Platform.script.path));
@@ -31,29 +34,29 @@ class ConsoleCommand extends FlutterPatcherCommand {
31
34
 
32
35
  @override
33
36
  Future<int> run() => runGuarded(() async {
34
- final consoleDir = _findConsoleDir();
35
- if (consoleDir == null) {
36
- err('Console package not found (expected packages/console).');
37
- return;
38
- }
39
- if (argResults!['open'] as bool) {
40
- step('Launching console...');
41
- final process = await Process.start(
42
- 'flutter',
43
- ['run', '-d', 'chrome'],
44
- workingDirectory: consoleDir.path,
45
- runInShell: true,
46
- mode: ProcessStartMode.inheritStdio,
47
- );
48
- exitCode = await process.exitCode;
49
- return;
50
- }
51
- banner('console');
52
- box('flutter_ota_kit console', [
53
- 'Open the web console with Flutter:',
54
- '',
55
- kv('dir', consoleDir.path),
56
- kv('run', 'flutter run -d chrome'),
57
- ]);
58
- });
37
+ final consoleDir = _findConsoleDir();
38
+ if (consoleDir == null) {
39
+ err('Console package not found (expected packages/console).');
40
+ return;
41
+ }
42
+ if (argResults!['open'] as bool) {
43
+ step('Launching console...');
44
+ final process = await Process.start(
45
+ 'flutter',
46
+ ['run', '-d', 'chrome'],
47
+ workingDirectory: consoleDir.path,
48
+ runInShell: true,
49
+ mode: ProcessStartMode.inheritStdio,
50
+ );
51
+ exitCode = await process.exitCode;
52
+ return;
53
+ }
54
+ banner('console');
55
+ box('flutter_ota_kit console', [
56
+ 'Open the web console with Flutter:',
57
+ '',
58
+ kv('dir', consoleDir.path),
59
+ kv('run', 'flutter run -d chrome'),
60
+ ]);
61
+ });
59
62
  }
@@ -25,87 +25,103 @@ class DeployCommand extends FlutterPatcherCommand {
25
25
 
26
26
  @override
27
27
  ArgParser get argParser => ArgParser()
28
- ..addOption('backend',
29
- abbr: 'b', help: 'Backend provider (supabase/postgres/cloudflare/aws).')
28
+ ..addOption(
29
+ 'backend',
30
+ abbr: 'b',
31
+ help: 'Backend provider (supabase/postgres/cloudflare/aws).',
32
+ )
30
33
  ..addOption('source', abbr: 's', help: 'Source directory to zip + upload.')
31
34
  ..addOption('channel', abbr: 'c', help: 'Target channel.')
32
35
  ..addOption('platform', abbr: 'p', defaultsTo: 'android', help: 'Platform.')
33
36
  ..addOption('message', abbr: 'm', help: 'Release message.')
34
37
  ..addFlag('force', abbr: 'f', help: 'Force the update on clients.')
35
- ..addOption('target-app-version',
36
- help: 'Semver range target (XOR with fingerprint-hash).')
37
- ..addOption('fingerprint-hash',
38
- help: 'Fingerprint hash target (XOR with target-app-version).')
39
- ..addOption('key', abbr: 'k', help: 'Path to Ed25519 private key file (sign bundle).')
38
+ ..addOption(
39
+ 'target-app-version',
40
+ help: 'Semver range target (XOR with fingerprint-hash).',
41
+ )
42
+ ..addOption(
43
+ 'fingerprint-hash',
44
+ help: 'Fingerprint hash target (XOR with target-app-version).',
45
+ )
46
+ ..addOption(
47
+ 'key',
48
+ abbr: 'k',
49
+ help: 'Path to Ed25519 private key file (sign bundle).',
50
+ )
40
51
  ..addOption('git-commit-hash', help: 'Git commit hash (auto-detected).')
41
- ..addOption('bundle-id', abbr: 'i', help: 'Explicit bundle id (uuidv7 by default).');
52
+ ..addOption(
53
+ 'bundle-id',
54
+ abbr: 'i',
55
+ help: 'Explicit bundle id (uuidv7 by default).',
56
+ );
42
57
 
43
58
  @override
44
59
  Future<int> run() => runGuarded(() async {
45
- final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
46
- final backend = requireBackend(cfg, override: backendOverride);
47
- final source = argResults!['source'] as String? ?? cfg?.source ?? './dist';
48
- final channel =
49
- argResults!['channel'] as String? ?? cfg?.channel ?? 'production';
50
- final platform =
51
- argResults!['platform'] as String? ?? cfg?.platform ?? 'android';
52
- final message = argResults!['message'] as String?;
53
- final force = argResults!['force'] as bool;
54
- final targetAppVersion = argResults!['target-app-version'] as String?;
55
- final fingerprintHash = argResults!['fingerprint-hash'] as String?;
56
- final keyPath = argResults!['key'] as String?;
57
- String? signingKey;
58
- if (keyPath != null) signingKey = File(keyPath).readAsStringSync().trim();
59
- final resolvedGitCommitHash =
60
- argResults!['git-commit-hash'] as String? ?? await gitCommitHash(source);
61
- final bundleId = argResults!['bundle-id'] as String?;
60
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
61
+ final backend = requireBackend(cfg, override: backendOverride);
62
+ final source = argResults!['source'] as String? ?? cfg?.source ?? './dist';
63
+ final channel =
64
+ argResults!['channel'] as String? ?? cfg?.channel ?? 'production';
65
+ final platform =
66
+ argResults!['platform'] as String? ?? cfg?.platform ?? 'android';
67
+ final message = argResults!['message'] as String?;
68
+ final force = argResults!['force'] as bool;
69
+ final targetAppVersion = argResults!['target-app-version'] as String?;
70
+ final fingerprintHash = argResults!['fingerprint-hash'] as String?;
71
+ final keyPath = argResults!['key'] as String?;
72
+ String? signingKey;
73
+ if (keyPath != null) signingKey = File(keyPath).readAsStringSync().trim();
74
+ final resolvedGitCommitHash =
75
+ argResults!['git-commit-hash'] as String? ??
76
+ await gitCommitHash(source);
77
+ final bundleId = argResults!['bundle-id'] as String?;
62
78
 
63
- banner('deploy');
64
- stdout.writeln(
65
- '${gray('channel')} ${cyan(channel)} '
66
- '${gray('platform')} ${cyan(platform)} '
67
- '${gray('source')} ${dim(source)}',
68
- );
69
- final bundle = await spinner(
70
- () => deployBundle(
71
- backend,
72
- DeployOptions(
73
- source: source,
74
- channel: channel,
75
- platform: platform,
76
- message: message,
77
- force: force,
78
- targetAppVersion: targetAppVersion,
79
- fingerprintHash: fingerprintHash,
80
- signingKeyBase64: signingKey,
81
- gitCommitHash: resolvedGitCommitHash,
82
- bundleId: bundleId,
83
- ),
84
- ),
85
- 'Uploading & registering bundle',
86
- done: 'Bundle deployed',
87
- );
79
+ banner('deploy');
80
+ stdout.writeln(
81
+ '${gray('channel')} ${cyan(channel)} '
82
+ '${gray('platform')} ${cyan(platform)} '
83
+ '${gray('source')} ${dim(source)}',
84
+ );
85
+ final bundle = await spinner(
86
+ () => deployBundle(
87
+ backend,
88
+ DeployOptions(
89
+ source: source,
90
+ channel: channel,
91
+ platform: platform,
92
+ message: message,
93
+ force: force,
94
+ targetAppVersion: targetAppVersion,
95
+ fingerprintHash: fingerprintHash,
96
+ signingKeyBase64: signingKey,
97
+ gitCommitHash: resolvedGitCommitHash,
98
+ bundleId: bundleId,
99
+ ),
100
+ ),
101
+ 'Uploading & registering bundle',
102
+ done: 'Bundle deployed',
103
+ );
88
104
 
89
- final lines = <String>[
90
- kv('bundle id', cyan(bundle.id)),
91
- kv('channel', bundle.channel),
92
- kv('platform', bundle.platform.value),
93
- kv('enabled', bundle.enabled ? green('true') : yellow('false')),
94
- kv('storage uri', gray(bundle.storageUri)),
95
- ];
96
- if (bundle.targetAppVersion != null) {
97
- lines.add(kv('target app', bundle.targetAppVersion!));
98
- }
99
- if (bundle.fingerprintHash != null) {
100
- lines.add(kv('fingerprint', bundle.fingerprintHash!));
101
- }
102
- if (bundle.gitCommitHash != null) {
103
- lines.add(kv('git commit', bundle.gitCommitHash!));
104
- }
105
- if (bundle.message != null) lines.add(kv('message', bundle.message!));
106
- if (bundle.metadata?.signature != null) {
107
- lines.add(kv('signature', dim('✓ signed')));
108
- }
109
- box('deployed', lines);
110
- });
105
+ final lines = <String>[
106
+ kv('bundle id', cyan(bundle.id)),
107
+ kv('channel', bundle.channel),
108
+ kv('platform', bundle.platform.value),
109
+ kv('enabled', bundle.enabled ? green('true') : yellow('false')),
110
+ kv('storage uri', gray(bundle.storageUri)),
111
+ ];
112
+ if (bundle.targetAppVersion != null) {
113
+ lines.add(kv('target app', bundle.targetAppVersion!));
114
+ }
115
+ if (bundle.fingerprintHash != null) {
116
+ lines.add(kv('fingerprint', bundle.fingerprintHash!));
117
+ }
118
+ if (bundle.gitCommitHash != null) {
119
+ lines.add(kv('git commit', bundle.gitCommitHash!));
120
+ }
121
+ if (bundle.message != null) lines.add(kv('message', bundle.message!));
122
+ if (bundle.metadata?.signature != null) {
123
+ lines.add(kv('signature', dim('✓ signed')));
124
+ }
125
+ box('deployed', lines);
126
+ });
111
127
  }
@@ -20,37 +20,74 @@ class DoctorCommand extends FlutterPatcherCommand {
20
20
  'Diagnose the local environment and backend connection.';
21
21
 
22
22
  @override
23
- ArgParser get argParser => ArgParser()
24
- ..addOption('backend', abbr: 'b', help: 'Backend provider.');
23
+ ArgParser get argParser =>
24
+ ArgParser()..addOption('backend', abbr: 'b', help: 'Backend provider.');
25
25
 
26
26
  @override
27
27
  Future<int> run() => runGuarded(() async {
28
- banner('doctor');
29
- final lines = <String>[
30
- kv('dart', Platform.version.split(' ').first),
31
- kv(
32
- 'os',
33
- '${Platform.operatingSystem} '
28
+ banner('doctor');
29
+ final lines = <String>[
30
+ kv('dart', Platform.version.split(' ').first),
31
+ kv(
32
+ 'os',
33
+ '${Platform.operatingSystem} '
34
34
  '${Platform.operatingSystemVersion.split('\n').first}',
35
- ),
36
- ];
37
- final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
38
- if (cfg == null) {
39
- lines.add(kv('config', yellow('not found — run `flutter_ota_kit init`')));
40
- box('doctor', lines);
41
- return;
42
- }
43
- lines
44
- ..add(kv('provider', cfg.provider))
45
- ..add(kv('channel', cfg.channel))
46
- ..add(kv('platform', cfg.platform));
47
- try {
48
- final backend = requireBackend(cfg, override: backendOverride);
49
- final channels = await backend.db.getChannels();
50
- lines.add(kv('backend', green('reachable — ${channels.join(', ')}')));
51
- } catch (e) {
52
- lines.add(kv('backend', red('unreachable — $e')));
53
- }
54
- box('doctor', lines);
55
- });
35
+ ),
36
+ ];
37
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
38
+ if (cfg == null) {
39
+ lines.add(kv('config', yellow('not found — run `flutter_ota_kit init`')));
40
+ box('doctor', lines);
41
+ return;
42
+ }
43
+ lines
44
+ ..add(kv('provider', cfg.provider))
45
+ ..add(kv('channel', cfg.channel))
46
+ ..add(kv('platform', cfg.platform));
47
+
48
+ // PocketBase-specific: show binary + health endpoint status.
49
+ if (cfg.provider == 'pocketbase') {
50
+ await _checkPocketBase(cfg, lines);
51
+ }
52
+
53
+ try {
54
+ final backend = requireBackend(cfg, override: backendOverride);
55
+ final channels = await backend.db.getChannels();
56
+ lines.add(kv('backend', green('reachable — ${channels.join(', ')}')));
57
+ } catch (e) {
58
+ lines.add(kv('backend', red('unreachable — $e')));
59
+ }
60
+ box('doctor', lines);
61
+ });
62
+
63
+ Future<void> _checkPocketBase(
64
+ FlutterPatcherConfig cfg,
65
+ List<String> lines,
66
+ ) async {
67
+ final paths = PocketBaseInstallPaths.resolve();
68
+ final installed = await paths.binaryPath.exists();
69
+ lines.add(
70
+ kv(
71
+ 'pocketbase binary',
72
+ installed
73
+ ? green('installed at ${paths.binaryPath.path}')
74
+ : yellow(
75
+ 'not installed — run `flutter_ota_kit pocketbase install`',
76
+ ),
77
+ ),
78
+ );
79
+ // Probe the health endpoint if a URL is configured.
80
+ final url = cfg.pocketbase.url;
81
+ if (url != null && url.isNotEmpty) {
82
+ try {
83
+ final client = PocketBaseClient(url);
84
+ final ok = await client.health();
85
+ lines.add(
86
+ kv('pocketbase health', ok ? green('reachable') : red('unreachable')),
87
+ );
88
+ } catch (e) {
89
+ lines.add(kv('pocketbase health', red('unreachable — $e')));
90
+ }
91
+ }
92
+ }
56
93
  }