@_nazmiforreal/flutter-ota 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/flutter-ota-linux-x64 +0 -0
- package/bin/flutter-ota.js +23 -72
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +0 -2
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +2 -2
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +112 -124
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +20 -28
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +40 -18
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +10 -11
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +30 -33
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +43 -41
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +9 -10
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +32 -35
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +3 -6
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +59 -50
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +32 -37
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +13 -16
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +12 -20
- package/dart-src/packages/cli-tools/lib/src/config.dart +10 -3
- package/dart-src/packages/cli-tools/lib/src/operations.dart +5 -0
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +225 -31
- package/dart-src/packages/cli-tools/lib/src/util.dart +7 -3
- package/dart-src/packages/cli-tools/test/cli_test.dart +6 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +7 -3
- package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +7 -4
- package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +18 -6
- package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +4 -2
- package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +4 -2
- package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +22 -6
- package/migrations/supabase/20250103114225_init.sql +7 -3
- package/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +7 -4
- package/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +18 -6
- package/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +4 -2
- package/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +4 -2
- package/migrations/supabase/20260520014100_hot-updater_rls.sql +22 -6
- package/package.json +1 -1
- package/scripts/vendor.sh +32 -0
- package/migrations/aws/README.md +0 -7
- 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
|
-
/// `
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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('
|
|
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
|
-
/// `
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
26
|
+
);
|
|
27
|
+
argParser.addOption(
|
|
43
28
|
'fingerprint-hash',
|
|
44
29
|
help: 'Fingerprint hash target (XOR with target-app-version).',
|
|
45
|
-
)
|
|
46
|
-
|
|
30
|
+
);
|
|
31
|
+
argParser.addOption(
|
|
47
32
|
'key',
|
|
48
33
|
abbr: 'k',
|
|
49
34
|
help: 'Path to Ed25519 private key file (sign bundle).',
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
/// `
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
40
|
-
|
|
36
|
+
steps.fail('Config not found — run ${bold('flutter-ota init')}');
|
|
37
|
+
steps.summary();
|
|
41
38
|
return;
|
|
42
39
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
62
|
+
steps.fail('Backend unreachable — $e');
|
|
59
63
|
}
|
|
60
|
-
|
|
64
|
+
steps.summary();
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
Future<void> _checkPocketBase(
|
|
64
68
|
FlutterPatcherConfig cfg,
|
|
65
|
-
|
|
69
|
+
Steps steps,
|
|
66
70
|
) async {
|
|
67
71
|
final paths = PocketBaseInstallPaths.resolve();
|
|
68
72
|
final installed = await paths.binaryPath.exists();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
if (ok) {
|
|
86
|
+
steps.success('PocketBase reachable');
|
|
87
|
+
} else {
|
|
88
|
+
steps.fail('PocketBase unreachable');
|
|
89
|
+
}
|
|
88
90
|
} catch (e) {
|
|
89
|
-
|
|
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
|
-
/// `
|
|
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
|
-
|
|
27
|
+
);
|
|
28
|
+
argParser.addOption(
|
|
44
29
|
'channel',
|
|
45
30
|
abbr: 'c',
|
|
46
31
|
defaultsTo: 'production',
|
|
47
32
|
help: 'Default channel.',
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
40
|
+
);
|
|
41
|
+
argParser.addFlag(
|
|
57
42
|
'global',
|
|
58
43
|
help: 'Write to the global ~/.flutter_ota_kit config (no scaffolding).',
|
|
59
|
-
)
|
|
60
|
-
|
|
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 (`
|
|
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('
|
|
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('
|
|
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('
|
|
287
|
-
'${dim('
|
|
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('
|
|
292
|
-
'then ${dim('
|
|
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 `
|
|
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
|