@_nazmiforreal/flutter-ota 0.1.21 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/flutter-ota-linux-x64 +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +460 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +707 -0
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +7 -4
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +95 -32
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +28 -12
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +9 -4
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +29 -6
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +20 -32
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +40 -19
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +7 -5
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +33 -22
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +256 -68
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +17 -11
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +28 -12
- package/dart-src/packages/cli-tools/lib/src/config.dart +5 -6
- package/dart-src/packages/cli-tools/lib/src/operations.dart +1 -3
- package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +17 -16
- package/dart-src/packages/cli-tools/lib/src/runner.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +8 -14
- package/dart-src/packages/cli-tools/pubspec.lock +590 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +7 -3
- package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +13 -16
- package/dart-src/packages/core/CHANGELOG.md +15 -0
- package/dart-src/packages/core/lib/src/bundle.dart +3 -6
- package/dart-src/packages/core/pubspec.yaml +1 -1
- package/dart-src/plugins/aws/CHANGELOG.md +18 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +1 -2
- package/dart-src/plugins/aws/pubspec.yaml +3 -3
- package/dart-src/plugins/cloudflare/CHANGELOG.md +18 -0
- package/dart-src/plugins/cloudflare/pubspec.yaml +3 -3
- package/dart-src/plugins/plugin-core/CHANGELOG.md +15 -0
- package/dart-src/plugins/plugin-core/pubspec.yaml +2 -2
- package/dart-src/plugins/pocketbase/CHANGELOG.md +18 -0
- package/dart-src/plugins/pocketbase/pubspec.yaml +3 -3
- package/dart-src/plugins/postgres/CHANGELOG.md +18 -0
- package/dart-src/plugins/postgres/pubspec.yaml +3 -3
- package/dart-src/plugins/supabase/CHANGELOG.md +18 -0
- package/dart-src/plugins/supabase/pubspec.yaml +3 -3
- package/package.json +2 -2
- package/scripts/postinstall.js +5 -1
|
@@ -103,8 +103,8 @@ Future<int> run(List<String> args) async {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Unknown command → fuzzy-match and suggest.
|
|
106
|
-
final unknownMatch =
|
|
107
|
-
|
|
106
|
+
final unknownMatch = RegExp(r'Could not find a command named "?(\w+)"?')
|
|
107
|
+
.firstMatch(msg);
|
|
108
108
|
if (unknownMatch != null) {
|
|
109
109
|
final typed = unknownMatch.group(1)!;
|
|
110
110
|
_printUnknownCommand(runner, typed);
|
|
@@ -191,7 +191,9 @@ void _printUnknownCommand(FlutterPatcherRunner runner, String typed) {
|
|
|
191
191
|
.where((s) => s.startsWith(sub) || _levenshtein(sub, s) <= 2)
|
|
192
192
|
.toList();
|
|
193
193
|
if (subSuggestion.isNotEmpty) {
|
|
194
|
-
stderr.writeln(
|
|
194
|
+
stderr.writeln(
|
|
195
|
+
' ${_cyan('Did you mean?')} $parentCmd ${_green(subSuggestion.first)}',
|
|
196
|
+
);
|
|
195
197
|
stderr.writeln('');
|
|
196
198
|
}
|
|
197
199
|
cmd.printUsage();
|
|
@@ -283,7 +285,8 @@ bool get _noColor {
|
|
|
283
285
|
return v != null && v.isNotEmpty;
|
|
284
286
|
}
|
|
285
287
|
|
|
286
|
-
bool get _colorOn =>
|
|
288
|
+
bool get _colorOn =>
|
|
289
|
+
!_noColor && stderr.hasTerminal && stderr.supportsAnsiEscapes;
|
|
287
290
|
|
|
288
291
|
String _red(String s) => _colorOn ? '\x1b[31m$s\x1b[0m' : s;
|
|
289
292
|
String _green(String s) => _colorOn ? '\x1b[32m$s\x1b[0m' : s;
|
|
@@ -66,13 +66,22 @@ class BundleCommand extends FlutterPatcherCommand {
|
|
|
66
66
|
class BundleListCommand extends FlutterPatcherCommand {
|
|
67
67
|
BundleListCommand({this.config, this.backendOverride}) {
|
|
68
68
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
69
|
-
argParser.addOption(
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
argParser.addOption(
|
|
70
|
+
'backend',
|
|
71
|
+
abbr: 'b',
|
|
72
|
+
help: detected != null
|
|
73
|
+
? 'Backend provider [detected: $detected].'
|
|
74
|
+
: 'Backend provider.',
|
|
75
|
+
);
|
|
72
76
|
argParser.addOption('channel', abbr: 'c', help: 'Filter by channel.');
|
|
73
77
|
argParser.addOption('platform', abbr: 'p', help: 'Filter by platform.');
|
|
74
78
|
argParser.addOption('enabled', help: 'Filter by enabled (true/false).');
|
|
75
|
-
argParser.addOption(
|
|
79
|
+
argParser.addOption(
|
|
80
|
+
'limit',
|
|
81
|
+
abbr: 'l',
|
|
82
|
+
defaultsTo: '20',
|
|
83
|
+
help: 'Page size.',
|
|
84
|
+
);
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
final FlutterPatcherConfig? config;
|
|
@@ -95,7 +104,10 @@ class BundleListCommand extends FlutterPatcherCommand {
|
|
|
95
104
|
final limitRaw = argResults!['limit'] as String;
|
|
96
105
|
final limit = int.tryParse(limitRaw);
|
|
97
106
|
if (limit == null || limit < 1) {
|
|
98
|
-
throw PackException(
|
|
107
|
+
throw PackException(
|
|
108
|
+
'--limit must be a positive integer (got "$limitRaw")',
|
|
109
|
+
64,
|
|
110
|
+
);
|
|
99
111
|
}
|
|
100
112
|
final res = await listBundles(
|
|
101
113
|
backend,
|
|
@@ -116,27 +128,29 @@ class BundleListCommand extends FlutterPatcherCommand {
|
|
|
116
128
|
final b = res.data[i];
|
|
117
129
|
final enabled = b.enabled ? green('✓') : red('✗');
|
|
118
130
|
final force = b.shouldForceUpdate ? green('✓') : dim('✗');
|
|
119
|
-
rows.add([
|
|
120
|
-
'$i',
|
|
121
|
-
cyan(b.id),
|
|
122
|
-
b.channel,
|
|
123
|
-
b.platform.value,
|
|
124
|
-
force,
|
|
125
|
-
enabled,
|
|
126
|
-
]);
|
|
131
|
+
rows.add(['$i', cyan(b.id), b.channel, b.platform.value, force, enabled]);
|
|
127
132
|
}
|
|
128
|
-
table(
|
|
129
|
-
'
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
table('${res.data.length} bundles (total: ${res.pagination.total})', [
|
|
134
|
+
'#',
|
|
135
|
+
'ID',
|
|
136
|
+
'CHANNEL',
|
|
137
|
+
'PLAT',
|
|
138
|
+
'FORCE',
|
|
139
|
+
'ON',
|
|
140
|
+
], rows);
|
|
133
141
|
});
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
class BundleShowCommand extends FlutterPatcherCommand {
|
|
137
145
|
BundleShowCommand({this.config, this.backendOverride}) {
|
|
138
146
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
139
|
-
argParser.addOption(
|
|
147
|
+
argParser.addOption(
|
|
148
|
+
'backend',
|
|
149
|
+
abbr: 'b',
|
|
150
|
+
help: detected != null
|
|
151
|
+
? 'Backend provider [detected: $detected].'
|
|
152
|
+
: 'Backend provider.',
|
|
153
|
+
);
|
|
140
154
|
argParser.addOption('id', help: 'Bundle id.');
|
|
141
155
|
}
|
|
142
156
|
|
|
@@ -180,7 +194,13 @@ class BundleShowCommand extends FlutterPatcherCommand {
|
|
|
180
194
|
class BundleDeleteCommand extends FlutterPatcherCommand {
|
|
181
195
|
BundleDeleteCommand({this.config, this.backendOverride}) {
|
|
182
196
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
183
|
-
argParser.addOption(
|
|
197
|
+
argParser.addOption(
|
|
198
|
+
'backend',
|
|
199
|
+
abbr: 'b',
|
|
200
|
+
help: detected != null
|
|
201
|
+
? 'Backend provider [detected: $detected].'
|
|
202
|
+
: 'Backend provider.',
|
|
203
|
+
);
|
|
184
204
|
argParser.addOption('id', help: 'Bundle id.');
|
|
185
205
|
argParser.addFlag(
|
|
186
206
|
'keep-storage',
|
|
@@ -213,8 +233,10 @@ class BundleDeleteCommand extends FlutterPatcherCommand {
|
|
|
213
233
|
final steps = Steps('delete');
|
|
214
234
|
await steps.run('Deleting bundle $id', () => deleteBundle(backend, id));
|
|
215
235
|
if (!keepStorage && existing.storageUri.isNotEmpty) {
|
|
216
|
-
await steps.run(
|
|
217
|
-
|
|
236
|
+
await steps.run(
|
|
237
|
+
'Removing storage object',
|
|
238
|
+
() => backend.storage.delete(existing.storageUri),
|
|
239
|
+
);
|
|
218
240
|
}
|
|
219
241
|
steps.summary();
|
|
220
242
|
});
|
|
@@ -223,7 +245,13 @@ class BundleDeleteCommand extends FlutterPatcherCommand {
|
|
|
223
245
|
class BundleDisableCommand extends FlutterPatcherCommand {
|
|
224
246
|
BundleDisableCommand({this.config, this.backendOverride}) {
|
|
225
247
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
226
|
-
argParser.addOption(
|
|
248
|
+
argParser.addOption(
|
|
249
|
+
'backend',
|
|
250
|
+
abbr: 'b',
|
|
251
|
+
help: detected != null
|
|
252
|
+
? 'Backend provider [detected: $detected].'
|
|
253
|
+
: 'Backend provider.',
|
|
254
|
+
);
|
|
227
255
|
argParser.addOption('id', help: 'Bundle id.');
|
|
228
256
|
}
|
|
229
257
|
|
|
@@ -255,7 +283,13 @@ class BundleDisableCommand extends FlutterPatcherCommand {
|
|
|
255
283
|
class BundleEnableCommand extends FlutterPatcherCommand {
|
|
256
284
|
BundleEnableCommand({this.config, this.backendOverride}) {
|
|
257
285
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
258
|
-
argParser.addOption(
|
|
286
|
+
argParser.addOption(
|
|
287
|
+
'backend',
|
|
288
|
+
abbr: 'b',
|
|
289
|
+
help: detected != null
|
|
290
|
+
? 'Backend provider [detected: $detected].'
|
|
291
|
+
: 'Backend provider.',
|
|
292
|
+
);
|
|
259
293
|
argParser.addOption('id', help: 'Bundle id.');
|
|
260
294
|
}
|
|
261
295
|
|
|
@@ -287,7 +321,13 @@ class BundleEnableCommand extends FlutterPatcherCommand {
|
|
|
287
321
|
class BundleForceCommand extends FlutterPatcherCommand {
|
|
288
322
|
BundleForceCommand({this.config, this.backendOverride}) {
|
|
289
323
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
290
|
-
argParser.addOption(
|
|
324
|
+
argParser.addOption(
|
|
325
|
+
'backend',
|
|
326
|
+
abbr: 'b',
|
|
327
|
+
help: detected != null
|
|
328
|
+
? 'Backend provider [detected: $detected].'
|
|
329
|
+
: 'Backend provider.',
|
|
330
|
+
);
|
|
291
331
|
argParser.addOption('id', help: 'Bundle id.');
|
|
292
332
|
argParser.addFlag(
|
|
293
333
|
'off',
|
|
@@ -330,7 +370,13 @@ class BundleForceCommand extends FlutterPatcherCommand {
|
|
|
330
370
|
class BundlePromoteCommand extends FlutterPatcherCommand {
|
|
331
371
|
BundlePromoteCommand({this.config, this.backendOverride}) {
|
|
332
372
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
333
|
-
argParser.addOption(
|
|
373
|
+
argParser.addOption(
|
|
374
|
+
'backend',
|
|
375
|
+
abbr: 'b',
|
|
376
|
+
help: detected != null
|
|
377
|
+
? 'Backend provider [detected: $detected].'
|
|
378
|
+
: 'Backend provider.',
|
|
379
|
+
);
|
|
334
380
|
argParser.addOption('id', help: 'Bundle id.');
|
|
335
381
|
argParser.addOption('channel', abbr: 'c', help: 'Target channel.');
|
|
336
382
|
}
|
|
@@ -359,22 +405,39 @@ class BundlePromoteCommand extends FlutterPatcherCommand {
|
|
|
359
405
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
360
406
|
banner('bundle · promote');
|
|
361
407
|
final steps = Steps('promote');
|
|
362
|
-
await steps.run(
|
|
363
|
-
|
|
408
|
+
await steps.run(
|
|
409
|
+
'Promoting $id to $channel',
|
|
410
|
+
() => promoteBundle(backend, id!, channel),
|
|
411
|
+
);
|
|
364
412
|
steps.summary();
|
|
365
|
-
stdout.writeln(
|
|
413
|
+
stdout.writeln(
|
|
414
|
+
' ${dim('→')} bundle ${cyan(id!)} → channel ${cyan(channel)}',
|
|
415
|
+
);
|
|
366
416
|
});
|
|
367
417
|
}
|
|
368
418
|
|
|
369
419
|
class BundleUpdateCommand extends FlutterPatcherCommand {
|
|
370
420
|
BundleUpdateCommand({this.config, this.backendOverride}) {
|
|
371
421
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
372
|
-
argParser.addOption(
|
|
422
|
+
argParser.addOption(
|
|
423
|
+
'backend',
|
|
424
|
+
abbr: 'b',
|
|
425
|
+
help: detected != null
|
|
426
|
+
? 'Backend provider [detected: $detected].'
|
|
427
|
+
: 'Backend provider.',
|
|
428
|
+
);
|
|
373
429
|
argParser.addOption('id', help: 'Bundle id.');
|
|
374
430
|
argParser.addOption('message', abbr: 'm', help: 'New release message.');
|
|
375
|
-
argParser.addOption(
|
|
431
|
+
argParser.addOption(
|
|
432
|
+
'target-version',
|
|
433
|
+
help: 'New target app version (e.g. 1.0.0).',
|
|
434
|
+
);
|
|
376
435
|
argParser.addOption('enabled', help: 'Set enabled (true/false).');
|
|
377
|
-
argParser.addOption(
|
|
436
|
+
argParser.addOption(
|
|
437
|
+
'force',
|
|
438
|
+
abbr: 'f',
|
|
439
|
+
help: 'Set force-update (true/false).',
|
|
440
|
+
);
|
|
378
441
|
}
|
|
379
442
|
|
|
380
443
|
final FlutterPatcherConfig? config;
|
|
@@ -31,9 +31,13 @@ class ChannelCommand extends FlutterPatcherCommand {
|
|
|
31
31
|
class ChannelListCommand extends FlutterPatcherCommand {
|
|
32
32
|
ChannelListCommand({this.config, this.backendOverride}) {
|
|
33
33
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
34
|
-
argParser.addOption(
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
argParser.addOption(
|
|
35
|
+
'backend',
|
|
36
|
+
abbr: 'b',
|
|
37
|
+
help: detected != null
|
|
38
|
+
? 'Backend provider [detected: $detected].'
|
|
39
|
+
: 'Backend provider.',
|
|
40
|
+
);
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
final FlutterPatcherConfig? config;
|
|
@@ -62,9 +66,13 @@ class ChannelListCommand extends FlutterPatcherCommand {
|
|
|
62
66
|
class ChannelGetCommand extends FlutterPatcherCommand {
|
|
63
67
|
ChannelGetCommand({this.config, this.backendOverride}) {
|
|
64
68
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
65
|
-
argParser.addOption(
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
argParser.addOption(
|
|
70
|
+
'backend',
|
|
71
|
+
abbr: 'b',
|
|
72
|
+
help: detected != null
|
|
73
|
+
? 'Backend provider [detected: $detected].'
|
|
74
|
+
: 'Backend provider.',
|
|
75
|
+
);
|
|
68
76
|
argParser.addOption('channel', abbr: 'c', help: 'Channel.');
|
|
69
77
|
}
|
|
70
78
|
|
|
@@ -109,9 +117,13 @@ class ChannelGetCommand extends FlutterPatcherCommand {
|
|
|
109
117
|
class ChannelSetCommand extends FlutterPatcherCommand {
|
|
110
118
|
ChannelSetCommand({this.config, this.backendOverride}) {
|
|
111
119
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
112
|
-
argParser.addOption(
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
argParser.addOption(
|
|
121
|
+
'backend',
|
|
122
|
+
abbr: 'b',
|
|
123
|
+
help: detected != null
|
|
124
|
+
? 'Backend provider [detected: $detected].'
|
|
125
|
+
: 'Backend provider.',
|
|
126
|
+
);
|
|
115
127
|
argParser.addOption('channel', abbr: 'c', help: 'Channel.');
|
|
116
128
|
argParser.addOption('bundle-id', abbr: 'i', help: 'Bundle id.');
|
|
117
129
|
}
|
|
@@ -142,9 +154,13 @@ class ChannelSetCommand extends FlutterPatcherCommand {
|
|
|
142
154
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
143
155
|
banner('channel · set');
|
|
144
156
|
final steps = Steps('set');
|
|
145
|
-
await steps.run(
|
|
146
|
-
|
|
157
|
+
await steps.run(
|
|
158
|
+
'Promoting $bundleId to $channel',
|
|
159
|
+
() => promoteBundle(backend, bundleId, channel),
|
|
160
|
+
);
|
|
147
161
|
steps.summary();
|
|
148
|
-
stdout.writeln(
|
|
162
|
+
stdout.writeln(
|
|
163
|
+
' ${dim('→')} channel ${cyan(channel)} = ${cyan(bundleId)}',
|
|
164
|
+
);
|
|
149
165
|
});
|
|
150
166
|
}
|
|
@@ -52,7 +52,8 @@ class ConfigGetCommand extends FlutterPatcherCommand {
|
|
|
52
52
|
@override
|
|
53
53
|
Future<int> run() => runGuarded(() async {
|
|
54
54
|
ui.banner('config · get');
|
|
55
|
-
final key =
|
|
55
|
+
final key =
|
|
56
|
+
argResults!['key'] as String? ??
|
|
56
57
|
(argResults!.rest.isNotEmpty ? argResults!.rest.first : null);
|
|
57
58
|
if (key == null || key.isEmpty) {
|
|
58
59
|
throw StateError('Usage: flutter-ota config get <key>');
|
|
@@ -82,9 +83,11 @@ class ConfigSetCommand extends FlutterPatcherCommand {
|
|
|
82
83
|
|
|
83
84
|
@override
|
|
84
85
|
Future<int> run() => runGuarded(() async {
|
|
85
|
-
final key =
|
|
86
|
+
final key =
|
|
87
|
+
argResults!['key'] as String? ??
|
|
86
88
|
(argResults!.rest.isNotEmpty ? argResults!.rest.first : null);
|
|
87
|
-
final value =
|
|
89
|
+
final value =
|
|
90
|
+
argResults!['value'] as String? ??
|
|
88
91
|
(argResults!.rest.length > 1 ? argResults!.rest[1] : null);
|
|
89
92
|
if (key == null || key.isEmpty || value == null) {
|
|
90
93
|
throw StateError('Usage: flutter-ota config set <key> <value>');
|
|
@@ -124,7 +127,9 @@ void _printConfig(Map<String, dynamic> json, String prefix) {
|
|
|
124
127
|
if (value is Map<String, dynamic>) {
|
|
125
128
|
_printConfig(value, key);
|
|
126
129
|
} else {
|
|
127
|
-
final display = value is String
|
|
130
|
+
final display = value is String
|
|
131
|
+
? value
|
|
132
|
+
: const JsonEncoder().convert(value);
|
|
128
133
|
stdout.writeln(ui.kv(key, display));
|
|
129
134
|
}
|
|
130
135
|
}
|
|
@@ -21,9 +21,18 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
21
21
|
? 'Backend provider [detected: $detected].'
|
|
22
22
|
: 'Backend provider.',
|
|
23
23
|
);
|
|
24
|
-
argParser.addOption(
|
|
24
|
+
argParser.addOption(
|
|
25
|
+
'source',
|
|
26
|
+
abbr: 's',
|
|
27
|
+
help: 'Source directory to zip + upload.',
|
|
28
|
+
);
|
|
25
29
|
argParser.addOption('channel', abbr: 'c', help: 'Target channel.');
|
|
26
|
-
argParser.addOption(
|
|
30
|
+
argParser.addOption(
|
|
31
|
+
'platform',
|
|
32
|
+
abbr: 'p',
|
|
33
|
+
defaultsTo: 'android',
|
|
34
|
+
help: 'Platform.',
|
|
35
|
+
);
|
|
27
36
|
argParser.addOption('message', abbr: 'm', help: 'Release message.');
|
|
28
37
|
argParser.addFlag('force', abbr: 'f', help: 'Force the update on clients.');
|
|
29
38
|
argParser.addOption(
|
|
@@ -39,7 +48,10 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
39
48
|
abbr: 'k',
|
|
40
49
|
help: 'Path to Ed25519 private key file (sign bundle).',
|
|
41
50
|
);
|
|
42
|
-
argParser.addOption(
|
|
51
|
+
argParser.addOption(
|
|
52
|
+
'git-commit-hash',
|
|
53
|
+
help: 'Git commit hash (auto-detected).',
|
|
54
|
+
);
|
|
43
55
|
argParser.addOption(
|
|
44
56
|
'bundle-id',
|
|
45
57
|
abbr: 'i',
|
|
@@ -87,9 +99,20 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
87
99
|
banner('deploy');
|
|
88
100
|
|
|
89
101
|
final steps = Steps('deploy');
|
|
90
|
-
final bundle = await _deployWithPhases(
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
final bundle = await _deployWithPhases(
|
|
103
|
+
steps,
|
|
104
|
+
backend,
|
|
105
|
+
source,
|
|
106
|
+
channel,
|
|
107
|
+
platform,
|
|
108
|
+
message,
|
|
109
|
+
force,
|
|
110
|
+
targetAppVersion,
|
|
111
|
+
fingerprintHash,
|
|
112
|
+
signingKey,
|
|
113
|
+
resolvedGitCommitHash,
|
|
114
|
+
bundleId,
|
|
115
|
+
);
|
|
93
116
|
steps.summary();
|
|
94
117
|
|
|
95
118
|
final lines = <String>[
|
|
@@ -11,9 +11,13 @@ import '../ui/ui.dart';
|
|
|
11
11
|
class DoctorCommand extends FlutterPatcherCommand {
|
|
12
12
|
DoctorCommand({this.config, this.backendOverride}) {
|
|
13
13
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
14
|
-
argParser.addOption(
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
argParser.addOption(
|
|
15
|
+
'backend',
|
|
16
|
+
abbr: 'b',
|
|
17
|
+
help: detected != null
|
|
18
|
+
? 'Backend provider [detected: $detected].'
|
|
19
|
+
: 'Backend provider.',
|
|
20
|
+
);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
final FlutterPatcherConfig? config;
|
|
@@ -59,9 +63,7 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
59
63
|
final time = ms >= 1000
|
|
60
64
|
? '${(ms / 1000).toStringAsFixed(1)}s'
|
|
61
65
|
: '${ms}ms';
|
|
62
|
-
steps.success(
|
|
63
|
-
'Backend reachable — ${channels.join(', ')} in $time',
|
|
64
|
-
);
|
|
66
|
+
steps.success('Backend reachable — ${channels.join(', ')} in $time');
|
|
65
67
|
} catch (e) {
|
|
66
68
|
steps.fail('Backend unreachable — $e');
|
|
67
69
|
}
|
|
@@ -85,19 +87,16 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
Future<void> _checkSupabase(
|
|
89
|
-
FlutterPatcherConfig cfg,
|
|
90
|
-
Steps steps,
|
|
91
|
-
) async {
|
|
90
|
+
Future<void> _checkSupabase(FlutterPatcherConfig cfg, Steps steps) async {
|
|
92
91
|
final url = cfg.supabase.url;
|
|
93
92
|
if (url == null || url.isEmpty) {
|
|
94
93
|
steps.fail('SUPABASE_URL not set');
|
|
95
94
|
return;
|
|
96
95
|
}
|
|
97
96
|
try {
|
|
98
|
-
final res = await http
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
final res = await http
|
|
98
|
+
.get(Uri.parse('$url/rest/v1/'))
|
|
99
|
+
.timeout(const Duration(seconds: 5));
|
|
101
100
|
if (res.statusCode < 400) {
|
|
102
101
|
steps.success('Supabase reachable ($url)');
|
|
103
102
|
} else {
|
|
@@ -108,10 +107,7 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
108
107
|
}
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
Future<void> _checkPostgres(
|
|
112
|
-
FlutterPatcherConfig cfg,
|
|
113
|
-
Steps steps,
|
|
114
|
-
) async {
|
|
110
|
+
Future<void> _checkPostgres(FlutterPatcherConfig cfg, Steps steps) async {
|
|
115
111
|
final host = cfg.postgres.host;
|
|
116
112
|
if (host == null || host.isEmpty) {
|
|
117
113
|
steps.fail('POSTGRES_HOST not set');
|
|
@@ -119,9 +115,10 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
119
115
|
}
|
|
120
116
|
final port = int.tryParse(cfg.postgres.port ?? '5432') ?? 5432;
|
|
121
117
|
try {
|
|
122
|
-
final socket = await Socket.connect(
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
final socket = await Socket.connect(
|
|
119
|
+
host,
|
|
120
|
+
port,
|
|
121
|
+
).timeout(const Duration(seconds: 5));
|
|
125
122
|
await socket.close();
|
|
126
123
|
steps.success('Postgres reachable ($host:$port)');
|
|
127
124
|
} catch (e) {
|
|
@@ -129,10 +126,7 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
129
126
|
}
|
|
130
127
|
}
|
|
131
128
|
|
|
132
|
-
Future<void> _checkCloudflare(
|
|
133
|
-
FlutterPatcherConfig cfg,
|
|
134
|
-
Steps steps,
|
|
135
|
-
) async {
|
|
129
|
+
Future<void> _checkCloudflare(FlutterPatcherConfig cfg, Steps steps) async {
|
|
136
130
|
final accountId = cfg.cloudflare.accountId;
|
|
137
131
|
final apiToken = cfg.cloudflare.apiToken;
|
|
138
132
|
if (accountId == null || accountId.isEmpty) {
|
|
@@ -168,10 +162,7 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
168
162
|
}
|
|
169
163
|
}
|
|
170
164
|
|
|
171
|
-
Future<void> _checkAws(
|
|
172
|
-
FlutterPatcherConfig cfg,
|
|
173
|
-
Steps steps,
|
|
174
|
-
) async {
|
|
165
|
+
Future<void> _checkAws(FlutterPatcherConfig cfg, Steps steps) async {
|
|
175
166
|
final bucket = cfg.aws.bucket;
|
|
176
167
|
if (bucket == null || bucket.isEmpty) {
|
|
177
168
|
steps.fail('AWS_BUCKET not set');
|
|
@@ -185,10 +176,7 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
185
176
|
steps.success('AWS S3 bucket "$bucket" in $region');
|
|
186
177
|
}
|
|
187
178
|
|
|
188
|
-
Future<void> _checkPocketBase(
|
|
189
|
-
FlutterPatcherConfig cfg,
|
|
190
|
-
Steps steps,
|
|
191
|
-
) async {
|
|
179
|
+
Future<void> _checkPocketBase(FlutterPatcherConfig cfg, Steps steps) async {
|
|
192
180
|
final paths = PocketBaseInstallPaths.resolve();
|
|
193
181
|
final installed = await paths.binaryPath.exists();
|
|
194
182
|
if (installed) {
|
|
@@ -31,7 +31,11 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
31
31
|
defaultsTo: 'production',
|
|
32
32
|
help: 'Default channel.',
|
|
33
33
|
);
|
|
34
|
-
argParser.addOption(
|
|
34
|
+
argParser.addOption(
|
|
35
|
+
'platform',
|
|
36
|
+
defaultsTo: 'android',
|
|
37
|
+
help: 'Default platform.',
|
|
38
|
+
);
|
|
35
39
|
argParser.addOption(
|
|
36
40
|
'source',
|
|
37
41
|
abbr: 's',
|
|
@@ -42,7 +46,11 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
42
46
|
'global',
|
|
43
47
|
help: 'Write to the global ~/.flutter_ota_kit config (no scaffolding).',
|
|
44
48
|
);
|
|
45
|
-
argParser.addFlag(
|
|
49
|
+
argParser.addFlag(
|
|
50
|
+
'force',
|
|
51
|
+
abbr: 'f',
|
|
52
|
+
help: 'Overwrite an existing config.',
|
|
53
|
+
);
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
final FlutterPatcherConfig? config;
|
|
@@ -226,10 +234,7 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
226
234
|
'Supabase service role key',
|
|
227
235
|
env['SUPABASE_SERVICE_ROLE_KEY'],
|
|
228
236
|
),
|
|
229
|
-
anonKey: _p(
|
|
230
|
-
'Supabase anon key',
|
|
231
|
-
env['SUPABASE_ANON_KEY'],
|
|
232
|
-
),
|
|
237
|
+
anonKey: _p('Supabase anon key', env['SUPABASE_ANON_KEY']),
|
|
233
238
|
bucket: _p('Storage bucket', env['SUPABASE_BUCKET'] ?? 'bundles'),
|
|
234
239
|
basePath: _p('Storage base path', env['SUPABASE_BASE_PATH']),
|
|
235
240
|
managementKey: _p(
|
|
@@ -248,8 +253,10 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
248
253
|
step('Wrote ${file.path}');
|
|
249
254
|
|
|
250
255
|
if (global) {
|
|
251
|
-
warn(
|
|
252
|
-
|
|
256
|
+
warn(
|
|
257
|
+
'Secrets are stored in plaintext under ~/.flutter_ota_kit. '
|
|
258
|
+
'Restrict permissions and use a secrets manager in production.',
|
|
259
|
+
);
|
|
253
260
|
return;
|
|
254
261
|
}
|
|
255
262
|
|
|
@@ -259,23 +266,33 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
259
266
|
stdout.writeln();
|
|
260
267
|
stdout.writeln(' ${cyan('Next steps:')}');
|
|
261
268
|
step('1. ${dim('flutter pub get')}');
|
|
262
|
-
step(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
269
|
+
step(
|
|
270
|
+
'2. In lib/main.dart call ${green('await setupFlutterOta();')} '
|
|
271
|
+
'right after WidgetsFlutterBinding.ensureInitialized() (before runApp). '
|
|
272
|
+
'A `.env` scaffold was written — put secrets there, then build with '
|
|
273
|
+
'`--dart-define-from-file=.env` (environment overrides config).',
|
|
274
|
+
);
|
|
266
275
|
if (provider == 'supabase') {
|
|
267
|
-
step(
|
|
276
|
+
step(
|
|
277
|
+
'3. Provision the backend once: ${dim('flutter-ota migrate supabase')}',
|
|
278
|
+
);
|
|
268
279
|
} else if (provider == 'postgres') {
|
|
269
280
|
step('3. Provision the backend: ${dim('flutter-ota migrate postgres')}');
|
|
270
281
|
} else if (provider == 'cloudflare') {
|
|
271
|
-
step(
|
|
282
|
+
step(
|
|
283
|
+
'3. Provision the backend: ${dim('flutter-ota migrate cloudflare')}',
|
|
284
|
+
);
|
|
272
285
|
} else if (provider == 'aws') {
|
|
273
286
|
step('3. Provision the backend: ${dim('flutter-ota migrate aws')}');
|
|
274
287
|
} else if (provider == 'pocketbase') {
|
|
275
|
-
step(
|
|
288
|
+
step(
|
|
289
|
+
'3. Provision the backend: ${dim('flutter-ota migrate pocketbase')}',
|
|
290
|
+
);
|
|
276
291
|
}
|
|
277
|
-
step(
|
|
278
|
-
|
|
292
|
+
step(
|
|
293
|
+
'4. Build a patch: ${dim('flutter-ota build --version 1.0.1')} '
|
|
294
|
+
'then ${dim('flutter-ota deploy')}',
|
|
295
|
+
);
|
|
279
296
|
});
|
|
280
297
|
|
|
281
298
|
/// Generate the integration files (pubspec dep, manifest permission, setup dart
|
|
@@ -306,7 +323,9 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
306
323
|
final marker = '\ndependencies:';
|
|
307
324
|
final idx = content.indexOf(marker);
|
|
308
325
|
if (idx == -1) {
|
|
309
|
-
warn(
|
|
326
|
+
warn(
|
|
327
|
+
'Could not find a `dependencies:` block; add `flutter_ota_kit` manually.',
|
|
328
|
+
);
|
|
310
329
|
return;
|
|
311
330
|
}
|
|
312
331
|
final insertAt = idx + marker.length;
|
|
@@ -328,7 +347,9 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
328
347
|
}
|
|
329
348
|
final match = RegExp(r'<manifest[^>]*>').firstMatch(content);
|
|
330
349
|
if (match == null) {
|
|
331
|
-
warn(
|
|
350
|
+
warn(
|
|
351
|
+
'Could not locate <manifest> tag; add INTERNET permission manually.',
|
|
352
|
+
);
|
|
332
353
|
return;
|
|
333
354
|
}
|
|
334
355
|
final insertAt = match.end;
|
|
@@ -9,7 +9,10 @@ import '../ui/ui.dart';
|
|
|
9
9
|
/// `flutter_ota_kit keys` — generate an Ed25519 keypair for bundle signing.
|
|
10
10
|
class KeysCommand extends FlutterPatcherCommand {
|
|
11
11
|
KeysCommand() {
|
|
12
|
-
argParser.addFlag(
|
|
12
|
+
argParser.addFlag(
|
|
13
|
+
'save',
|
|
14
|
+
help: 'Persist the public key into the project config.',
|
|
15
|
+
);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
@override
|
|
@@ -39,11 +42,10 @@ class KeysCommand extends FlutterPatcherCommand {
|
|
|
39
42
|
final file = configCandidates().first;
|
|
40
43
|
final json =
|
|
41
44
|
file.existsSync() && file.readAsStringSync().trim().isNotEmpty
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
? (jsonDecode(file.readAsStringSync()) as Map<String, dynamic>)
|
|
46
|
+
: <String, dynamic>{};
|
|
44
47
|
writePath(json, 'publicKey', publicB64);
|
|
45
|
-
await steps.run<void>('Saving public key to ${file.path}',
|
|
46
|
-
() async {
|
|
48
|
+
await steps.run<void>('Saving public key to ${file.path}', () async {
|
|
47
49
|
_save(json);
|
|
48
50
|
});
|
|
49
51
|
}
|