@_nazmiforreal/flutter-ota 0.1.13 → 0.1.15

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 (54) hide show
  1. package/bin/flutter-ota-linux-x64 +0 -0
  2. package/bin/flutter-ota.js +23 -72
  3. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +0 -2
  4. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +6 -6
  5. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +2 -2
  6. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +1 -1
  7. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +112 -124
  8. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +20 -28
  9. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +40 -18
  10. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +10 -11
  11. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +30 -33
  12. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +43 -41
  13. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +9 -10
  14. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +32 -35
  15. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +3 -6
  16. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +59 -50
  17. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +32 -37
  18. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +13 -16
  19. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +12 -20
  20. package/dart-src/packages/cli-tools/lib/src/config.dart +10 -3
  21. package/dart-src/packages/cli-tools/lib/src/operations.dart +5 -0
  22. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +225 -31
  23. package/dart-src/packages/cli-tools/lib/src/util.dart +7 -3
  24. package/dart-src/packages/cli-tools/pubspec.lock +6 -6
  25. package/dart-src/packages/cli-tools/test/cli_test.dart +6 -0
  26. package/dart-src/packages/core/CHANGELOG.md +9 -0
  27. package/dart-src/packages/core/pubspec.yaml +1 -1
  28. package/dart-src/plugins/aws/CHANGELOG.md +10 -1
  29. package/dart-src/plugins/aws/pubspec.yaml +3 -3
  30. package/dart-src/plugins/cloudflare/CHANGELOG.md +9 -1
  31. package/dart-src/plugins/cloudflare/pubspec.yaml +3 -3
  32. package/dart-src/plugins/plugin-core/CHANGELOG.md +12 -14
  33. package/dart-src/plugins/plugin-core/pubspec.yaml +2 -2
  34. package/dart-src/plugins/pocketbase/pubspec.yaml +2 -2
  35. package/dart-src/plugins/postgres/CHANGELOG.md +13 -3
  36. package/dart-src/plugins/postgres/pubspec.yaml +3 -3
  37. package/dart-src/plugins/supabase/CHANGELOG.md +14 -11
  38. package/dart-src/plugins/supabase/pubspec.yaml +3 -3
  39. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +7 -3
  40. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +7 -4
  41. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +18 -6
  42. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +4 -2
  43. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +4 -2
  44. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +22 -6
  45. package/migrations/supabase/20250103114225_init.sql +7 -3
  46. package/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +7 -4
  47. package/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +18 -6
  48. package/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +4 -2
  49. package/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +4 -2
  50. package/migrations/supabase/20260520014100_hot-updater_rls.sql +22 -6
  51. package/package.json +1 -1
  52. package/scripts/vendor.sh +32 -0
  53. package/migrations/aws/README.md +0 -7
  54. package/migrations/cloudflare/bundles.sql +0 -42
@@ -1,9 +1,8 @@
1
- import 'package:args/args.dart';
2
1
  import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
3
2
 
4
3
  import '../ui/ui.dart';
5
4
 
