@_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.
Files changed (39) hide show
  1. package/bin/flutter-ota-linux-x64 +0 -0
  2. package/bin/flutter-ota.js +23 -72
  3. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +0 -2
  4. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +2 -2
  5. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +1 -1
  6. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +112 -124
  7. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +20 -28
  8. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +40 -18
  9. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +10 -11
  10. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +30 -33
  11. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +43 -41
  12. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +9 -10
  13. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +32 -35
  14. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +3 -6
  15. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +59 -50
  16. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +32 -37
  17. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +13 -16
  18. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +12 -20
  19. package/dart-src/packages/cli-tools/lib/src/config.dart +10 -3
  20. package/dart-src/packages/cli-tools/lib/src/operations.dart +5 -0
  21. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +225 -31
  22. package/dart-src/packages/cli-tools/lib/src/util.dart +7 -3
  23. package/dart-src/packages/cli-tools/test/cli_test.dart +6 -0
  24. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +7 -3
  25. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +7 -4
  26. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +18 -6
  27. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +4 -2
  28. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +4 -2
  29. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +22 -6
  30. package/migrations/supabase/20250103114225_init.sql +7 -3
  31. package/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +7 -4
  32. package/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +18 -6
  33. package/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +4 -2
  34. package/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +4 -2
  35. package/migrations/supabase/20260520014100_hot-updater_rls.sql +22 -6
  36. package/package.json +1 -1
  37. package/scripts/vendor.sh +32 -0
  38. package/migrations/aws/README.md +0 -7
  39. package/migrations/cloudflare/bundles.sql +0 -42
Binary file
@@ -1,85 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
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');
4
+ const { execFileSync } = require('child_process');
14
5
  const path = require('path');
6
+ const os = require('os');
15
7
 
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
- }
8
+ const platform = os.platform();
9
+ const arch = os.arch();
26
10
 
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);
11
+ const binaryNames = {
12
+ 'linux-x64': 'flutter-ota-linux-x64',
13
+ 'linux-arm64': 'flutter-ota-linux-arm64',
14
+ 'darwin-x64': 'flutter-ota-darwin-x64',
15
+ 'darwin-arm64': 'flutter-ota-darwin-arm64',
16
+ 'win32-x64': 'flutter-ota-win32-x64.exe',
17
+ };
33
18
 
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));
19
+ const key = `${platform}-${arch}`;
20
+ const binName = binaryNames[key];
38
21
 
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
- }
22
+ if (!binName) {
23
+ console.error(`flutter-ota: unsupported platform ${platform}-${arch}`);
24
+ process.exit(1);
47
25
  }
48
26
 
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
- }
27
+ const binPath = path.join(__dirname, binName);
53
28
 
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
- }
29
+ try {
30
+ execFileSync(binPath, process.argv.slice(2), { stdio: 'inherit' });
31
+ } catch (err) {
32
+ if (err.status !== undefined) {
33
+ process.exit(err.status);
75
34
  }
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);
35
+ throw err;
85
36
  }
@@ -454,7 +454,5 @@
454
454
  ],
455
455
  "generator": "pub",
456
456
  "generatorVersion": "3.13.2",
457
- "flutterRoot": "file:///tmp/opencode/flutter",
458
- "flutterVersion": "3.47.2",
459
457
  "pubCache": "file:///home/user/.pub-cache"
460
458
  }
