@_nazmiforreal/flutter-ota 0.1.6 → 0.1.8

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 (147) hide show
  1. package/README.md +13 -13
  2. package/bin/{flutter-patcher-linux-x64 → flutter-ota-linux-x64} +0 -0
  3. package/bin/{flutter-patcher.js → flutter-ota.js} +4 -4
  4. package/dart-src/packages/cli-tools/bin/{flutter_patcher.dart → flutter_ota_kit.dart} +2 -2
  5. package/dart-src/packages/cli-tools/lib/{flutter_patcher_cli.dart → flutter_ota_kit_cli.dart} +5 -3
  6. package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -25
  7. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +10 -4
  8. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +5 -3
  9. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +279 -10
  10. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +7 -7
  11. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +5 -5
  12. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +4 -4
  13. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -4
  14. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +3 -3
  15. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +1 -1
  16. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +294 -45
  17. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +1 -1
  18. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +59 -24
  19. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +5 -5
  20. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +117 -0
  21. package/dart-src/packages/cli-tools/lib/src/config.dart +29 -78
  22. package/dart-src/packages/cli-tools/lib/src/operations.dart +4 -4
  23. package/dart-src/packages/cli-tools/lib/src/pack.dart +12 -6
  24. package/dart-src/packages/cli-tools/lib/src/runner.dart +21 -3
  25. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +2 -2
  26. package/dart-src/packages/cli-tools/lib/src/verbose.dart +3 -0
  27. package/dart-src/packages/cli-tools/migrations/aws/README.md +7 -0
  28. package/dart-src/packages/cli-tools/migrations/postgres/bundles.sql +45 -0
  29. package/dart-src/packages/cli-tools/migrations/postgres/get_target_app_version_list.sql +21 -0
  30. package/dart-src/packages/cli-tools/migrations/postgres/get_update_info_by_app_version.sql +126 -0
  31. package/dart-src/packages/cli-tools/migrations/postgres/get_update_info_by_fingerprint_hash.sql +125 -0
  32. package/dart-src/packages/cli-tools/migrations/postgres/hash_user_id.sql +30 -0
  33. package/dart-src/packages/cli-tools/migrations/postgres/is_device_eligible.sql +237 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +11 -0
  35. package/dart-src/packages/cli-tools/pubspec.yaml +8 -10
  36. package/dart-src/packages/cli-tools/test/backends_test.dart +4 -4
  37. package/dart-src/packages/cli-tools/test/build_pack_test.dart +1 -1
  38. package/dart-src/packages/cli-tools/test/cli_test.dart +3 -3
  39. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
  40. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
  41. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +7 -7
  42. package/dart-src/packages/core/lib/{flutter_patcher_core.dart → flutter_ota_kit_core.dart} +1 -1
  43. package/dart-src/packages/core/pubspec.yaml +2 -2
  44. package/dart-src/packages/core/test/rollout_test.dart +1 -1
  45. package/dart-src/packages/core/test/semver_test.dart +1 -1
  46. package/dart-src/packages/core/test/types_test.dart +2 -2
  47. package/dart-src/packages/core/test/uuid_test.dart +1 -1
  48. package/dart-src/packages/core/tool/dbg.dart +1 -1
  49. package/dart-src/plugins/aws/lib/{flutter_patcher_aws.dart → flutter_ota_kit_aws.dart} +0 -2
  50. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +1 -1
  51. package/dart-src/plugins/aws/lib/src/aws_database.dart +1 -1
  52. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +1 -1
  53. package/dart-src/plugins/aws/lib/src/aws_storage.dart +1 -1
  54. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +25 -3
  55. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +1 -1
  56. package/dart-src/plugins/aws/pubspec.yaml +4 -4
  57. package/dart-src/plugins/aws/test/aws_database_test.dart +3 -3
  58. package/dart-src/plugins/aws/test/aws_storage_test.dart +2 -2
  59. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +2 -2
  60. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +1 -1
  61. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +1 -1
  62. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +1 -1
  63. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +1 -1
  64. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +2 -2
  65. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +1 -1
  66. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +1 -1
  67. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +1 -1
  68. package/dart-src/plugins/cloudflare/pubspec.yaml +4 -4
  69. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +2 -2
  70. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +2 -2
  71. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +2 -2
  72. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +2 -2
  73. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +2 -2
  74. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +2 -2
  75. package/dart-src/plugins/plugin-core/lib/{flutter_patcher_plugin_core.dart → flutter_ota_kit_plugin_core.dart} +1 -1
  76. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +1 -1
  77. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +1 -1
  78. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +1 -1
  79. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +1 -1
  80. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +1 -1
  81. package/dart-src/plugins/plugin-core/lib/src/types.dart +2 -2
  82. package/dart-src/plugins/plugin-core/pubspec.yaml +3 -3
  83. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +3 -3
  84. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +2 -2
  85. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +2 -2
  86. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +2 -2
  87. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +5 -5
  88. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +2 -2
  89. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +2 -2
  90. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +2 -2
  91. package/dart-src/plugins/plugin-core/test/helpers_test.dart +2 -2
  92. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +2 -2
  93. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +3 -3
  94. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +1 -1
  95. package/dart-src/plugins/postgres/lib/{flutter_patcher_postgres.dart → flutter_ota_kit_postgres.dart} +1 -1
  96. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +1 -1
  97. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +2 -2
  98. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +2 -2
  99. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +9 -9
  100. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +1 -1
  101. package/dart-src/plugins/postgres/pubspec.yaml +4 -4
  102. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +2 -2
  103. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +2 -2
  104. package/dart-src/plugins/postgres/test/postgres_database_test.dart +2 -2
  105. package/dart-src/plugins/supabase/lib/edge.dart +1 -1
  106. package/dart-src/plugins/supabase/lib/{flutter_patcher_supabase.dart → flutter_ota_kit_supabase.dart} +1 -1
  107. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +1 -1
  108. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +5 -1
  109. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +2 -2
  110. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +1 -1
  111. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +1 -1
  112. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +2 -5
  113. package/dart-src/plugins/supabase/pubspec.yaml +4 -4
  114. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +5 -5
  115. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +3 -3
  116. package/dart-src/plugins/supabase/test/supabase_database_test.dart +3 -3
  117. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +2 -2
  118. package/migrations/aws/README.md +7 -0
  119. package/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  120. package/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  121. package/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  122. package/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  123. package/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  124. package/migrations/cloudflare/bundles.sql +42 -0
  125. package/migrations/postgres/bundles.sql +45 -0
  126. package/migrations/postgres/get_target_app_version_list.sql +21 -0
  127. package/migrations/postgres/get_update_info_by_app_version.sql +126 -0
  128. package/migrations/postgres/get_update_info_by_fingerprint_hash.sql +125 -0
  129. package/migrations/postgres/hash_user_id.sql +30 -0
  130. package/migrations/postgres/is_device_eligible.sql +237 -0
  131. package/package.json +4 -4
  132. package/scripts/postinstall.js +15 -15
  133. package/scripts/vendor.sh +1 -1
  134. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +0 -496
  135. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +0 -743
  136. package/dart-src/packages/cli-tools/pubspec.lock +0 -638
  137. package/dart-src/packages/cli-tools/test/standalone_backend_test.dart +0 -315
  138. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +0 -72
  139. package/dart-src/plugins/aws/test/aws_lambda_edge_storage_test.dart +0 -190
  140. package/dart-src/plugins/standalone/.gitkeep +0 -0
  141. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +0 -12
  142. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +0 -107
  143. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +0 -233
  144. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +0 -175
  145. package/dart-src/plugins/standalone/pubspec.yaml +0 -18
  146. package/dart-src/plugins/standalone/test/standalone_test.dart +0 -264
  147. /package/dart-src/plugins/cloudflare/lib/{flutter_patcher_cloudflare.dart → flutter_ota_kit_cloudflare.dart} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # flutter-patcher (flutter-ota CLI)
