@_nazmiforreal/flutter-ota 0.1.16 → 0.1.18
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/dart-src/packages/cli-tools/.dart_tool/package_graph.json +7 -7
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +200 -0
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +29 -65
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +4 -15
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +2 -12
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +62 -24
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +3 -4
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +19 -47
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +12 -13
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +2 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +15 -17
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +14 -16
- package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +0 -1
- package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +0 -3
- package/dart-src/packages/cli-tools/lib/src/runner.dart +15 -1
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +126 -8
- package/dart-src/packages/cli-tools/pubspec.lock +7 -7
- package/dart-src/packages/core/CHANGELOG.md +1 -12
- package/dart-src/packages/core/LICENSE +1 -1
- package/dart-src/packages/core/pubspec.yaml +1 -1
- package/dart-src/plugins/aws/CHANGELOG.md +1 -18
- package/dart-src/plugins/aws/LICENSE +1 -1
- package/dart-src/plugins/aws/pubspec.yaml +1 -1
- package/dart-src/plugins/cloudflare/CHANGELOG.md +1 -18
- package/dart-src/plugins/cloudflare/LICENSE +1 -1
- package/dart-src/plugins/cloudflare/pubspec.yaml +1 -1
- package/dart-src/plugins/plugin-core/CHANGELOG.md +1 -17
- package/dart-src/plugins/plugin-core/LICENSE +1 -1
- package/dart-src/plugins/plugin-core/pubspec.yaml +1 -1
- package/dart-src/plugins/pocketbase/CHANGELOG.md +1 -8
- package/dart-src/plugins/pocketbase/LICENSE +1 -1
- package/dart-src/plugins/pocketbase/pubspec.yaml +1 -1
- package/dart-src/plugins/postgres/CHANGELOG.md +1 -19
- package/dart-src/plugins/postgres/LICENSE +1 -1
- package/dart-src/plugins/postgres/pubspec.yaml +1 -1
- package/dart-src/plugins/supabase/CHANGELOG.md +1 -23
- package/dart-src/plugins/supabase/LICENSE +1 -1
- package/dart-src/plugins/supabase/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/scripts/vendor.sh +0 -0
|
Binary file
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"name": "flutter_ota_kit_pocketbase",
|
|
33
|
-
"version": "0.1.
|
|
33
|
+
"version": "0.1.2",
|
|
34
34
|
"dependencies": [
|
|
35
35
|
"flutter_ota_kit_core",
|
|
36
36
|
"flutter_ota_kit_plugin_core",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"name": "flutter_ota_kit_aws",
|
|
42
|
-
"version": "0.1.
|
|
42
|
+
"version": "0.1.5",
|
|
43
43
|
"dependencies": [
|
|
44
44
|
"asn1lib",
|
|
45
45
|
"crypto",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
"name": "flutter_ota_kit_cloudflare",
|
|
55
|
-
"version": "0.1.
|
|
55
|
+
"version": "0.1.5",
|
|
56
56
|
"dependencies": [
|
|
57
57
|
"crypto",
|
|
58
58
|
"flutter_ota_kit_core",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"name": "flutter_ota_kit_postgres",
|
|
66
|
-
"version": "0.1.
|
|
66
|
+
"version": "0.1.5",
|
|
67
67
|
"dependencies": [
|
|
68
68
|
"flutter_ota_kit_core",
|
|
69
69
|
"flutter_ota_kit_plugin_core",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"name": "flutter_ota_kit_supabase",
|
|
75
|
-
"version": "0.1.
|
|
75
|
+
"version": "0.1.6",
|
|
76
76
|
"dependencies": [
|
|
77
77
|
"flutter_ota_kit_core",
|
|
78
78
|
"flutter_ota_kit_plugin_core",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
"name": "flutter_ota_kit_plugin_core",
|
|
85
|
-
"version": "0.0.
|
|
85
|
+
"version": "0.0.6",
|
|
86
86
|
"dependencies": [
|
|
87
87
|
"flutter_ota_kit_core",
|
|
88
88
|
"mime"
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"name": "flutter_ota_kit_core",
|
|
93
|
-
"version": "0.1.
|
|
93
|
+
"version": "0.1.4",
|
|
94
94
|
"dependencies": []
|
|
95
95
|
},
|
|
96
96
|
{
|
|
@@ -81,9 +81,209 @@ Future<int> run(List<String> args) async {
|
|
|
81
81
|
final result = await runner.run(args);
|
|
82
82
|
return result ?? 0;
|
|
83
83
|
} on UsageException catch (e) {
|
|
84
|
+
final msg = e.message;
|
|
85
|
+
|
|
86
|
+
// Missing subcommand → show the command's own help + suggestions.
|
|
87
|
+
if (msg.startsWith('Missing subcommand')) {
|
|
88
|
+
final match = RegExp(r'"flutter-ota (\w+)"').firstMatch(msg);
|
|
89
|
+
if (match != null) {
|
|
90
|
+
final cmd = runner.commands[match.group(1)];
|
|
91
|
+
if (cmd != null) {
|
|
92
|
+
_printMissingSubcommand(cmd);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// No command given → show top-level help.
|
|
99
|
+
if (msg.contains('No command specified')) {
|
|
100
|
+
runner.printUsage();
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Unknown command → fuzzy-match and suggest.
|
|
105
|
+
final unknownMatch =
|
|
106
|
+
RegExp(r'Could not find a command named "?(\w+)"?').firstMatch(msg);
|
|
107
|
+
if (unknownMatch != null) {
|
|
108
|
+
final typed = unknownMatch.group(1)!;
|
|
109
|
+
_printUnknownCommand(runner, typed);
|
|
110
|
+
return 64;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Unknown subcommand → suggest closest subcommand.
|
|
114
|
+
final subMatch = RegExp(
|
|
115
|
+
r'Could not find a subcommand named "?(\w+)"? for "?flutter-ota (\w+)"?',
|
|
116
|
+
).firstMatch(msg);
|
|
117
|
+
if (subMatch != null) {
|
|
118
|
+
final subTyped = subMatch.group(1)!;
|
|
119
|
+
final parentName = subMatch.group(2)!;
|
|
120
|
+
final parentCmd = runner.commands[parentName];
|
|
121
|
+
if (parentCmd != null) {
|
|
122
|
+
_printUnknownSubcommand(parentCmd, subTyped);
|
|
123
|
+
return 64;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback — show the raw error + usage.
|
|
84
128
|
stderr.writeln(e.message);
|
|
85
129
|
stderr.writeln('');
|
|
86
130
|
stderr.writeln(e.usage);
|
|
87
131
|
return 64;
|
|
88
132
|
}
|
|
89
133
|
}
|
|
134
|
+
|
|
135
|
+
/// Show a helpful "unknown command" message with fuzzy-match suggestions.
|
|
136
|
+
void _printUnknownCommand(FlutterPatcherRunner runner, String typed) {
|
|
137
|
+
final commands = runner.commands.keys.toList();
|
|
138
|
+
|
|
139
|
+
// Exact prefix match (e.g. "bund" → "bundle").
|
|
140
|
+
final prefixMatches = commands.where((c) => c.startsWith(typed)).toList();
|
|
141
|
+
|
|
142
|
+
// Levenshtein-based fuzzy match for short typos.
|
|
143
|
+
final fuzzyMatches = <String>[];
|
|
144
|
+
for (final c in commands) {
|
|
145
|
+
if (prefixMatches.contains(c)) continue;
|
|
146
|
+
if (_levenshtein(typed, c) <= 2) {
|
|
147
|
+
fuzzyMatches.add(c);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Subcommand matching (e.g. "bundle lis" → suggest "list").
|
|
152
|
+
final subMatch = RegExp(r'^(\w+)\s+(\w+)$').firstMatch(typed);
|
|
153
|
+
String? parentCmd;
|
|
154
|
+
String? subTyped;
|
|
155
|
+
if (subMatch != null) {
|
|
156
|
+
parentCmd = subMatch.group(1);
|
|
157
|
+
subTyped = subMatch.group(2);
|
|
158
|
+
}
|
|
159
|
+
// Also try if typed is just the first part.
|
|
160
|
+
if (parentCmd != null && !commands.contains(parentCmd)) {
|
|
161
|
+
// Check if parentCmd is a fuzzy match for a real command.
|
|
162
|
+
final parentFuzzy = commands.where((c) {
|
|
163
|
+
if (c == parentCmd) return true;
|
|
164
|
+
return _levenshtein(parentCmd!, c) <= 2;
|
|
165
|
+
}).toList();
|
|
166
|
+
if (parentFuzzy.isNotEmpty) {
|
|
167
|
+
parentCmd = parentFuzzy.first;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
stderr.writeln('');
|
|
172
|
+
stderr.writeln(' ${_red('✗')} Unknown command: ${_red(typed)}');
|
|
173
|
+
stderr.writeln('');
|
|
174
|
+
|
|
175
|
+
if (prefixMatches.isNotEmpty || fuzzyMatches.isNotEmpty) {
|
|
176
|
+
final suggestions = [...prefixMatches, ...fuzzyMatches];
|
|
177
|
+
stderr.writeln(' ${_cyan('Did you mean?')}');
|
|
178
|
+
for (final s in suggestions.take(3)) {
|
|
179
|
+
stderr.writeln(' ${_green(s)}');
|
|
180
|
+
}
|
|
181
|
+
stderr.writeln('');
|
|
182
|
+
|
|
183
|
+
// If it looks like "parent sub" but the sub is wrong, show sub help.
|
|
184
|
+
if (parentCmd != null && subTyped != null) {
|
|
185
|
+
final cmd = runner.commands[parentCmd];
|
|
186
|
+
if (cmd != null && cmd.subcommands.isNotEmpty) {
|
|
187
|
+
// Find closest subcommand.
|
|
188
|
+
final subNames = cmd.subcommands.keys.toList();
|
|
189
|
+
final subSuggestion = subNames
|
|
190
|
+
.where((s) => s.startsWith(subTyped!) || _levenshtein(subTyped!, s) <= 2)
|
|
191
|
+
.toList();
|
|
192
|
+
if (subSuggestion.isNotEmpty) {
|
|
193
|
+
stderr.writeln(' ${_cyan('Did you mean?')} $parentCmd ${_green(subSuggestion.first)}');
|
|
194
|
+
stderr.writeln('');
|
|
195
|
+
}
|
|
196
|
+
cmd.printUsage();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Show top-level help.
|
|
202
|
+
runner.printUsage();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// No close matches — show all available commands.
|
|
207
|
+
runner.printUsage();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/// Show a helpful "unknown subcommand" message with suggestions.
|
|
211
|
+
void _printUnknownSubcommand(Command<int> parent, String typed) {
|
|
212
|
+
final subNames = parent.subcommands.keys.toList();
|
|
213
|
+
|
|
214
|
+
// Find closest subcommands.
|
|
215
|
+
final suggestions = subNames
|
|
216
|
+
.where((s) => s.startsWith(typed) || _levenshtein(typed, s) <= 2)
|
|
217
|
+
.toList();
|
|
218
|
+
|
|
219
|
+
stderr.writeln('');
|
|
220
|
+
stderr.writeln(
|
|
221
|
+
' ${_red('✗')} Unknown subcommand: ${_red(typed)} for ${_cyan('flutter-ota ${parent.name}')}',
|
|
222
|
+
);
|
|
223
|
+
stderr.writeln('');
|
|
224
|
+
|
|
225
|
+
if (suggestions.isNotEmpty) {
|
|
226
|
+
stderr.writeln(' ${_cyan('Did you mean?')}');
|
|
227
|
+
for (final s in suggestions.take(3)) {
|
|
228
|
+
stderr.writeln(' ${_green(s)}');
|
|
229
|
+
}
|
|
230
|
+
stderr.writeln('');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
parent.printUsage();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Show a helpful "missing subcommand" message with all subcommand suggestions.
|
|
237
|
+
void _printMissingSubcommand(Command<int> parent) {
|
|
238
|
+
final subNames = parent.subcommands.keys.toList()..sort();
|
|
239
|
+
|
|
240
|
+
stderr.writeln('');
|
|
241
|
+
stderr.writeln(
|
|
242
|
+
' ${_red('✗')} Missing subcommand for ${_cyan('flutter-ota ${parent.name}')}',
|
|
243
|
+
);
|
|
244
|
+
stderr.writeln('');
|
|
245
|
+
|
|
246
|
+
if (subNames.isNotEmpty) {
|
|
247
|
+
stderr.writeln(' ${_cyan('Did you mean?')}');
|
|
248
|
+
for (final s in subNames) {
|
|
249
|
+
stderr.writeln(' ${_green('${parent.name} $s')}');
|
|
250
|
+
}
|
|
251
|
+
stderr.writeln('');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
parent.printUsage();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── Minimal Levenshtein distance ──────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
int _levenshtein(String a, String b) {
|
|
260
|
+
if (a.isEmpty) return b.length;
|
|
261
|
+
if (b.isEmpty) return a.length;
|
|
262
|
+
final dp = List.generate(a.length + 1, (i) => List.filled(b.length + 1, 0));
|
|
263
|
+
for (var i = 0; i <= a.length; i++) dp[i][0] = i;
|
|
264
|
+
for (var j = 0; j <= b.length; j++) dp[0][j] = j;
|
|
265
|
+
for (var i = 1; i <= a.length; i++) {
|
|
266
|
+
for (var j = 1; j <= b.length; j++) {
|
|
267
|
+
final cost = a[i - 1] == b[j - 1] ? 0 : 1;
|
|
268
|
+
dp[i][j] = [
|
|
269
|
+
dp[i - 1][j] + 1,
|
|
270
|
+
dp[i][j - 1] + 1,
|
|
271
|
+
dp[i - 1][j - 1] + cost,
|
|
272
|
+
].reduce((a, b) => a < b ? a : b);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return dp[a.length][b.length];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Minimal inline color helpers (stderr-safe, TTY-aware) ────────────────────
|
|
279
|
+
|
|
280
|
+
bool get _noColor {
|
|
281
|
+
final v = Platform.environment['NO_COLOR'];
|
|
282
|
+
return v != null && v.isNotEmpty;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
bool get _colorOn => !_noColor && stderr.hasTerminal && stderr.supportsAnsiEscapes;
|
|
286
|
+
|
|
287
|
+
String _red(String s) => _colorOn ? '\x1b[31m$s\x1b[0m' : s;
|
|
288
|
+
String _green(String s) => _colorOn ? '\x1b[32m$s\x1b[0m' : s;
|
|
289
|
+
String _cyan(String s) => _colorOn ? '\x1b[36m$s\x1b[0m' : s;
|
|
@@ -59,27 +59,6 @@ class BundleCommand extends FlutterPatcherCommand {
|
|
|
59
59
|
@override
|
|
60
60
|
String get description =>
|
|
61
61
|
'Manage bundles (list / show / delete / disable / enable / force / promote).';
|
|
62
|
-
|
|
63
|
-
@override
|
|
64
|
-
Future<int> run() => runGuarded(() async {
|
|
65
|
-
print(description);
|
|
66
|
-
print('');
|
|
67
|
-
print('Subcommands:');
|
|
68
|
-
print(
|
|
69
|
-
' list List bundles (filters: --channel/-c, --platform/-p, --enabled, --limit/-l)',
|
|
70
|
-
);
|
|
71
|
-
print(' show --id <id> Show a single bundle\'s details');
|
|
72
|
-
print(' delete --id <id> Delete a bundle');
|
|
73
|
-
print(' disable --id <id> Disable a bundle (stop serving it)');
|
|
74
|
-
print(' enable --id <id> Enable a bundle');
|
|
75
|
-
print(
|
|
76
|
-
' force --id <id> Force an update onto clients (--off to clear)',
|
|
77
|
-
);
|
|
78
|
-
print(' promote --id <id> -c Promote a bundle to a channel');
|
|
79
|
-
print(
|
|
80
|
-
' update --id <id> ... Edit metadata (--message/--target-version/--enabled/--force)',
|
|
81
|
-
);
|
|
82
|
-
});
|
|
83
62
|
}
|
|
84
63
|
|
|
85
64
|
class BundleListCommand extends FlutterPatcherCommand {
|
|
@@ -225,19 +204,10 @@ class BundleDeleteCommand extends FlutterPatcherCommand {
|
|
|
225
204
|
}
|
|
226
205
|
final keepStorage = argResults!['keep-storage'] as bool;
|
|
227
206
|
final steps = Steps('delete');
|
|
228
|
-
await
|
|
229
|
-
() => deleteBundle(backend, id),
|
|
230
|
-
'Deleting bundle $id',
|
|
231
|
-
done: 'Deleted',
|
|
232
|
-
);
|
|
233
|
-
steps.success('Deleted bundle $id');
|
|
207
|
+
await steps.run('Deleting bundle $id', () => deleteBundle(backend, id));
|
|
234
208
|
if (!keepStorage && existing.storageUri.isNotEmpty) {
|
|
235
|
-
await
|
|
236
|
-
|
|
237
|
-
'Removing storage object',
|
|
238
|
-
done: 'Storage removed',
|
|
239
|
-
);
|
|
240
|
-
steps.success('Removed storage object');
|
|
209
|
+
await steps.run('Removing storage object',
|
|
210
|
+
() => backend.storage.delete(existing.storageUri));
|
|
241
211
|
}
|
|
242
212
|
steps.summary();
|
|
243
213
|
});
|
|
@@ -265,14 +235,12 @@ class BundleDisableCommand extends FlutterPatcherCommand {
|
|
|
265
235
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
266
236
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
267
237
|
banner('bundle · disable');
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
done: 'Disabled',
|
|
275
|
-
);
|
|
238
|
+
final steps = Steps('disable');
|
|
239
|
+
await steps.run('Disabling bundle $id', () async {
|
|
240
|
+
await backend.db.updateBundle(id!, {'enabled': false});
|
|
241
|
+
await backend.db.commitBundle();
|
|
242
|
+
});
|
|
243
|
+
steps.summary();
|
|
276
244
|
});
|
|
277
245
|
}
|
|
278
246
|
|
|
@@ -298,14 +266,12 @@ class BundleEnableCommand extends FlutterPatcherCommand {
|
|
|
298
266
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
299
267
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
300
268
|
banner('bundle · enable');
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
done: 'Enabled',
|
|
308
|
-
);
|
|
269
|
+
final steps = Steps('enable');
|
|
270
|
+
await steps.run('Enabling bundle $id', () async {
|
|
271
|
+
await backend.db.updateBundle(id!, {'enabled': true});
|
|
272
|
+
await backend.db.commitBundle();
|
|
273
|
+
});
|
|
274
|
+
steps.summary();
|
|
309
275
|
});
|
|
310
276
|
}
|
|
311
277
|
|
|
@@ -336,7 +302,9 @@ class BundleForceCommand extends FlutterPatcherCommand {
|
|
|
336
302
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
337
303
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
338
304
|
banner('bundle · force');
|
|
339
|
-
|
|
305
|
+
final steps = Steps('force');
|
|
306
|
+
await steps.run(
|
|
307
|
+
off ? 'Clearing force flag on $id' : 'Forcing update for $id',
|
|
340
308
|
() async {
|
|
341
309
|
await backend.db.updateBundle(id!, {
|
|
342
310
|
'shouldForceUpdate': !off,
|
|
@@ -344,9 +312,8 @@ class BundleForceCommand extends FlutterPatcherCommand {
|
|
|
344
312
|
});
|
|
345
313
|
await backend.db.commitBundle();
|
|
346
314
|
},
|
|
347
|
-
off ? 'Clearing force flag on $id' : 'Forcing update for $id',
|
|
348
|
-
done: off ? 'Cleared' : 'Forced',
|
|
349
315
|
);
|
|
316
|
+
steps.summary();
|
|
350
317
|
});
|
|
351
318
|
}
|
|
352
319
|
|
|
@@ -380,11 +347,10 @@ class BundlePromoteCommand extends FlutterPatcherCommand {
|
|
|
380
347
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
381
348
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
382
349
|
banner('bundle · promote');
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
);
|
|
350
|
+
final steps = Steps('promote');
|
|
351
|
+
await steps.run('Promoting $id to $channel',
|
|
352
|
+
() => promoteBundle(backend, id!, channel));
|
|
353
|
+
steps.summary();
|
|
388
354
|
box('promote', [kv('bundle', cyan(id!)), kv('channel', channel)]);
|
|
389
355
|
});
|
|
390
356
|
}
|
|
@@ -432,15 +398,13 @@ class BundleUpdateCommand extends FlutterPatcherCommand {
|
|
|
432
398
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
433
399
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
434
400
|
banner('bundle · update');
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
'Updating $id',
|
|
441
|
-
done: 'Updated',
|
|
442
|
-
);
|
|
401
|
+
final steps = Steps('update');
|
|
402
|
+
await steps.run('Updating $id', () async {
|
|
403
|
+
await backend.db.updateBundle(id!, patch);
|
|
404
|
+
await backend.db.commitBundle();
|
|
405
|
+
});
|
|
443
406
|
final b = await backend.db.getBundleById(id!);
|
|
407
|
+
steps.summary();
|
|
444
408
|
if (b != null) {
|
|
445
409
|
box('bundle', [
|
|
446
410
|
kv('id', cyan(b.id)),
|
|
@@ -24,16 +24,6 @@ class ChannelCommand extends FlutterPatcherCommand {
|
|
|
24
24
|
|
|
25
25
|
@override
|
|
26
26
|
String get description => 'Manage channels (list / get / set).';
|
|
27
|
-
|
|
28
|
-
@override
|
|
29
|
-
Future<int> run() => runGuarded(() async {
|
|
30
|
-
print(description);
|
|
31
|
-
print('');
|
|
32
|
-
print('Subcommands:');
|
|
33
|
-
print(' list List channels');
|
|
34
|
-
print(' get <channel> Show the live bundle for a channel');
|
|
35
|
-
print(' set <c> <id> Promote a bundle to a channel');
|
|
36
|
-
});
|
|
37
27
|
}
|
|
38
28
|
|
|
39
29
|
class ChannelListCommand extends FlutterPatcherCommand {
|
|
@@ -140,11 +130,10 @@ class ChannelSetCommand extends FlutterPatcherCommand {
|
|
|
140
130
|
}
|
|
141
131
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
142
132
|
banner('channel · set');
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
133
|
+
final steps = Steps('set');
|
|
134
|
+
await steps.run('Promoting $bundleId to $channel',
|
|
135
|
+
() => promoteBundle(backend, bundleId, channel));
|
|
136
|
+
steps.summary();
|
|
148
137
|
box('channel set', [kv('channel', channel), kv('bundle', cyan(bundleId))]);
|
|
149
138
|
});
|
|
150
139
|
}
|
|
@@ -18,17 +18,6 @@ class ConfigCommand extends FlutterPatcherCommand {
|
|
|
18
18
|
|
|
19
19
|
@override
|
|
20
20
|
String get description => 'Get, set, or list configuration values.';
|
|
21
|
-
|
|
22
|
-
@override
|
|
23
|
-
Future<int> run() => runGuarded(() async {
|
|
24
|
-
// No subcommand selected.
|
|
25
|
-
print(description);
|
|
26
|
-
print('');
|
|
27
|
-
print('Subcommands:');
|
|
28
|
-
print(' get <key>');
|
|
29
|
-
print(' set <key> <value>');
|
|
30
|
-
print(' list');
|
|
31
|
-
});
|
|
32
21
|
}
|
|
33
22
|
|
|
34
23
|
Map<String, dynamic> _loadProjectJson() {
|
|
@@ -59,6 +48,7 @@ class ConfigGetCommand extends FlutterPatcherCommand {
|
|
|
59
48
|
|
|
60
49
|
@override
|
|
61
50
|
Future<int> run() => runGuarded(() async {
|
|
51
|
+
ui.banner('config · get');
|
|
62
52
|
final key = argResults!['key'] as String? ??
|
|
63
53
|
(argResults!.rest.isNotEmpty ? argResults!.rest.first : null);
|
|
64
54
|
if (key == null || key.isEmpty) {
|
|
@@ -66,7 +56,7 @@ class ConfigGetCommand extends FlutterPatcherCommand {
|
|
|
66
56
|
}
|
|
67
57
|
final value = readPath(_loadProjectJson(), key);
|
|
68
58
|
if (value == null) {
|
|
69
|
-
|
|
59
|
+
ui.warn('(not set)');
|
|
70
60
|
return;
|
|
71
61
|
}
|
|
72
62
|
stdout.writeln(
|
|
@@ -39,6 +39,7 @@ class ConsoleCommand extends FlutterPatcherCommand {
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
if (argResults!['open'] as bool) {
|
|
42
|
+
banner('console · open');
|
|
42
43
|
step('Launching console...');
|
|
43
44
|
final process = await Process.start(
|
|
44
45
|
'flutter',
|
|
@@ -47,7 +48,8 @@ class ConsoleCommand extends FlutterPatcherCommand {
|
|
|
47
48
|
runInShell: true,
|
|
48
49
|
mode: ProcessStartMode.inheritStdio,
|
|
49
50
|
);
|
|
50
|
-
|
|
51
|
+
final code = await process.exitCode;
|
|
52
|
+
if (code != 0) err('flutter run exited with code $code');
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
53
55
|
banner('console');
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import 'dart:io';
|
|
2
2
|
|
|
3
|
+
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
|
|
4
|
+
|
|
3
5
|
import '../backend.dart';
|
|
4
6
|
import '../cli_base.dart';
|
|
5
7
|
import '../config.dart';
|
|
@@ -73,31 +75,13 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
73
75
|
final bundleId = argResults!['bundle-id'] as String?;
|
|
74
76
|
|
|
75
77
|
banner('deploy');
|
|
76
|
-
|
|
77
|
-
' ${gray('channel')} ${cyan(channel)} '
|
|
78
|
-
'${gray('platform')} ${cyan(platform)} '
|
|
79
|
-
'${gray('source')} ${dim(source)}',
|
|
80
|
-
);
|
|
78
|
+
info('channel ${cyan(channel)} platform ${cyan(platform)} source ${dim(source)}');
|
|
81
79
|
|
|
82
|
-
final
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
channel: channel,
|
|
88
|
-
platform: platform,
|
|
89
|
-
message: message,
|
|
90
|
-
force: force,
|
|
91
|
-
targetAppVersion: targetAppVersion,
|
|
92
|
-
fingerprintHash: fingerprintHash,
|
|
93
|
-
signingKeyBase64: signingKey,
|
|
94
|
-
gitCommitHash: resolvedGitCommitHash,
|
|
95
|
-
bundleId: bundleId,
|
|
96
|
-
),
|
|
97
|
-
),
|
|
98
|
-
'Uploading & registering bundle',
|
|
99
|
-
done: 'Bundle deployed',
|
|
100
|
-
);
|
|
80
|
+
final steps = Steps('deploy');
|
|
81
|
+
final bundle = await _deployWithPhases(steps, backend, source, channel,
|
|
82
|
+
platform, message, force, targetAppVersion, fingerprintHash,
|
|
83
|
+
signingKey, resolvedGitCommitHash, bundleId);
|
|
84
|
+
steps.summary();
|
|
101
85
|
|
|
102
86
|
final lines = <String>[
|
|
103
87
|
kv('bundle id', cyan(bundle.id)),
|
|
@@ -122,3 +106,57 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
122
106
|
box('deployed', lines);
|
|
123
107
|
});
|
|
124
108
|
}
|
|
109
|
+
|
|
110
|
+
/// Deploy with per-phase UI. Each phase reported by `DeployOptions.onPhase`
|
|
111
|
+
/// is shown as an active spinner line that gets replaced in-place.
|
|
112
|
+
Future<Bundle> _deployWithPhases(
|
|
113
|
+
Steps steps,
|
|
114
|
+
Backend backend,
|
|
115
|
+
String source,
|
|
116
|
+
String channel,
|
|
117
|
+
String platform,
|
|
118
|
+
String? message,
|
|
119
|
+
bool force,
|
|
120
|
+
String? targetAppVersion,
|
|
121
|
+
String? fingerprintHash,
|
|
122
|
+
String? signingKey,
|
|
123
|
+
String? resolvedGitCommitHash,
|
|
124
|
+
String? bundleId,
|
|
125
|
+
) async {
|
|
126
|
+
String? activeLabel;
|
|
127
|
+
try {
|
|
128
|
+
final bundle = await deployBundle(
|
|
129
|
+
backend,
|
|
130
|
+
DeployOptions(
|
|
131
|
+
source: source,
|
|
132
|
+
channel: channel,
|
|
133
|
+
platform: platform,
|
|
134
|
+
message: message,
|
|
135
|
+
force: force,
|
|
136
|
+
targetAppVersion: targetAppVersion,
|
|
137
|
+
fingerprintHash: fingerprintHash,
|
|
138
|
+
signingKeyBase64: signingKey,
|
|
139
|
+
gitCommitHash: resolvedGitCommitHash,
|
|
140
|
+
bundleId: bundleId,
|
|
141
|
+
onPhase: (phase) {
|
|
142
|
+
if (activeLabel != null) {
|
|
143
|
+
steps.completeActive();
|
|
144
|
+
}
|
|
145
|
+
activeLabel = phase;
|
|
146
|
+
steps.startActive(phase);
|
|
147
|
+
},
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
if (activeLabel != null) {
|
|
151
|
+
steps.completeActive();
|
|
152
|
+
activeLabel = null;
|
|
153
|
+
}
|
|
154
|
+
return bundle;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
if (activeLabel != null) {
|
|
157
|
+
steps.completeActive(success: false);
|
|
158
|
+
activeLabel = null;
|
|
159
|
+
}
|
|
160
|
+
rethrow;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import 'dart:io';
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
import '../cli_base.dart';
|
|
2
|
+
import '../ui/ui.dart';
|
|
5
3
|
import '../util.dart';
|
|
6
4
|
|
|
7
5
|
/// `flutter_ota_kit fingerprint` — compute a build-time fingerprint hash.
|
|
@@ -24,8 +22,9 @@ class FingerprintCommand extends FlutterPatcherCommand {
|
|
|
24
22
|
|
|
25
23
|
@override
|
|
26
24
|
Future<int> run() => runGuarded(() async {
|
|
25
|
+
banner(name);
|
|
27
26
|
final source = argResults!['source'] as String;
|
|
28
27
|
final hash = generateFingerprint(source);
|
|
29
|
-
|
|
28
|
+
box('fingerprint', [kv('source', source), kv('hash', hash)]);
|
|
30
29
|
});
|
|
31
30
|
}
|