@@ -59,8 +59,8 @@ import 'src/commands/storage.dart';
59
59
  Future<int> run(List<String> args) async {
60
60
  final runner =
61
61
  FlutterPatcherRunner(
62
- 'flutter_ota_kit',
63
- 'flutter_ota_kit CLI — OTA code push for Flutter (hot-updater compatible).',
62
+ 'flutter-ota',
63
+ 'flutter-ota CLI — OTA code push for Flutter (hot-updater compatible).',
64
64
  )
65
65
  ..addCommand(InitCommand())
66
66
  ..addCommand(ConfigCommand())
@@ -70,7 +70,7 @@ class BuildCommand extends FlutterPatcherCommand {
70
70
  }
71
71
 
72
72
  banner('build');
73
- final bar = ProgressBar(1, 'pack');
73
+ final bar = ProgressBar(1, 'Packing patch.zip');
74
74
  final result = await packPatch(
75
75
  apkPath: apk,
76
76
  version: version,
@@ -1,9 +1,26 @@
1
- import 'package:args/args.dart';
2
1
  import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
3
2
 
4
3
  import '../ui/ui.dart';
5
4
 
6
- /// `flutter_ota_kit bundle` — manage bundles entirely from the CLI (no direct
5
+ final _uuidRe = RegExp(
6
+ r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
7
+ caseSensitive: false,
8
+ );
9
+
10
+ void _requireUuid(String? id, String command) {
11
+ if (id == null || id.isEmpty) {
12
+ throw PackException('Usage: flutter-ota bundle $command --id <id>', 64);
13
+ }
14
+ if (!_uuidRe.hasMatch(id)) {
15
+ throw PackException(
16
+ 'Invalid UUID format: "$id". '
17
+ 'Expected: 8-4-4-4-12 hex digits.',
18
+ 64,
19
+ );
20
+ }
21
+ }
22
+
23
+ /// `flutter-ota bundle` — manage bundles entirely from the CLI (no direct
7
24
  /// DB access needed; the CLI talks to the backend through its plugins).
8
25
  class BundleCommand extends FlutterPatcherCommand {
9
26
  BundleCommand({this.config, this.backendOverride}) {
@@ -66,7 +83,13 @@ class BundleCommand extends FlutterPatcherCommand {
66
83
  }
67
84
 
68
85
  class BundleListCommand extends FlutterPatcherCommand {
69
- BundleListCommand({this.config, this.backendOverride});
86
+ BundleListCommand({this.config, this.backendOverride}) {
87
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
88
+ argParser.addOption('channel', abbr: 'c', help: 'Filter by channel.');
89
+ argParser.addOption('platform', abbr: 'p', help: 'Filter by platform.');
90
+ argParser.addOption('enabled', help: 'Filter by enabled (true/false).');
91
+ argParser.addOption('limit', abbr: 'l', defaultsTo: '20', help: 'Page size.');
92
+ }
70
93
 
71
94
  final FlutterPatcherConfig? config;
72
95
  final Backend? backendOverride;
@@ -77,14 +100,6 @@ class BundleListCommand extends FlutterPatcherCommand {
77
100
  @override
78
101
  String get description => 'List bundles.';
79
102
 
80
- @override
81
- ArgParser get argParser => ArgParser()
82
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
83
- ..addOption('channel', abbr: 'c', help: 'Filter by channel.')
84
- ..addOption('platform', abbr: 'p', help: 'Filter by platform.')
85
- ..addOption('enabled', help: 'Filter by enabled (true/false).')
86
- ..addOption('limit', abbr: 'l', defaultsTo: '20', help: 'Page size.');
87
-
88
103
  @override
89
104
  Future<int> run() => runGuarded(() async {
90
105
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
@@ -93,13 +108,18 @@ class BundleListCommand extends FlutterPatcherCommand {
93
108
  final enabled = enabledRaw == null
94
109
  ? null
95
110
  : (enabledRaw == 'true' || enabledRaw == '1');
111
+ final limitRaw = argResults!['limit'] as String;
112
+ final limit = int.tryParse(limitRaw);
113
+ if (limit == null || limit < 1) {
114
+ throw PackException('--limit must be a positive integer (got "$limitRaw")', 64);
115
+ }
96
116
  final res = await listBundles(
97
117
  backend,
98
118
  ListOptions(
99
119
  channel: argResults!['channel'] as String?,
100
120
  platform: argResults!['platform'] as String?,
101
121
  enabled: enabled,
102
- limit: int.parse(argResults!['limit'] as String),
122
+ limit: limit,
103
123
  ),
104
124
  );
105
125
  banner('bundle · list');
@@ -107,30 +127,33 @@ class BundleListCommand extends FlutterPatcherCommand {
107
127
  step('(no bundles)');
108
128
  return;
109
129
  }
110
- final lines = <String>[];
130
+ final rows = <List<String>>[];
111
131
  for (var i = 0; i < res.data.length; i++) {
112
132
  final b = res.data[i];
113
- final target = b.targetAppVersion ?? b.fingerprintHash ?? dim('-');
114
- lines
115
- ..add(kv('#$i', cyan(b.id)))
116
- ..add(kv('channel', b.channel))
117
- ..add(kv('enabled', b.enabled ? green('yes') : yellow('no')))
118
- ..add(kv('platform', b.platform.value))
119
- ..add(kv('force', b.shouldForceUpdate ? green('yes') : yellow('no')))
120
- ..add(kv('target', target));
121
- if (b.message != null) lines.add(kv('message', b.message!));
122
- if (b.metadata?.signature != null) {
123
- lines.add(kv('signature', green('✓ signed')));
124
- }
125
- lines.add('');
133
+ final enabled = b.enabled ? green('✓') : red('✗');
134
+ final force = b.shouldForceUpdate ? green('✓') : dim('✗');
135
+ rows.add([
136
+ '$i',
137
+ cyan(b.id),
138
+ b.channel,
139
+ b.platform.value,
140
+ force,
141
+ enabled,
142
+ ]);
126
143
  }
127
- box('${res.data.length} bundles', lines);
128
- step('total: ${res.pagination.total}');
144
+ table(
145
+ '${res.data.length} bundles (total: ${res.pagination.total})',
146
+ ['#', 'ID', 'CHANNEL', 'PLAT', 'FORCE', 'ON'],
147
+ rows,
148
+ );
129
149
  });
130
150
  }
131
151
 
132
152
  class BundleShowCommand extends FlutterPatcherCommand {
133
- BundleShowCommand({this.config, this.backendOverride});
153
+ BundleShowCommand({this.config, this.backendOverride}) {
154
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
155
+ argParser.addOption('id', help: 'Bundle id.');
156
+ }
134
157
 
135
158
  final FlutterPatcherConfig? config;
136
159
  final Backend? backendOverride;
@@ -141,21 +164,14 @@ class BundleShowCommand extends FlutterPatcherCommand {
141
164
  @override
142
165
  String get description => 'Show a single bundle\'s details.';
143
166
 
144
- @override
145
- ArgParser get argParser => ArgParser()
146
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
147
- ..addOption('id', help: 'Bundle id.');
148
-
149
167
  @override
150
168
  Future<int> run() => runGuarded(() async {
151
169
  final id = argResults!['id'] as String?;
152
- if (id == null || id.isEmpty) {
153
- throw PackException('Usage: flutter_ota_kit bundle show --id <id>', 64);
154
- }
170
+ _requireUuid(id, 'show');
155
171
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
156
172
  final backend = requireBackend(cfg, override: backendOverride);
157
173
  banner('bundle · show');
158
- final b = await backend.db.getBundleById(id);
174
+ final b = await backend.db.getBundleById(id!);
159
175
  if (b == null) {
160
176
  step('(not found)');
161
177
  return;
@@ -177,7 +193,15 @@ class BundleShowCommand extends FlutterPatcherCommand {
177
193
  }
178
194
 
179
195
  class BundleDeleteCommand extends FlutterPatcherCommand {
180
- BundleDeleteCommand({this.config, this.backendOverride});
196
+ BundleDeleteCommand({this.config, this.backendOverride}) {
197
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
198
+ argParser.addOption('id', help: 'Bundle id.');
199
+ argParser.addFlag(
200
+ 'keep-storage',
201
+ help: 'Do not delete the storage object.',
202
+ negatable: false,
203
+ );
204
+ }
181
205
 
182
206
  final FlutterPatcherConfig? config;
183
207
  final Backend? backendOverride;
@@ -188,47 +212,42 @@ class BundleDeleteCommand extends FlutterPatcherCommand {
188
212
  @override
189
213
  String get description => 'Delete a bundle by id.';
190
214
 
191
- @override
192
- ArgParser get argParser => ArgParser()
193
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
194
- ..addOption('id', help: 'Bundle id.')
195
- ..addFlag(
196
- 'keep-storage',
197
- help: 'Do not delete the storage object.',
198
- negatable: false,
199
- );
200
-
201
215
  @override
202
216
  Future<int> run() => runGuarded(() async {
203
217
  final id = argResults!['id'] as String?;
204
- if (id == null || id.isEmpty) {
205
- throw PackException('Usage: flutter_ota_kit bundle delete --id <id>', 64);
206
- }
218
+ _requireUuid(id, 'delete');
207
219
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
208
220
  final backend = requireBackend(cfg, override: backendOverride);
209
221
  banner('bundle · delete');
210
- final existing = await backend.db.getBundleById(id);
222
+ final existing = await backend.db.getBundleById(id!);
211
223
  if (existing == null) {
212
224
  throw StateError('Bundle "$id" not found.');
213
225
  }
214
226
  final keepStorage = argResults!['keep-storage'] as bool;
227
+ final steps = Steps('delete');
215
228
  await spinner(
216
229
  () => deleteBundle(backend, id),
217
230
  'Deleting bundle $id',
218
231
  done: 'Deleted',
219
232
  );
233
+ steps.success('Deleted bundle $id');
220
234
  if (!keepStorage && existing.storageUri.isNotEmpty) {
221
235
  await spinner(
222
236
  () => backend.storage.delete(existing.storageUri),
223
237
  'Removing storage object',
224
238
  done: 'Storage removed',
225
239
  );
240
+ steps.success('Removed storage object');
226
241
  }
242
+ steps.summary();
227
243
  });
228
244
  }
229
245
 
230
246
  class BundleDisableCommand extends FlutterPatcherCommand {
231
- BundleDisableCommand({this.config, this.backendOverride});
247
+ BundleDisableCommand({this.config, this.backendOverride}) {
248
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
249
+ argParser.addOption('id', help: 'Bundle id.');
250
+ }
232
251
 
233
252
  final FlutterPatcherConfig? config;
234
253
  final Backend? backendOverride;
@@ -239,26 +258,16 @@ class BundleDisableCommand extends FlutterPatcherCommand {
239
258
  @override
240
259
  String get description => 'Disable a bundle (stop serving it).';
241
260
 
242
- @override
243
- ArgParser get argParser => ArgParser()
244
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
245
- ..addOption('id', help: 'Bundle id.');
246
-
247
261
  @override
248
262
  Future<int> run() => runGuarded(() async {
249
263
  final id = argResults!['id'] as String?;
250
- if (id == null || id.isEmpty) {
251
- throw PackException(
252
- 'Usage: flutter_ota_kit bundle disable --id <id>',
253
- 64,
254
- );
255
- }
264
+ _requireUuid(id, 'disable');
256
265
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
257
266
  final backend = requireBackend(cfg, override: backendOverride);
258
267
  banner('bundle · disable');
259
268
  await spinner(
260
269
  () async {
261
- await backend.db.updateBundle(id, {'enabled': false});
270
+ await backend.db.updateBundle(id!, {'enabled': false});
262
271
  await backend.db.commitBundle();
263
272
  },
264
273
  'Disabling bundle $id',
@@ -268,7 +277,10 @@ class BundleDisableCommand extends FlutterPatcherCommand {
268
277
  }
269
278
 
270
279
  class BundleEnableCommand extends FlutterPatcherCommand {
271
- BundleEnableCommand({this.config, this.backendOverride});
280
+ BundleEnableCommand({this.config, this.backendOverride}) {
281
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
282
+ argParser.addOption('id', help: 'Bundle id.');
283
+ }
272
284
 
273
285
  final FlutterPatcherConfig? config;
274
286
  final Backend? backendOverride;
@@ -279,23 +291,16 @@ class BundleEnableCommand extends FlutterPatcherCommand {
279
291
  @override
280
292
  String get description => 'Enable a bundle.';
281
293
 
282
- @override
283
- ArgParser get argParser => ArgParser()
284
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
285
- ..addOption('id', help: 'Bundle id.');
286
-
287
294
  @override
288
295
  Future<int> run() => runGuarded(() async {
289
296
  final id = argResults!['id'] as String?;
290
- if (id == null || id.isEmpty) {
291
- throw PackException('Usage: flutter_ota_kit bundle enable --id <id>', 64);
292
- }
297
+ _requireUuid(id, 'enable');
293
298
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
294
299
  final backend = requireBackend(cfg, override: backendOverride);
295
300
  banner('bundle · enable');
296
301
  await spinner(
297
302
  () async {
298
- await backend.db.updateBundle(id, {'enabled': true});
303
+ await backend.db.updateBundle(id!, {'enabled': true});
299
304
  await backend.db.commitBundle();
300
305
  },
301
306
  'Enabling bundle $id',
@@ -305,7 +310,14 @@ class BundleEnableCommand extends FlutterPatcherCommand {
305
310
  }
306
311
 
307
312
  class BundleForceCommand extends FlutterPatcherCommand {
308
- BundleForceCommand({this.config, this.backendOverride});
313
+ BundleForceCommand({this.config, this.backendOverride}) {
314
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
315
+ argParser.addOption('id', help: 'Bundle id.');
316
+ argParser.addFlag(
317
+ 'off',
318
+ help: 'Clear the force-update flag instead of setting it.',
319
+ );
320
+ }
309
321
 
310
322
  final FlutterPatcherConfig? config;
311
323
  final Backend? backendOverride;
@@ -316,31 +328,17 @@ class BundleForceCommand extends FlutterPatcherCommand {
316
328
  @override
317
329
  String get description => 'Force an update onto clients (--off to clear).';
318
330
 
319
- @override
320
- ArgParser get argParser => ArgParser()
321
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
322
- ..addOption('id', help: 'Bundle id.')
323
- ..addFlag(
324
- 'off',
325
- help: 'Clear the force-update flag instead of setting it.',
326
- );
327
-
328
331
  @override
329
332
  Future<int> run() => runGuarded(() async {
330
333
  final id = argResults!['id'] as String?;
331
- if (id == null || id.isEmpty) {
332
- throw PackException(
333
- 'Usage: flutter_ota_kit bundle force --id <id> [--off]',
334
- 64,
335
- );
336
- }
334
+ _requireUuid(id, 'force');
337
335
  final off = argResults!['off'] as bool;
338
336
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
339
337
  final backend = requireBackend(cfg, override: backendOverride);
340
338
  banner('bundle · force');
341
339
  await spinner(
342
340
  () async {
343
- await backend.db.updateBundle(id, {
341
+ await backend.db.updateBundle(id!, {
344
342
  'shouldForceUpdate': !off,
345
343
  if (!off) 'enabled': true,
346
344
  });
@@ -353,7 +351,11 @@ class BundleForceCommand extends FlutterPatcherCommand {
353
351
  }
354
352
 
355
353
  class BundlePromoteCommand extends FlutterPatcherCommand {
356
- BundlePromoteCommand({this.config, this.backendOverride});
354
+ BundlePromoteCommand({this.config, this.backendOverride}) {
355
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
356
+ argParser.addOption('id', help: 'Bundle id.');
357
+ argParser.addOption('channel', abbr: 'c', help: 'Target channel.');
358
+ }
357
359
 
358
360
  final FlutterPatcherConfig? config;
359
361
  final Backend? backendOverride;
@@ -364,19 +366,14 @@ class BundlePromoteCommand extends FlutterPatcherCommand {
364
366
  @override
365
367
  String get description => 'Promote a bundle to a channel.';
366
368
 
367
- @override
368
- ArgParser get argParser => ArgParser()
369
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
370
- ..addOption('id', help: 'Bundle id.')
371
- ..addOption('channel', abbr: 'c', help: 'Target channel.');
372
-
373
369
  @override
374
370
  Future<int> run() => runGuarded(() async {
375
371
  final id = argResults!['id'] as String?;
376
372
  final channel = argResults!['channel'] as String?;
377
- if (id == null || id.isEmpty || channel == null || channel.isEmpty) {
373
+ _requireUuid(id, 'promote');
374
+ if (channel == null || channel.isEmpty) {
378
375
  throw PackException(
379
- 'Usage: flutter_ota_kit bundle promote --id <id> --channel <channel>',
376
+ 'Usage: flutter-ota bundle promote --id <id> --channel <channel>',
380
377
  64,
381
378
  );
382
379
  }
@@ -384,16 +381,23 @@ class BundlePromoteCommand extends FlutterPatcherCommand {
384
381
  final backend = requireBackend(cfg, override: backendOverride);
385
382
  banner('bundle · promote');
386
383
  await spinner(
387
- () => promoteBundle(backend, id, channel),
384
+ () => promoteBundle(backend, id!, channel),
388
385
  'Promoting $id to $channel',
389
386
  done: 'Promoted',
390
387
  );
391
- box('promote', [kv('bundle', cyan(id)), kv('channel', channel)]);
388
+ box('promote', [kv('bundle', cyan(id!)), kv('channel', channel)]);
392
389
  });
393
390
  }
394
391
 
395
392
  class BundleUpdateCommand extends FlutterPatcherCommand {
396
- BundleUpdateCommand({this.config, this.backendOverride});
393
+ BundleUpdateCommand({this.config, this.backendOverride}) {
394
+ argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
395
+ argParser.addOption('id', help: 'Bundle id.');
396
+ argParser.addOption('message', abbr: 'm', help: 'New release message.');
397
+ argParser.addOption('target-version', help: 'New target app version (e.g. 1.0.0).');
398
+ argParser.addOption('enabled', help: 'Set enabled (true/false).');
399
+ argParser.addOption('force', abbr: 'f', help: 'Set force-update (true/false).');
400
+ }
397
401
 
398
402
  final FlutterPatcherConfig? config;
399
403
  final Backend? backendOverride;
@@ -405,26 +409,10 @@ class BundleUpdateCommand extends FlutterPatcherCommand {
405
409
  String get description =>
406
410
  'Edit a bundle\'s metadata (message / target version / enabled / force).';
407
411
 
408
- @override
409
- ArgParser get argParser => ArgParser()
410
- ..addOption('backend', abbr: 'b', help: 'Backend provider.')
411
- ..addOption('id', help: 'Bundle id.')
412
- ..addOption('message', abbr: 'm', help: 'New release message.')
413
- ..addOption('target-version', help: 'New target app version (e.g. 1.0.0).')
414
- ..addOption('enabled', help: 'Set enabled (true/false).')
415
- ..addOption('force', abbr: 'f', help: 'Set force-update (true/false).');
416
-
417
412
  @override
418
413
  Future<int> run() => runGuarded(() async {
419
414
  final id = argResults!['id'] as String?;
420
- if (id == null || id.isEmpty) {
421
- throw PackException(
422
- 'Usage: flutter-ota bundle update --id <id> '
423
- '[--message <m>] [--target-version <v>] [--enabled true|false] '
424
- '[--force true|false]',
425
- 64,
426
- );
427
- }
415
+ _requireUuid(id, 'update');
428
416
  final patch = <String, Object?>{};
429
417
  final message = argResults!['message'] as String?;
430
418
  if (message != null) patch['message'] = message;
@@ -446,13 +434,13 @@ class BundleUpdateCommand extends FlutterPatcherCommand {
446
434
  banner('bundle · update');
447
435
  await spinner(
448
436
  () async {
449
- await backend.db.updateBundle(id, patch);
437
+ await backend.db.updateBundle(id!, patch);
450
438
  await backend.db.commitBundle();
451
439
  },
452
440
  'Updating $id',
453
441
  done: 'Updated',
454
442
  );
455
- final b = await backend.db.getBundleById(id);
443
+ final b = await backend.db.getBundleById(id!);
456
444
  if (b != null) {
457
445
  box('bundle', [
458
446
  kv('id', cyan(b.id)),