@_nazmiforreal/flutter-ota 0.1.21 → 0.1.23
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 +85 -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/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 +54 -2
|
Binary file
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Launcher for the flutter_ota_kit CLI.
|
|
5
|
+
//
|
|
6
|
+
// Resolves a platform+arch-specific prebuilt binary (`flutter-ota-<os>-<arch>`)
|
|
7
|
+
// shipped next to this script, falling back to `dart run` of the bundled Dart
|
|
8
|
+
// source when no prebuilt exists for the current architecture (e.g. a user on
|
|
9
|
+
// arm64 with only an x64 binary present). This keeps the package installable on
|
|
10
|
+
// any platform that has the Dart SDK, without us needing to ship every binary.
|
|
11
|
+
|
|
12
|
+
const { spawnSync } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
function platform() {
|
|
17
|
+
if (process.platform === 'win32') return 'windows';
|
|
18
|
+
if (process.platform === 'darwin') return 'macos';
|
|
19
|
+
return 'linux';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function arch() {
|
|
23
|
+
// Only x64/arm64 matter for Dart AOT; everything else falls back to Dart VM.
|
|
24
|
+
return process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const platformName = platform();
|
|
28
|
+
const archName = arch();
|
|
29
|
+
const ext = process.platform === 'win32' ? '.exe' : '';
|
|
30
|
+
const binDir = __dirname;
|
|
31
|
+
const binName = `flutter-ota-${platformName}-${archName}${ext}`;
|
|
32
|
+
const binPath = path.join(binDir, binName);
|
|
33
|
+
|
|
34
|
+
const sourceEntry = path.join(
|
|
35
|
+
binDir, '..', 'dart-src', 'packages', 'cli-tools', 'bin', 'flutter_ota_kit.dart',
|
|
36
|
+
);
|
|
37
|
+
const cliDir = path.dirname(path.dirname(sourceEntry));
|
|
38
|
+
|
|
39
|
+
function chmodIfNeeded(p) {
|
|
40
|
+
try {
|
|
41
|
+
const st = fs.statSync(p);
|
|
42
|
+
// Ensure owner-executable bit is set.
|
|
43
|
+
if (!(st.mode & 0o100)) fs.chmodSync(p, st.mode | 0o755);
|
|
44
|
+
} catch (_) {
|
|
45
|
+
// Non-fatal.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function run(bin, args) {
|
|
50
|
+
const res = spawnSync(bin, args, { stdio: 'inherit', windowsHide: false });
|
|
51
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (fs.existsSync(binPath)) {
|
|
55
|
+
chmodIfNeeded(binPath);
|
|
56
|
+
run(binPath, process.argv.slice(2));
|
|
57
|
+
} else if (
|
|
58
|
+
spawnSync('dart', ['--version'], { stdio: 'ignore' }).status === 0 &&
|
|
59
|
+
fs.existsSync(sourceEntry)
|
|
60
|
+
) {
|
|
61
|
+
// Ensure dependencies are resolved before running from source. We only run
|
|
62
|
+
// `dart pub get` when the package config is missing, so repeated invocations
|
|
63
|
+
// don't re-resolve dependencies on every command.
|
|
64
|
+
const configPath = path.join(cliDir, '.dart_tool', 'package_config.json');
|
|
65
|
+
if (!fs.existsSync(configPath)) {
|
|
66
|
+
const getRes = spawnSync('dart', ['pub', 'get'], { stdio: 'inherit', cwd: cliDir });
|
|
67
|
+
if (getRes.status !== 0) {
|
|
68
|
+
console.error(
|
|
69
|
+
'flutter-ota: `dart pub get` failed. Install dependencies or build ' +
|
|
70
|
+
`a prebuilt manually:\n cd ${cliDir} && dart compile exe ` +
|
|
71
|
+
`bin/flutter_ota_kit.dart -o ${binPath}`,
|
|
72
|
+
);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
chmodIfNeeded(binPath);
|
|
77
|
+
run('dart', [sourceEntry, ...process.argv.slice(2)]);
|
|
78
|
+
} else {
|
|
79
|
+
console.error(
|
|
80
|
+
`flutter-ota: no prebuilt binary for ${platformName}-${archName} and ` +
|
|
81
|
+
'the Dart SDK was not found. Install Dart (https://dart.dev) or use a ' +
|
|
82
|
+
'platform/arch with a shipped prebuilt binary.',
|
|
83
|
+
);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
@@ -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) {
|