1
+ # flutter-ota (flutter-ota CLI)
2
2
 
3
- Command-line interface for [flutter_patcher](https://github.com/HYPER12755/flutter_patcher) — a self-hosted OTA ("code push") platform for Flutter Android, hot-updater compatible.
3
+ Command-line interface for [flutter_ota_kit](https://github.com/HYPER12755/flutter_ota_kit) — a multi-backend OTA ("code push") toolkit for Flutter Android, hot-updater compatible.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,30 +8,30 @@ Command-line interface for [flutter_patcher](https://github.com/HYPER12755/flutt
8
8
  npm i -g @_nazmiforreal/flutter-ota
9
9
  ```
10
10
 
11
- This installs the `flutter-patcher` command. A prebuilt Linux binary ships with the package; on other platforms it falls back to `dart compile exe` (requires the [Dart SDK](https://dart.dev)).
11
+ This installs the `flutter-ota` command. A prebuilt Linux binary ships with the package; on other platforms it falls back to `dart compile exe` (requires the [Dart SDK](https://dart.dev)).
12
12
 
13
13
  ## Usage
14
14
 
15
15
  ```bash
16
- flutter-patcher --help
17
- flutter-patcher keys # generate an Ed25519 keypair
18
- flutter-patcher build -a app-release.apk \
16
+ flutter-ota --help
17
+ flutter-ota keys # generate an Ed25519 keypair
18
+ flutter-ota build -a app-release.apk \
19
19
  --version 1.0.0 --target-version-code 1 # -> dist/patch.zip (all ABIs)
20
- flutter-patcher deploy --source dist \
21
- --channel production --backend standalone \
20
+ flutter-ota deploy --source dist \
21
+ --channel production --backend supabase \
22
22
  --key <PRIVATE_KEY_BASE64> # upload + sign a bundle
23
23
  ```
24
24
 
25
25
  ### Backends
26
26
 
27
- `--backend supabase|postgres|cloudflare|aws|standalone` (or `FLUTTER_PATCHER_BACKEND`).
28
- Each resolves its own env / `.flutter_patcher.json` config.
27
+ `--backend supabase|postgres|cloudflare|aws`.
28
+ Each resolves its own env / `.flutter_ota_kit/config.json` (global: `~/.flutter_ota_kit/config.json`).
29
29
 
30
30
  ### Commands
31
31
 
32
- `init`, `build`, `deploy`, `bundle` (list/delete/promote), `rollback`, `channel`
33
- (get/set/list), `fingerprint`, `doctor`, `migrate`, `config`, `keys`, `console`,
34
- `patch`, `mock_server`.
32
+ `init`, `build`, `deploy`, `bundle` (list/show/delete/disable/enable/force/promote/update),
33
+ `rollback`, `channel` (list/get/set), `fingerprint`, `doctor`, `migrate`, `config`
34
+ (get/set/list), `keys`, `console`, `storage` (list/delete).
35
35
 
36
36
  ## License
37
37
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  // Launcher for the flutter_patcher CLI.
5
5
  //
6
- // Resolves a platform+arch-specific prebuilt binary (`flutter-patcher-<os>-<arch>`)
6
+ // Resolves a platform+arch-specific prebuilt binary (`flutter-ota-<os>-<arch>`)
7
7
  // shipped next to this script, falling back to `dart run` of the bundled Dart
8
8
  // source when no prebuilt exists for the current architecture (e.g. a user on
9
9
  // arm64 with only an x64 binary present). This keeps the package installable on
@@ -28,7 +28,7 @@ const platformName = platform();
28
28
  const archName = arch();
29
29
  const ext = process.platform === 'win32' ? '.exe' : '';
30
30
  const binDir = __dirname;
31
- const binName = `flutter-patcher-${platformName}-${archName}${ext}`;
31
+ const binName = `flutter-ota-${platformName}-${archName}${ext}`;
32
32
  const binPath = path.join(binDir, binName);
33
33
 
34
34
  const sourceEntry = path.join(
@@ -66,7 +66,7 @@ if (fs.existsSync(binPath)) {
66
66
  const getRes = spawnSync('dart', ['pub', 'get'], { stdio: 'inherit', cwd: cliDir });
67
67
  if (getRes.status !== 0) {
68
68
  console.error(
69
- 'flutter-patcher: `dart pub get` failed. Install dependencies or build ' +
69
+ 'flutter-ota: `dart pub get` failed. Install dependencies or build ' +
70
70
  `a prebuilt manually:\n cd ${cliDir} && dart compile exe ` +
71
71
  `bin/flutter_patcher.dart -o ${binPath}`,
72
72
  );
@@ -77,7 +77,7 @@ if (fs.existsSync(binPath)) {
77
77
  run('dart', [sourceEntry, ...process.argv.slice(2)]);
78
78
  } else {
79
79
  console.error(
80
- `flutter-patcher: no prebuilt binary for ${platformName}-${archName} and ` +
80
+ `flutter-ota: no prebuilt binary for ${platformName}-${archName} and ` +
81
81
  'the Dart SDK was not found. Install Dart (https://dart.dev) or use a ' +
82
82
  'platform/arch with a shipped prebuilt binary.',
83
83
  );
@@ -1,8 +1,8 @@
1
1
  import 'dart:io';
2
2
 
3
- import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
3
+ import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
4
4
 
5
- /// `flutter_patcher` executable entry point.
5
+ /// `flutter_ota_kit` executable entry point.
6
6
  Future<void> main(List<String> args) async {
7
7
  final code = await run(args);
8
8
  if (code != 0) exitCode = code;
@@ -1,4 +1,4 @@
1
- /// flutter_patcher_cli — command-line interface for flutter_patcher.
1
+ /// flutter_ota_kit_cli — command-line interface for flutter_ota_kit.
2
2
  ///
3
3
  /// Faithful Dart port of hot-updater's `@hot-updater/cli`: deploy bundles,
4
4
  /// manage channels/rollbacks, run migrations, and inspect the backend.
@@ -42,12 +42,13 @@ import 'src/commands/init.dart';
42
42
  import 'src/commands/keys.dart';
43
43
  import 'src/commands/migrate.dart';
44
44
  import 'src/commands/rollback.dart';
45
+ import 'src/commands/storage.dart';
45
46
 
46
47
  /// Entry point: build the command runner and dispatch [args].
47
48
  Future<int> run(List<String> args) async {
48
49
  final runner = FlutterPatcherRunner(
49
- 'flutter_patcher',
50
- 'flutter_patcher CLI — OTA code push for Flutter (hot-updater compatible).',
50
+ 'flutter_ota_kit',
51
+ 'flutter_ota_kit CLI — OTA code push for Flutter (hot-updater compatible).',
51
52
  )
52
53
  ..addCommand(InitCommand())
53
54
  ..addCommand(ConfigCommand())
@@ -59,6 +60,7 @@ Future<int> run(List<String> args) async {
59
60
  ..addCommand(BundleCommand())
60
61
  ..addCommand(RollbackCommand())
61
62
  ..addCommand(ChannelCommand())
63
+ ..addCommand(StorageCommand())
62
64
  ..addCommand(MigrateCommand())
63
65
  ..addCommand(ConsoleCommand());
64
66
 
@@ -1,9 +1,8 @@
1
- import 'package:flutter_patcher_aws/flutter_patcher_aws.dart';
2
- import 'package:flutter_patcher_cloudflare/flutter_patcher_cloudflare.dart';
3
- import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
4
- import 'package:flutter_patcher_postgres/flutter_patcher_postgres.dart';
5
- import 'package:flutter_patcher_standalone/flutter_patcher_standalone.dart';
6
- import 'package:flutter_patcher_supabase/flutter_patcher_supabase.dart';
1
+ import 'package:flutter_ota_kit_aws/flutter_ota_kit_aws.dart';
2
+ import 'package:flutter_ota_kit_cloudflare/flutter_ota_kit_cloudflare.dart';
3
+ import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart';
4
+ import 'package:flutter_ota_kit_postgres/flutter_ota_kit_postgres.dart';
5
+ import 'package:flutter_ota_kit_supabase/flutter_ota_kit_supabase.dart';
7
6
 
8
7
  import 'config.dart';
9
8
 
@@ -48,7 +47,6 @@ Backend resolveBackend(
48
47
  AwsCloudFrontClientLike Function(S3DatabaseConfig config)?
49
48
  awsCloudFrontClientFactory,
50
49
  AwsS3ClientLike Function(AwsS3StorageConfig config)? awsStorageClientFactory,
51
- StandaloneClientFactory? standaloneClientFactory,
52
50
  }) {
53
51
  switch (config.provider) {
54
52
  case 'supabase':
@@ -136,27 +134,10 @@ Backend resolveBackend(
136
134
  basePath: storageConfig.basePath,
137
135
  );
138
136
 
139
- case 'standalone':
140
- final dbConfig = resolveStandaloneDatabaseConfig(
141
- config,
142
- clientFactory: standaloneClientFactory,
143
- );
144
- final storageConfig = resolveStandaloneStorageConfig(
145
- config,
146
- clientFactory: standaloneClientFactory,
147
- );
148
- final db = standaloneRepository(dbConfig)();
149
- final storage = standaloneStorage(storageConfig);
150
- final node = storage.profiles.node;
151
- if (node == null) {
152
- throw StateError('Standalone storage plugin exposes no node profile.');
153
- }
154
- return Backend(db: db, storage: node);
155
-
156
137
  default:
157
138
  throw UnsupportedError(
158
139
  'Provider "${config.provider}" is not supported. '
159
- 'Supported: supabase, postgres, cloudflare, aws, standalone.',
140
+ 'Supported: supabase, postgres, cloudflare, aws.',
160
141
  );
161
142
  }
162
143
  }
@@ -1,9 +1,12 @@
1
+ import 'dart:io';
2
+
1
3
  import 'package:args/args.dart';
2
4
 
3
5
  import 'backend.dart';
4
6
  import 'config.dart';
5
7
  import 'pack.dart';
6
8
  import 'ui/ui.dart';
9
+ import 'verbose.dart';
7
10
 
8
11
  export 'runner.dart';
9
12
 
@@ -12,14 +15,17 @@ Future<int> runGuarded(Future<void> Function() body) async {
12
15
  try {
13
16
  await body();
14
17
  return 0;
15
- } on StateError catch (e) {
18
+ } on StateError catch (e, st) {
16
19
  err(e.message);
20
+ if (verboseMode) stderr.writeln(st);
17
21
  return 1;
18
- } on PackException catch (e) {
22
+ } on PackException catch (e, st) {
19
23
  err(e.message);
24
+ if (verboseMode) stderr.writeln(st);
20
25
  return e.exitCode;
21
- } catch (e) {
26
+ } catch (e, st) {
22
27
  err(e.toString());
28
+ if (verboseMode) stderr.writeln(st);
23
29
  return 1;
24
30
  }
25
31
  }
@@ -29,7 +35,7 @@ Backend requireBackend(FlutterPatcherConfig? config, {Backend? override}) {
29
35
  if (override != null) return override;
30
36
  if (config == null) {
31
37
  throw StateError(
32
- 'No configuration found. Run `flutter_patcher init` or set '
38
+ 'No configuration found. Run `flutter_ota_kit init` or set '
33
39
  'SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY.',
34
40
  );
35
41
  }
@@ -1,8 +1,8 @@
1
- import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
1
+ import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
2
2
 
3
3
  import '../ui/ui.dart';
4
4
 
5
- /// `flutter_patcher build` — package a release APK into a device-compatible
5
+ /// `flutter_ota_kit build` — package a release APK into a device-compatible
6
6
  /// OTA patch (`dist/patch.zip`) that the `deploy` command can publish.
7
7
  ///
8
8
  /// Includes every ABI found in the APK so a single bundle installs on all
@@ -25,11 +25,13 @@ class BuildCommand extends FlutterPatcherCommand {
25
25
  )
26
26
  ..addOption(
27
27
  'target-version-code',
28
+ abbr: 't',
28
29
  help: 'Host APK versionCode this patch targets (integer).',
29
30
  )
30
31
  ..addOption(
31
32
  'abi',
32
- help: 'Preferred ABI to report (default: all ABIs found in APK).',
33
+ help: 'Restrict the patch to a single ABI (e.g. arm64-v8a). '
34
+ 'Default: all ABIs found in the APK are included.',
33
35
  )
34
36
  ..addOption(
35
37
  'out',
@@ -1,11 +1,21 @@
1
1
  import 'package:args/args.dart';
2
- import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
2
+ import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
3
3
 
4
4
  import '../ui/ui.dart';
5
5
 
6
- /// `flutter_patcher bundle` — manage bundles.
6
+ /// `flutter_ota_kit bundle` — manage bundles entirely from the CLI (no direct
7
+ /// DB access needed; the CLI talks to the backend through its plugins).
7
8
  class BundleCommand extends FlutterPatcherCommand {
8
- BundleCommand({this.config, this.backendOverride});
9
+ BundleCommand({this.config, this.backendOverride}) {
10
+ addSubcommand(BundleListCommand(config: config, backendOverride: backendOverride));
11
+ addSubcommand(BundleShowCommand(config: config, backendOverride: backendOverride));
12
+ addSubcommand(BundleDeleteCommand(config: config, backendOverride: backendOverride));
13
+ addSubcommand(BundleDisableCommand(config: config, backendOverride: backendOverride));
14
+ addSubcommand(BundleEnableCommand(config: config, backendOverride: backendOverride));
15
+ addSubcommand(BundleForceCommand(config: config, backendOverride: backendOverride));
16
+ addSubcommand(BundlePromoteCommand(config: config, backendOverride: backendOverride));
17
+ addSubcommand(BundleUpdateCommand(config: config, backendOverride: backendOverride));
18
+ }
9
19
 
10
20
  final FlutterPatcherConfig? config;
11
21
  final Backend? backendOverride;
@@ -14,16 +24,22 @@ class BundleCommand extends FlutterPatcherCommand {
14
24
  String get name => 'bundle';
15
25
 
16
26
  @override
17
- String get description => 'Manage bundles (list / delete / promote).';
27
+ String get description =>
28
+ 'Manage bundles (list / show / delete / disable / enable / force / promote).';
18
29
 
19
30
  @override
20
31
  Future<int> run() => runGuarded(() async {
21
32
  print(description);
22
33
  print('');
23
34
  print('Subcommands:');
24
- print(' list List bundles');
25
- print(' delete <id> Delete a bundle');
26
- print(' promote <id> -c <c> Promote a bundle to a channel');
35
+ print(' list List bundles (filters: --channel/-c, --platform/-p, --enabled, --limit/-l)');
36
+ print(' show --id <id> Show a single bundle\'s details');
37
+ print(' delete --id <id> Delete a bundle');
38
+ print(' disable --id <id> Disable a bundle (stop serving it)');
39
+ print(' enable --id <id> Enable a bundle');
40
+ print(' force --id <id> Force an update onto clients (--off to clear)');
41
+ print(' promote --id <id> -c Promote a bundle to a channel');
42
+ print(' update --id <id> ... Edit metadata (--message/--target-version/--enabled/--force)');
27
43
  });
28
44
  }
29
45
 
@@ -81,6 +97,7 @@ class BundleListCommand extends FlutterPatcherCommand {
81
97
  b.enabled ? green('yes') : yellow('no'),
82
98
  ))
83
99
  ..add(kv('platform', b.platform.value))
100
+ ..add(kv('force', b.shouldForceUpdate ? green('yes') : yellow('no')))
84
101
  ..add(kv('target', target));
85
102
  if (b.message != null) lines.add(kv('message', b.message!));
86
103
  if (b.metadata?.signature != null) {
@@ -93,6 +110,53 @@ class BundleListCommand extends FlutterPatcherCommand {
93
110
  });
94
111
  }
95
112
 
113
+ class BundleShowCommand extends FlutterPatcherCommand {
114
+ BundleShowCommand({this.config, this.backendOverride});
115
+
116
+ final FlutterPatcherConfig? config;
117
+ final Backend? backendOverride;
118
+
119
+ @override
120
+ String get name => 'show';
121
+
122
+ @override
123
+ String get description => 'Show a single bundle\'s details.';
124
+
125
+ @override
126
+ ArgParser get argParser => ArgParser()
127
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
128
+ ..addOption('id', help: 'Bundle id.');
129
+
130
+ @override
131
+ Future<int> run() => runGuarded(() async {
132
+ final id = argResults!['id'] as String?;
133
+ if (id == null || id.isEmpty) {
134
+ throw PackException('Usage: flutter_ota_kit bundle show --id <id>', 64);
135
+ }
136
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
137
+ final backend = requireBackend(cfg, override: backendOverride);
138
+ banner('bundle · show');
139
+ final b = await backend.db.getBundleById(id);
140
+ if (b == null) {
141
+ step('(not found)');
142
+ return;
143
+ }
144
+ final lines = <String>[
145
+ kv('id', cyan(b.id)),
146
+ kv('channel', b.channel),
147
+ kv('enabled', b.enabled ? green('yes') : yellow('no')),
148
+ kv('platform', b.platform.value),
149
+ kv('force', b.shouldForceUpdate ? green('yes') : yellow('no')),
150
+ kv('target', b.targetAppVersion ?? b.fingerprintHash ?? dim('-')),
151
+ ];
152
+ if (b.message != null) lines.add(kv('message', b.message!));
153
+ if (b.metadata?.signature != null) {
154
+ lines.add(kv('signature', green('✓ signed')));
155
+ }
156
+ box('bundle', lines);
157
+ });
158
+ }
159
+
96
160
  class BundleDeleteCommand extends FlutterPatcherCommand {
97
161
  BundleDeleteCommand({this.config, this.backendOverride});
98
162
 
@@ -108,22 +172,154 @@ class BundleDeleteCommand extends FlutterPatcherCommand {
108
172
  @override
109
173
  ArgParser get argParser => ArgParser()
110
174
  ..addOption('backend', abbr: 'b', help: 'Backend provider.')
111
- ..addOption('id', help: 'Bundle id.');
175
+ ..addOption('id', help: 'Bundle id.')
176
+ ..addFlag('keep-storage', help: 'Do not delete the storage object.', negatable: false);
112
177
 
113
178
  @override
114
179
  Future<int> run() => runGuarded(() async {
115
180
  final id = argResults!['id'] as String?;
116
181
  if (id == null || id.isEmpty) {
117
- throw PackException('Usage: flutter_patcher bundle delete --id <id>', 64);
182
+ throw PackException('Usage: flutter_ota_kit bundle delete --id <id>', 64);
118
183
  }
119
184
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
120
185
  final backend = requireBackend(cfg, override: backendOverride);
121
186
  banner('bundle · delete');
187
+ final existing = await backend.db.getBundleById(id);
188
+ if (existing == null) {
189
+ throw StateError('Bundle "$id" not found.');
190
+ }
191
+ final keepStorage = argResults!['keep-storage'] as bool;
122
192
  await spinner(
123
193
  () => deleteBundle(backend, id),
124
194
  'Deleting bundle $id',
125
195
  done: 'Deleted',
126
196
  );
197
+ if (!keepStorage && existing.storageUri.isNotEmpty) {
198
+ await spinner(
199
+ () => backend.storage.delete(existing.storageUri),
200
+ 'Removing storage object',
201
+ done: 'Storage removed',
202
+ );
203
+ }
204
+ });
205
+ }
206
+
207
+ class BundleDisableCommand extends FlutterPatcherCommand {
208
+ BundleDisableCommand({this.config, this.backendOverride});
209
+
210
+ final FlutterPatcherConfig? config;
211
+ final Backend? backendOverride;
212
+
213
+ @override
214
+ String get name => 'disable';
215
+
216
+ @override
217
+ String get description => 'Disable a bundle (stop serving it).';
218
+
219
+ @override
220
+ ArgParser get argParser => ArgParser()
221
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
222
+ ..addOption('id', help: 'Bundle id.');
223
+
224
+ @override
225
+ Future<int> run() => runGuarded(() async {
226
+ final id = argResults!['id'] as String?;
227
+ if (id == null || id.isEmpty) {
228
+ throw PackException('Usage: flutter_ota_kit bundle disable --id <id>', 64);
229
+ }
230
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
231
+ final backend = requireBackend(cfg, override: backendOverride);
232
+ banner('bundle · disable');
233
+ await spinner(
234
+ () async {
235
+ await backend.db.updateBundle(id, {'enabled': false});
236
+ await backend.db.commitBundle();
237
+ },
238
+ 'Disabling bundle $id',
239
+ done: 'Disabled',
240
+ );
241
+ });
242
+ }
243
+
244
+ class BundleEnableCommand extends FlutterPatcherCommand {
245
+ BundleEnableCommand({this.config, this.backendOverride});
246
+
247
+ final FlutterPatcherConfig? config;
248
+ final Backend? backendOverride;
249
+
250
+ @override
251
+ String get name => 'enable';
252
+
253
+ @override
254
+ String get description => 'Enable a bundle.';
255
+
256
+ @override
257
+ ArgParser get argParser => ArgParser()
258
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
259
+ ..addOption('id', help: 'Bundle id.');
260
+
261
+ @override
262
+ Future<int> run() => runGuarded(() async {
263
+ final id = argResults!['id'] as String?;
264
+ if (id == null || id.isEmpty) {
265
+ throw PackException('Usage: flutter_ota_kit bundle enable --id <id>', 64);
266
+ }
267
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
268
+ final backend = requireBackend(cfg, override: backendOverride);
269
+ banner('bundle · enable');
270
+ await spinner(
271
+ () async {
272
+ await backend.db.updateBundle(id, {'enabled': true});
273
+ await backend.db.commitBundle();
274
+ },
275
+ 'Enabling bundle $id',
276
+ done: 'Enabled',
277
+ );
278
+ });
279
+ }
280
+
281
+ class BundleForceCommand extends FlutterPatcherCommand {
282
+ BundleForceCommand({this.config, this.backendOverride});
283
+
284
+ final FlutterPatcherConfig? config;
285
+ final Backend? backendOverride;
286
+
287
+ @override
288
+ String get name => 'force';
289
+
290
+ @override
291
+ String get description => 'Force an update onto clients (--off to clear).';
292
+
293
+ @override
294
+ ArgParser get argParser => ArgParser()
295
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
296
+ ..addOption('id', help: 'Bundle id.')
297
+ ..addFlag('off', help: 'Clear the force-update flag instead of setting it.');
298
+
299
+ @override
300
+ Future<int> run() => runGuarded(() async {
301
+ final id = argResults!['id'] as String?;
302
+ if (id == null || id.isEmpty) {
303
+ throw PackException(
304
+ 'Usage: flutter_ota_kit bundle force --id <id> [--off]',
305
+ 64,
306
+ );
307
+ }
308
+ final off = argResults!['off'] as bool;
309
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
310
+ final backend = requireBackend(cfg, override: backendOverride);
311
+ banner('bundle · force');
312
+ await spinner(
313
+ () async {
314
+ await backend.db.updateBundle(id, {
315
+ 'shouldForceUpdate': !off,
316
+ if (!off) 'enabled': true,
317
+ });
318
+ await backend.db.commitBundle();
319
+ },
320
+ off ? 'Clearing force flag on $id' : 'Forcing update for $id',
321
+ done: off ? 'Cleared' : 'Forced',
322
+ );
127
323
  });
128
324
  }
129
325
 
@@ -151,7 +347,7 @@ class BundlePromoteCommand extends FlutterPatcherCommand {
151
347
  final channel = argResults!['channel'] as String?;
152
348
  if (id == null || id.isEmpty || channel == null || channel.isEmpty) {
153
349
  throw PackException(
154
- 'Usage: flutter_patcher bundle promote --id <id> --channel <channel>',
350
+ 'Usage: flutter_ota_kit bundle promote --id <id> --channel <channel>',
155
351
  64,
156
352
  );
157
353
  }
@@ -169,3 +365,76 @@ class BundlePromoteCommand extends FlutterPatcherCommand {
169
365
  ]);
170
366
  });
171
367
  }
368
+
369
+ class BundleUpdateCommand extends FlutterPatcherCommand {
370
+ BundleUpdateCommand({this.config, this.backendOverride});
371
+
372
+ final FlutterPatcherConfig? config;
373
+ final Backend? backendOverride;
374
+
375
+ @override
376
+ String get name => 'update';
377
+
378
+ @override
379
+ String get description =>
380
+ 'Edit a bundle\'s metadata (message / target version / enabled / force).';
381
+
382
+ @override
383
+ ArgParser get argParser => ArgParser()
384
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
385
+ ..addOption('id', help: 'Bundle id.')
386
+ ..addOption('message', abbr: 'm', help: 'New release message.')
387
+ ..addOption('target-version', help: 'New target app version (e.g. 1.0.0).')
388
+ ..addOption('enabled', help: 'Set enabled (true/false).')
389
+ ..addOption('force', abbr: 'f', help: 'Set force-update (true/false).');
390
+
391
+ @override
392
+ Future<int> run() => runGuarded(() async {
393
+ final id = argResults!['id'] as String?;
394
+ if (id == null || id.isEmpty) {
395
+ throw PackException(
396
+ 'Usage: flutter-ota bundle update --id <id> '
397
+ '[--message <m>] [--target-version <v>] [--enabled true|false] '
398
+ '[--force true|false]',
399
+ 64,
400
+ );
401
+ }
402
+ final patch = <String, Object?>{};
403
+ final message = argResults!['message'] as String?;
404
+ if (message != null) patch['message'] = message;
405
+ final target = argResults!['target-version'] as String?;
406
+ if (target != null) patch['targetAppVersion'] = target;
407
+ final enabled = argResults!['enabled'] as String?;
408
+ if (enabled != null) {
409
+ patch['enabled'] = enabled == 'true' || enabled == '1';
410
+ }
411
+ final force = argResults!['force'] as String?;
412
+ if (force != null) {
413
+ patch['shouldForceUpdate'] = force == 'true' || force == '1';
414
+ }
415
+ if (patch.isEmpty) {
416
+ throw PackException('Nothing to update — pass at least one flag.', 64);
417
+ }
418
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
419
+ final backend = requireBackend(cfg, override: backendOverride);
420
+ banner('bundle · update');
421
+ await spinner(
422
+ () async {
423
+ await backend.db.updateBundle(id, patch);
424
+ await backend.db.commitBundle();
425
+ },
426
+ 'Updating $id',
427
+ done: 'Updated',
428
+ );
429
+ final b = await backend.db.getBundleById(id);
430
+ if (b != null) {
431
+ box('bundle', [
432
+ kv('id', cyan(b.id)),
433
+ kv('enabled', b.enabled ? green('yes') : yellow('no')),
434
+ kv('force', b.shouldForceUpdate ? green('yes') : yellow('no')),
435
+ kv('target', b.targetAppVersion ?? dim('-')),
436
+ if (b.message != null) kv('message', b.message!),
437
+ ]);
438
+ }
439
+ });
440
+ }
@@ -1,9 +1,9 @@
1
1
  import 'package:args/args.dart';
2
- import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
2
+ import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
3
3
 
4
4
  import '../ui/ui.dart';
5
5
 
6
- /// `flutter_patcher channel` — manage channels.
6
+ /// `flutter_ota_kit channel` — manage channels.
7
7
  class ChannelCommand extends FlutterPatcherCommand {
8
8
  final FlutterPatcherConfig? config;
9
9
  final Backend? backendOverride;
@@ -76,13 +76,13 @@ class ChannelGetCommand extends FlutterPatcherCommand {
76
76
  @override
77
77
  ArgParser get argParser => ArgParser()
78
78
  ..addOption('backend', abbr: 'b', help: 'Backend provider.')
79
- ..addOption('channel', help: 'Channel.');
79
+ ..addOption('channel', abbr: 'c', help: 'Channel.');
80
80
 
81
81
  @override
82
82
  Future<int> run() => runGuarded(() async {
83
83
  final channel = argResults!['channel'] as String?;
84
84
  if (channel == null || channel.isEmpty) {
85
- throw PackException('Usage: flutter_patcher channel get --channel <channel>', 64);
85
+ throw PackException('Usage: flutter_ota_kit channel get --channel <channel>', 64);
86
86
  }
87
87
  final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
88
88
  final backend = requireBackend(cfg, override: backendOverride);
@@ -117,8 +117,8 @@ class ChannelSetCommand extends FlutterPatcherCommand {
117
117
  @override
118
118
  ArgParser get argParser => ArgParser()
119
119
  ..addOption('backend', abbr: 'b', help: 'Backend provider.')
120
- ..addOption('channel', help: 'Channel.')
121
- ..addOption('bundle-id', help: 'Bundle id.');
120
+ ..addOption('channel', abbr: 'c', help: 'Channel.')
121
+ ..addOption('bundle-id', abbr: 'i', help: 'Bundle id.');
122
122
 
123
123
  @override
124
124
  Future<int> run() => runGuarded(() async {
@@ -129,7 +129,7 @@ class ChannelSetCommand extends FlutterPatcherCommand {
129
129
  bundleId == null ||
130
130
  bundleId.isEmpty) {
131
131
  throw PackException(
132
- 'Usage: flutter_patcher channel set --channel <c> --bundle-id <id>',
132
+ 'Usage: flutter_ota_kit channel set --channel <c> --bundle-id <id>',
133
133
  64,
134
134
  );
135
135
  }