6
- /// `flutter_ota_kit channel` — manage channels.
5
+ /// `flutter-ota channel` — manage channels.
7
6
  class ChannelCommand extends FlutterPatcherCommand {
8
7
  final FlutterPatcherConfig? config;
9
8
  final Backend? backendOverride;
@@ -38,7 +37,9 @@ class ChannelCommand extends FlutterPatcherCommand {
38
37
  }
39
38
 
40
39
  class ChannelListCommand extends FlutterPatcherCommand {
41
- ChannelListCommand({this.config, this.backendOverride});
40
+ ChannelListCommand({this.config, this.backendOverride}) {
41
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
42
+ }
42
43
 
43
44
  final FlutterPatcherConfig? config;
44
45
  final Backend? backendOverride;
@@ -49,10 +50,6 @@ class ChannelListCommand extends FlutterPatcherCommand {
49
50
  @override
50
51
  String get description => 'List channels.';
51
52
 
52
- @override
53
- ArgParser get argParser =>
54
- ArgParser()..addOption('backend', abbr: 'b', help: 'Backend provider.');
55
-
56
53
  @override
57
54
  Future<int> run() => runGuarded(() async {
58
55
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
@@ -68,7 +65,10 @@ class ChannelListCommand extends FlutterPatcherCommand {
68
65
  }
69
66
 
70
67
  class ChannelGetCommand extends FlutterPatcherCommand {
71
- ChannelGetCommand({this.config, this.backendOverride});
68
+ ChannelGetCommand({this.config, this.backendOverride}) {
69
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
70
+ argParser.addOption('channel', abbr: 'c', help: 'Channel.');
71
+ }
72
72
 
73
73
  final FlutterPatcherConfig? config;
74
74
  final Backend? backendOverride;
@@ -79,27 +79,21 @@ class ChannelGetCommand extends FlutterPatcherCommand {
79
79
  @override
80
80
  String get description => 'Show the live (enabled) bundle for a channel.';
81
81
 
82
- @override
83
- ArgParser get argParser => ArgParser()
84
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
85
- ..addOption('channel', abbr: 'c', help: 'Channel.');
86
-
87
82
  @override
88
83
  Future<int> run() => runGuarded(() async {
89
- final channel = argResults!['channel'] as String?;
84
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
85
+ final channel = argResults!['channel'] as String? ?? cfg?.channel;
90
86
  if (channel == null || channel.isEmpty) {
91
87
  throw PackException(
92
- 'Usage: flutter_ota_kit channel get --channel <channel>',
88
+ 'Usage: flutter-ota channel get --channel <channel>',
93
89
  64,
94
90
  );
95
91
  }
96
- final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
97
92
  final backend = requireBackend(cfg, override: backendOverride);
98
93
  banner('channel · get');
99
94
  final bundle = await getChannel(backend, channel);
100
95
  if (bundle == null) {
101
- err('no live bundle on channel "$channel"');
102
- return;
96
+ throw StateError('no live bundle on channel "$channel"');
103
97
  }
104
98
  box('channel "$channel"', [
105
99
  kv('live bundle', cyan(bundle.id)),
@@ -115,7 +109,11 @@ class ChannelGetCommand extends FlutterPatcherCommand {
115
109
  }
116
110
 
117
111
  class ChannelSetCommand extends FlutterPatcherCommand {
118
- ChannelSetCommand({this.config, this.backendOverride});
112
+ ChannelSetCommand({this.config, this.backendOverride}) {
113
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
114
+ argParser.addOption('channel', abbr: 'c', help: 'Channel.');
115
+ argParser.addOption('bundle-id', abbr: 'i', help: 'Bundle id.');
116
+ }
119
117
 
120
118
  final FlutterPatcherConfig? config;
121
119
  final Backend? backendOverride;
@@ -126,26 +124,20 @@ class ChannelSetCommand extends FlutterPatcherCommand {
126
124
  @override
127
125
  String get description => 'Promote a bundle to a channel.';
128
126
 
129
- @override
130
- ArgParser get argParser => ArgParser()
131
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
132
- ..addOption('channel', abbr: 'c', help: 'Channel.')
133
- ..addOption('bundle-id', abbr: 'i', help: 'Bundle id.');
134
-
135
127
  @override
136
128
  Future<int> run() => runGuarded(() async {
137
- final channel = argResults!['channel'] as String?;
129
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
130
+ final channel = argResults!['channel'] as String? ?? cfg?.channel;
138
131
  final bundleId = argResults!['bundle-id'] as String?;
139
132
  if (channel == null ||
140
133
  channel.isEmpty ||
141
134
  bundleId == null ||
142
135
  bundleId.isEmpty) {
143
136
  throw PackException(
144
- 'Usage: flutter_ota_kit channel set --channel <c> --bundle-id <id>',
137
+ 'Usage: flutter-ota channel set --channel <c> --bundle-id <id>',
145
138
  64,
146
139
  );
147
140
  }
148
- final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
149
141
  final backend = requireBackend(cfg, override: backendOverride);
150
142
  banner('channel · set');
151
143
  await spinner(
@@ -1,10 +1,9 @@
1
1
  import 'dart:convert';
2
2
  import 'dart:io';
3
3
 
4
- import 'package:args/args.dart';
5
-
6
4
  import '../cli_base.dart';
7
5
  import '../config.dart';
6
+ import '../ui/ui.dart' as ui;
8
7
 
9
8
  /// `flutter_ota_kit config` — get/set/list config values.
10
9
  class ConfigCommand extends FlutterPatcherCommand {
@@ -47,6 +46,10 @@ void _saveProjectJson(Map<String, dynamic> json) {
47
46
  }
48
47
 
49
48
  class ConfigGetCommand extends FlutterPatcherCommand {
49
+ ConfigGetCommand() {
50
+ argParser.addOption('key', abbr: 'k', help: 'Dot-path key.');
51
+ }
52
+
50
53
  @override
51
54
  String get name => 'get';
52
55
 
@@ -54,15 +57,12 @@ class ConfigGetCommand extends FlutterPatcherCommand {
54
57
  String get description =>
55
58
  'Print a config value by dot-path (e.g. supabase.url).';
56
59
 
57
- @override
58
- ArgParser get argParser =>
59
- ArgParser()..addOption('key', abbr: 'k', help: 'Dot-path key.');
60
-
61
60
  @override
62
61
  Future<int> run() => runGuarded(() async {
63
- final key = argResults!['key'] as String?;
62
+ final key = argResults!['key'] as String? ??
63
+ (argResults!.rest.isNotEmpty ? argResults!.rest.first : null);
64
64
  if (key == null || key.isEmpty) {
65
- throw StateError('Usage: flutter_ota_kit config get <key>');
65
+ throw StateError('Usage: flutter-ota config get <key>');
66
66
  }
67
67
  final value = readPath(_loadProjectJson(), key);
68
68
  if (value == null) {
@@ -76,28 +76,31 @@ class ConfigGetCommand extends FlutterPatcherCommand {
76
76
  }
77
77
 
78
78
  class ConfigSetCommand extends FlutterPatcherCommand {
79
+ ConfigSetCommand() {
80
+ argParser.addOption('key', abbr: 'k', help: 'Dot-path key.');
81
+ argParser.addOption('value', help: 'Value.');
82
+ }
83
+
79
84
  @override
80
85
  String get name => 'set';
81
86
 
82
87
  @override
83
88
  String get description => 'Set a config value by dot-path.';
84
89
 
85
- @override
86
- ArgParser get argParser => ArgParser()
87
- ..addOption('key', abbr: 'k', help: 'Dot-path key.')
88
- ..addOption('value', help: 'Value.');
89
-
90
90
  @override
91
91
  Future<int> run() => runGuarded(() async {
92
- final key = argResults!['key'] as String?;
93
- final value = argResults!['value'] as String?;
92
+ final key = argResults!['key'] as String? ??
93
+ (argResults!.rest.isNotEmpty ? argResults!.rest.first : null);
94
+ final value = argResults!['value'] as String? ??
95
+ (argResults!.rest.length > 1 ? argResults!.rest[1] : null);
94
96
  if (key == null || key.isEmpty || value == null) {
95
- throw StateError('Usage: flutter_ota_kit config set <key> <value>');
97
+ throw StateError('Usage: flutter-ota config set <key> <value>');
96
98
  }
97
99
  final json = _loadProjectJson();
98
100
  writePath(json, key, value);
99
101
  _saveProjectJson(json);
100
- stdout.writeln('Set $key = $value');
102
+ ui.banner('config · set');
103
+ ui.step('$key = $value');
101
104
  });
102
105
  }
103
106
 
@@ -111,6 +114,25 @@ class ConfigListCommand extends FlutterPatcherCommand {
111
114
  @override
112
115
  Future<int> run() => runGuarded(() async {
113
116
  final json = _loadProjectJson();
114
- stdout.writeln(const JsonEncoder.withIndent(' ').convert(json));
117
+ if (json.isEmpty) {
118
+ ui.banner('config');
119
+ ui.warn('No config found. Run flutter-ota init to create one.');
120
+ return;
121
+ }
122
+ ui.banner('config');
123
+ _printConfig(json, '');
115
124
  });
116
125
  }
126
+
127
+ void _printConfig(Map<String, dynamic> json, String prefix) {
128
+ for (final entry in json.entries) {
129
+ final key = prefix.isEmpty ? entry.key : '$prefix.${entry.key}';
130
+ final value = entry.value;
131
+ if (value is Map<String, dynamic>) {
132
+ _printConfig(value, key);
133
+ } else {
134
+ final display = value is String ? value : const JsonEncoder().convert(value);
135
+ stdout.writeln(ui.kv(key, display));
136
+ }
137
+ }
138
+ }
@@ -1,6 +1,5 @@
1
1
  import 'dart:io';
2
2
 
3
- import 'package:args/args.dart';
4
3
  import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
5
4
  import 'package:path/path.dart' as p;
6
5
 
@@ -8,19 +7,19 @@ import '../ui/ui.dart';
8
7
 
9
8
  /// `flutter_ota_kit console` — open the web console.
10
9
  class ConsoleCommand extends FlutterPatcherCommand {
11
- @override
12
- String get name => 'console';
13
-
14
- @override
15
- String get description => 'Open the flutter_ota_kit web console.';
16
-
17
- @override
18
- ArgParser get argParser => ArgParser()
19
- ..addFlag(
10
+ ConsoleCommand() {
11
+ argParser.addFlag(
20
12
  'open',
21
13
  help:
22
14
  'Launch the console with `flutter run -d chrome` (requires Flutter).',
23
15
  );
16
+ }
17
+
18
+ @override
19
+ String get name => 'console';
20
+
21
+ @override
22
+ String get description => 'Open the flutter-ota web console.';
24
23
 
25
24
  Directory? _findConsoleDir() {
26
25
  var dir = Directory(p.dirname(Platform.script.path));
@@ -52,7 +51,7 @@ class ConsoleCommand extends FlutterPatcherCommand {
52
51
  return;
53
52
  }
54
53
  banner('console');
55
- box('flutter_ota_kit console', [
54
+ box('flutter-ota console', [
56
55
  'Open the web console with Flutter:',
57
56
  '',
58
57
  kv('dir', consoleDir.path),
@@ -1,7 +1,5 @@
1
1
  import 'dart:io';
2
2
 
3
- import 'package:args/args.dart';
4
-
5
3
  import '../backend.dart';
6
4
  import '../cli_base.dart';
7
5
  import '../config.dart';
@@ -9,51 +7,49 @@ import '../operations.dart';
9
7
  import '../ui/ui.dart';
10
8
  import '../util.dart';
11
9
 
12
- /// `flutter_ota_kit deploy` — zip + upload + register a new bundle.
10
+ /// `flutter-ota deploy` — zip + upload + register a new bundle.
13
11
  class DeployCommand extends FlutterPatcherCommand {
14
- DeployCommand({this.config, this.backendOverride});
15
-
16
- final FlutterPatcherConfig? config;
17
- final Backend? backendOverride;
18
-
19
- @override
20
- String get name => 'deploy';
21
-
22
- @override
23
- String get description =>
24
- 'Zip a source directory, upload it, and register a new bundle.';
25
-
26
- @override
27
- ArgParser get argParser => ArgParser()
28
- ..addOption(
12
+ DeployCommand({this.config, this.backendOverride}) {
13
+ argParser.addOption(
29
14
  'backend',
30
15
  abbr: 'b',
31
16
  help: 'Backend provider (supabase/postgres/cloudflare/aws).',
32
- )
33
- ..addOption('source', abbr: 's', help: 'Source directory to zip + upload.')
34
- ..addOption('channel', abbr: 'c', help: 'Target channel.')
35
- ..addOption('platform', abbr: 'p', defaultsTo: 'android', help: 'Platform.')
36
- ..addOption('message', abbr: 'm', help: 'Release message.')
37
- ..addFlag('force', abbr: 'f', help: 'Force the update on clients.')
38
- ..addOption(
17
+ );
18
+ argParser.addOption('source', abbr: 's', help: 'Source directory to zip + upload.');
19
+ argParser.addOption('channel', abbr: 'c', help: 'Target channel.');
20
+ argParser.addOption('platform', abbr: 'p', defaultsTo: 'android', help: 'Platform.');
21
+ argParser.addOption('message', abbr: 'm', help: 'Release message.');
22
+ argParser.addFlag('force', abbr: 'f', help: 'Force the update on clients.');
23
+ argParser.addOption(
39
24
  'target-app-version',
40
25
  help: 'Semver range target (XOR with fingerprint-hash).',
41
- )
42
- ..addOption(
26
+ );
27
+ argParser.addOption(
43
28
  'fingerprint-hash',
44
29
  help: 'Fingerprint hash target (XOR with target-app-version).',
45
- )
46
- ..addOption(
30
+ );
31
+ argParser.addOption(
47
32
  'key',
48
33
  abbr: 'k',
49
34
  help: 'Path to Ed25519 private key file (sign bundle).',
50
- )
51
- ..addOption('git-commit-hash', help: 'Git commit hash (auto-detected).')
52
- ..addOption(
35
+ );
36
+ argParser.addOption('git-commit-hash', help: 'Git commit hash (auto-detected).');
37
+ argParser.addOption(
53
38
  'bundle-id',
54
39
  abbr: 'i',
55
40
  help: 'Explicit bundle id (uuidv7 by default).',
56
41
  );
42
+ }
43
+
44
+ final FlutterPatcherConfig? config;
45
+ final Backend? backendOverride;
46
+
47
+ @override
48
+ String get name => 'deploy';
49
+
50
+ @override
51
+ String get description =>
52
+ 'Zip a source directory, upload it, and register a new bundle.';
57
53
 
58
54
  @override
59
55
  Future<int> run() => runGuarded(() async {
@@ -78,10 +74,11 @@ class DeployCommand extends FlutterPatcherCommand {
78
74
 
79
75
  banner('deploy');
80
76
  stdout.writeln(
81
- '${gray('channel')} ${cyan(channel)} '
77
+ ' ${gray('channel')} ${cyan(channel)} '
82
78
  '${gray('platform')} ${cyan(platform)} '
83
79
  '${gray('source')} ${dim(source)}',
84
80
  );
81
+
85
82
  final bundle = await spinner(
86
83
  () => deployBundle(
87
84
  backend,
@@ -1,13 +1,14 @@
1
1
  import 'dart:io';
2
2
 
3
- import 'package:args/args.dart';
4
3
  import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
5
4
 
6
5
  import '../ui/ui.dart';
7
6
 
8
- /// `flutter_ota_kit doctor` — environment + backend connectivity check.
7
+ /// `flutter-ota doctor` — environment + backend connectivity check.
9
8
  class DoctorCommand extends FlutterPatcherCommand {
10
- DoctorCommand({this.config, this.backendOverride});
9
+ DoctorCommand({this.config, this.backendOverride}) {
10
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
11
+ }
11
12
 
12
13
  final FlutterPatcherConfig? config;
13
14
  final Backend? backendOverride;
@@ -19,74 +20,75 @@ class DoctorCommand extends FlutterPatcherCommand {
19
20
  String get description =>
20
21
  'Diagnose the local environment and backend connection.';
21
22
 
22
- @override
23
- ArgParser get argParser =>
24
- ArgParser()..addOption('backend', abbr: 'b', help: 'Backend provider.');
25
-
26
23
  @override
27
24
  Future<int> run() => runGuarded(() async {
28
25
  banner('doctor');
29
- final lines = <String>[
30
- kv('dart', Platform.version.split(' ').first),
31
- kv(
32
- 'os',
33
- '${Platform.operatingSystem} '
34
- '${Platform.operatingSystemVersion.split('\n').first}',
35
- ),
36
- ];
26
+ final steps = Steps('doctor');
27
+
28
+ steps.success('Dart ${Platform.version.split(' ').first}');
29
+ steps.success(
30
+ 'OS ${Platform.operatingSystem} '
31
+ '${Platform.operatingSystemVersion.split('\n').first}',
32
+ );
33
+
37
34
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
38
35
  if (cfg == null) {
39
- lines.add(kv('config', yellow('not found — run `flutter_ota_kit init`')));
40
- box('doctor', lines);
36
+ steps.fail('Config not found — run ${bold('flutter-ota init')}');
37
+ steps.summary();
41
38
  return;
42
39
  }
43
- lines
44
- ..add(kv('provider', cfg.provider))
45
- ..add(kv('channel', cfg.channel))
46
- ..add(kv('platform', cfg.platform));
40
+ steps.success('Provider ${cfg.provider}');
41
+ steps.success('Channel ${cfg.channel}');
42
+ steps.success('Platform ${cfg.platform}');
47
43
 
48
44
  // PocketBase-specific: show binary + health endpoint status.
49
45
  if (cfg.provider == 'pocketbase') {
50
- await _checkPocketBase(cfg, lines);
46
+ await _checkPocketBase(cfg, steps);
51
47
  }
52
48
 
53
49
  try {
54
50
  final backend = requireBackend(cfg, override: backendOverride);
51
+ final sw = Stopwatch()..start();
55
52
  final channels = await backend.db.getChannels();
56
- lines.add(kv('backend', green('reachable — ${channels.join(', ')}')));
53
+ sw.stop();
54
+ final ms = sw.elapsedMilliseconds;
55
+ final time = ms >= 1000
56
+ ? '${(ms / 1000).toStringAsFixed(1)}s'
57
+ : '${ms}ms';
58
+ steps.success(
59
+ 'Backend reachable — ${channels.join(', ')} in $time',
60
+ );
57
61
  } catch (e) {
58
- lines.add(kv('backend', red('unreachable — $e')));
62
+ steps.fail('Backend unreachable — $e');
59
63
  }
60
- box('doctor', lines);
64
+ steps.summary();
61
65
  });
62
66
 
63
67
  Future<void> _checkPocketBase(
64
68
  FlutterPatcherConfig cfg,
65
- List<String> lines,
69
+ Steps steps,
66
70
  ) async {
67
71
  final paths = PocketBaseInstallPaths.resolve();
68
72
  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.
73
+ if (installed) {
74
+ steps.success('PocketBase binary at ${paths.binaryPath.path}');
75
+ } else {
76
+ steps.fail(
77
+ 'PocketBase not installed — run ${bold('flutter-ota pocketbase install')}',
78
+ );
79
+ }
80
80
  final url = cfg.pocketbase.url;
81
81
  if (url != null && url.isNotEmpty) {
82
82
  try {
83
83
  final client = PocketBaseClient(url);
84
84
  final ok = await client.health();
85
- lines.add(
86
- kv('pocketbase health', ok ? green('reachable') : red('unreachable')),
87
- );
85
+ if (ok) {
86
+ steps.success('PocketBase reachable');
87
+ } else {
88
+ steps.fail('PocketBase unreachable');
89
+ }
88
90
  } catch (e) {
89
- lines.add(kv('pocketbase health', red('unreachable — $e')));
91
+ steps.fail('PocketBase unreachable — $e');
90
92
  }
91
93
  }
92
94
  }
@@ -1,12 +1,20 @@
1
1
  import 'dart:io';
2
2
 
3
- import 'package:args/args.dart';
4
3
 
5
4
  import '../cli_base.dart';
6
5
  import '../util.dart';
7
6
 
8
7
  /// `flutter_ota_kit fingerprint` — compute a build-time fingerprint hash.
9
8
  class FingerprintCommand extends FlutterPatcherCommand {
9
+ FingerprintCommand() {
10
+ argParser.addOption(
11
+ 'source',
12
+ abbr: 's',
13
+ defaultsTo: './dist',
14
+ help: 'Directory to fingerprint.',
15
+ );
16
+ }
17
+
10
18
  @override
11
19
  String get name => 'fingerprint';
12
20
 
@@ -14,15 +22,6 @@ class FingerprintCommand extends FlutterPatcherCommand {
14
22
  String get description =>
15
23
  'Compute a deterministic fingerprint hash for a directory (build-time).';
16
24
 
17
- @override
18
- ArgParser get argParser => ArgParser()
19
- ..addOption(
20
- 'source',
21
- abbr: 's',
22
- defaultsTo: './dist',
23
- help: 'Directory to fingerprint.',
24
- );
25
-
26
25
  @override
27
26
  Future<int> run() => runGuarded(() async {
28
27
  final source = argResults!['source'] as String;
@@ -1,11 +1,10 @@
1
1
  import 'dart:io';
2
2
 
3
- import 'package:args/args.dart';
4
3
  import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
5
4
 
6
5
  import '../ui/ui.dart';
7
6
 
8
- /// `flutter_ota_kit init <backend>` — scaffold a Flutter project for OTA updates.
7
+ /// `flutter-ota init <backend>` — scaffold a Flutter project for OTA updates.
9
8
  ///
10
9
  /// Beyond writing the `.flutter_ota_kit/config.json` (so every other `flutter_ota_kit` command
11
10
  /// knows the backend), it also drops the files a host app actually needs:
@@ -18,46 +17,44 @@ import '../ui/ui.dart';
18
17
  ///
19
18
  /// All CLI working state is stored under `.flutter_ota_kit/`.
20
19
  class InitCommand extends FlutterPatcherCommand {
21
- InitCommand({this.config, this.backendOverride});
22
-
23
- final FlutterPatcherConfig? config;
24
- final Backend? backendOverride;
25
-
26
- @override
27
- String get name => 'init';
28
-
29
- @override
30
- String get description =>
31
- 'Scaffold flutter_ota_kit integration files + .flutter_ota_kit config for the current '
32
- 'Flutter project. Usage: flutter_ota_kit init <supabase|postgres|cloudflare|aws|pocketbase>';
33
-
34
- @override
35
- ArgParser get argParser => ArgParser()
36
- ..addOption(
20
+ InitCommand({this.config, this.backendOverride}) {
21
+ argParser.addOption(
37
22
  'provider',
38
23
  abbr: 'p',
39
24
  defaultsTo: 'supabase',
40
25
  help:
41
26
  'Backend provider (also accepted as the first positional argument).',
42
- )
43
- ..addOption(
27
+ );
28
+ argParser.addOption(
44
29
  'channel',
45
30
  abbr: 'c',
46
31
  defaultsTo: 'production',
47
32
  help: 'Default channel.',
48
- )
49
- ..addOption('platform', defaultsTo: 'android', help: 'Default platform.')
50
- ..addOption(
33
+ );
34
+ argParser.addOption('platform', defaultsTo: 'android', help: 'Default platform.');
35
+ argParser.addOption(
51
36
  'source',
52
37
  abbr: 's',
53
38
  defaultsTo: './dist',
54
39
  help: 'Default deploy source.',
55
- )
56
- ..addFlag(
40
+ );
41
+ argParser.addFlag(
57
42
  'global',
58
43
  help: 'Write to the global ~/.flutter_ota_kit config (no scaffolding).',
59
- )
60
- ..addFlag('force', abbr: 'f', help: 'Overwrite an existing config.');
44
+ );
45
+ argParser.addFlag('force', abbr: 'f', help: 'Overwrite an existing config.');
46
+ }
47
+
48
+ final FlutterPatcherConfig? config;
49
+ final Backend? backendOverride;
50
+
51
+ @override
52
+ String get name => 'init';
53
+
54
+ @override
55
+ String get description =>
56
+ 'Scaffold flutter-ota integration files + .flutter_ota_kit config for the current '
57
+ 'Flutter project. Usage: flutter-ota init <supabase|postgres|cloudflare|aws|pocketbase>';
61
58
 
62
59
  String? _p(String label, String? current) {
63
60
  if (!stdin.hasTerminal) return current;
@@ -97,7 +94,7 @@ class InitCommand extends FlutterPatcherCommand {
97
94
 
98
95
  @override
99
96
  Future<int> run() => runGuarded(() async {
100
- // A positional backend (`flutter_ota_kit init supabase`) takes precedence
97
+ // A positional backend (`flutter-ota init supabase`) takes precedence
101
98
  // over the --provider flag.
102
99
  final positional = argResults!.rest;
103
100
  final requested = positional.isNotEmpty
@@ -274,22 +271,22 @@ class InitCommand extends FlutterPatcherCommand {
274
271
  );
275
272
  if (provider == 'supabase') {
276
273
  stdout.writeln(
277
- ' 3. Provision the backend once: ${dim('flutter_ota_kit migrate supabase')}',
274
+ ' 3. Provision the backend once: ${dim('flutter-ota migrate supabase')}',
278
275
  );
279
276
  } else if (provider == 'postgres') {
280
277
  stdout.writeln(
281
- ' 3. Provision the backend: ${dim('flutter_ota_kit migrate postgres')}',
278
+ ' 3. Provision the backend: ${dim('flutter-ota migrate postgres')}',
282
279
  );
283
280
  } else if (provider == 'pocketbase') {
284
281
  stdout.writeln(
285
282
  ' 3. Install PocketBase + provision the schema: '
286
- '${dim('flutter_ota_kit pocketbase install')} then '
287
- '${dim('flutter_ota_kit pocketbase serve')}.',
283
+ '${dim('flutter-ota pocketbase install')} then '
284
+ '${dim('flutter-ota pocketbase serve')}.',
288
285
  );
289
286
  }
290
287
  stdout.writeln(
291
- ' 4. Build a patch: ${dim('flutter_ota_kit build --name 1.0.1')} '
292
- 'then ${dim('flutter_ota_kit deploy')}',
288
+ ' 4. Build a patch: ${dim('flutter-ota build --name 1.0.1')} '
289
+ 'then ${dim('flutter-ota deploy')}',
293
290
  );
294
291
  });
295
292
 
@@ -592,7 +589,7 @@ class InitCommand extends FlutterPatcherCommand {
592
589
  return '''
593
590
  import 'package:flutter_ota_kit/flutter_ota_kit.dart';
594
591
 
595
- // Generated by `flutter_ota_kit init $provider`.
592
+ // Generated by `flutter-ota init $provider`.
596
593
  //
597
594
  // Non-secret values default to the project config (`.flutter_ota_kit/config.json`
598
595
  // set up during init); every value can be overridden by a build-time