@_nazmiforreal/flutter-ota 0.1.0 → 0.1.1

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 (177) hide show
  1. package/bin/flutter-patcher.js +37 -39
  2. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +496 -0
  3. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +743 -0
  4. package/dart-src/packages/cli-tools/.gitkeep +0 -0
  5. package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
  6. package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
  7. package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
  8. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
  9. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
  10. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
  11. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
  12. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
  13. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
  14. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
  15. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
  16. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
  17. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
  18. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
  19. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
  20. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
  21. package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
  22. package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
  23. package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
  24. package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
  25. package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
  26. package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  27. package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  28. package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  29. package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  30. package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  31. package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
  32. package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
  33. package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
  35. package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
  36. package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
  37. package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
  38. package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
  39. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
  40. package/dart-src/packages/cli-tools/pubspec.lock +638 -0
  41. package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
  42. package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
  43. package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
  44. package/dart-src/packages/core/lib/src/bundle.dart +191 -0
  45. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
  46. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
  47. package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
  48. package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
  49. package/dart-src/packages/core/lib/src/metadata.dart +31 -0
  50. package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
  51. package/dart-src/packages/core/lib/src/platform.dart +15 -0
  52. package/dart-src/packages/core/lib/src/rollout.dart +209 -0
  53. package/dart-src/packages/core/lib/src/semver.dart +70 -0
  54. package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
  55. package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
  56. package/dart-src/packages/core/lib/src/status.dart +24 -0
  57. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  58. package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
  59. package/dart-src/packages/core/lib/src/uuid.dart +45 -0
  60. package/dart-src/packages/core/pubspec.lock +381 -0
  61. package/dart-src/packages/core/pubspec.yaml +9 -0
  62. package/dart-src/packages/core/tool/dbg.dart +6 -0
  63. package/dart-src/plugins/aws/.gitkeep +0 -0
  64. package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
  65. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
  66. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
  67. package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
  68. package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
  69. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
  70. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
  71. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
  72. package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
  73. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
  74. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
  75. package/dart-src/plugins/aws/pubspec.lock +435 -0
  76. package/dart-src/plugins/aws/pubspec.yaml +22 -0
  77. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  78. package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
  79. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
  80. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
  81. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
  82. package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
  83. package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
  84. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
  85. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
  86. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
  87. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
  88. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
  89. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
  90. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
  91. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
  92. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
  93. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
  94. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
  95. package/dart-src/plugins/cloudflare/pubspec.lock +411 -0
  96. package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
  97. package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
  98. package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
  99. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
  100. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
  101. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
  102. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
  103. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
  104. package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
  105. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
  106. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
  107. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
  108. package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
  109. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
  110. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
  111. package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
  112. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
  113. package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
  114. package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
  115. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
  116. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
  117. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
  118. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
  119. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
  120. package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
  121. package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
  122. package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
  123. package/dart-src/plugins/plugin-core/pubspec.lock +396 -0
  124. package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
  125. package/dart-src/plugins/postgres/.gitkeep +0 -0
  126. package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
  127. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
  128. package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
  129. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
  130. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
  131. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
  132. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
  133. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
  134. package/dart-src/plugins/postgres/pubspec.lock +427 -0
  135. package/dart-src/plugins/postgres/pubspec.yaml +18 -0
  136. package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
  137. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
  138. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
  139. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
  140. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
  141. package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
  142. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
  143. package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
  144. package/dart-src/plugins/standalone/.gitkeep +0 -0
  145. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
  146. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
  147. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
  148. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
  149. package/dart-src/plugins/standalone/pubspec.lock +411 -0
  150. package/dart-src/plugins/standalone/pubspec.yaml +18 -0
  151. package/dart-src/plugins/supabase/lib/edge.dart +10 -0
  152. package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
  153. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
  154. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
  155. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
  156. package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
  157. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
  158. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
  159. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
  160. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
  161. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
  162. package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
  163. package/dart-src/plugins/supabase/pubspec.lock +515 -0
  164. package/dart-src/plugins/supabase/pubspec.yaml +19 -0
  165. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
  166. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
  167. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
  168. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
  169. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
  170. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
  171. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
  172. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
  173. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
  174. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
  175. package/package.json +2 -1
  176. package/scripts/postinstall.js +32 -23
  177. /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
File without changes
@@ -0,0 +1,9 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
4
+
5
+ /// `flutter_patcher` executable entry point.
6
+ Future<void> main(List<String> args) async {
7
+ final code = await run(args);
8
+ if (code != 0) exitCode = code;
9
+ }
@@ -0,0 +1,73 @@
1
+ /// flutter_patcher_cli — command-line interface for flutter_patcher.
2
+ ///
3
+ /// Faithful Dart port of hot-updater's `@hot-updater/cli`: deploy bundles,
4
+ /// manage channels/rollbacks, run migrations, and inspect the backend.
5
+ library;
6
+
7
+ import 'dart:io';
8
+
9
+ import 'package:args/command_runner.dart';
10
+
11
+ export 'src/backend.dart';
12
+ export 'src/cli_base.dart';
13
+ export 'src/config.dart';
14
+ export 'src/operations.dart';
15
+ export 'src/pack.dart';
16
+ export 'src/sign.dart';
17
+ export 'src/util.dart';
18
+
19
+ export 'src/commands/init.dart';
20
+ export 'src/commands/config_command.dart';
21
+ export 'src/commands/keys.dart';
22
+ export 'src/commands/doctor.dart';
23
+ export 'src/commands/fingerprint.dart';
24
+ export 'src/commands/deploy.dart';
25
+ export 'src/commands/bundle.dart';
26
+ export 'src/commands/build.dart';
27
+ export 'src/commands/rollback.dart';
28
+ export 'src/commands/channel.dart';
29
+ export 'src/commands/migrate.dart';
30
+ export 'src/commands/console.dart';
31
+
32
+ import 'src/commands/build.dart';
33
+ import 'src/commands/bundle.dart';
34
+ import 'src/commands/channel.dart';
35
+ import 'src/commands/config_command.dart';
36
+ import 'src/commands/console.dart';
37
+ import 'src/commands/deploy.dart';
38
+ import 'src/commands/doctor.dart';
39
+ import 'src/commands/fingerprint.dart';
40
+ import 'src/commands/init.dart';
41
+ import 'src/commands/keys.dart';
42
+ import 'src/commands/migrate.dart';
43
+ import 'src/commands/rollback.dart';
44
+
45
+ /// Entry point: build the command runner and dispatch [args].
46
+ Future<int> run(List<String> args) async {
47
+ final runner = CommandRunner<int>(
48
+ 'flutter_patcher',
49
+ 'flutter_patcher CLI — OTA code push for Flutter (hot-updater compatible).',
50
+ )
51
+ ..addCommand(InitCommand())
52
+ ..addCommand(ConfigCommand())
53
+ ..addCommand(KeysCommand())
54
+ ..addCommand(DoctorCommand())
55
+ ..addCommand(FingerprintCommand())
56
+ ..addCommand(DeployCommand())
57
+ ..addCommand(BuildCommand())
58
+ ..addCommand(BundleCommand())
59
+ ..addCommand(RollbackCommand())
60
+ ..addCommand(ChannelCommand())
61
+ ..addCommand(MigrateCommand())
62
+ ..addCommand(ConsoleCommand());
63
+
64
+ try {
65
+ final result = await runner.run(args);
66
+ return result ?? 0;
67
+ } on UsageException catch (e) {
68
+ stderr.writeln(e.message);
69
+ stderr.writeln('');
70
+ stderr.writeln(e.usage);
71
+ return 64;
72
+ }
73
+ }
@@ -0,0 +1,162 @@
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';
7
+
8
+ import 'config.dart';
9
+
10
+ /// Backend handle wiring the database + node storage profiles used by the CLI.
11
+ class Backend {
12
+ const Backend({
13
+ required this.db,
14
+ required this.storage,
15
+ this.basePath,
16
+ });
17
+
18
+ final DatabasePlugin db;
19
+ final NodeStorageProfile storage;
20
+
21
+ /// Optional storage key prefix (e.g. "bundles") honored when building keys.
22
+ final String? basePath;
23
+
24
+ /// Build the storage object key for a bundle artifact.
25
+ String storageKeyFor(String bundleId, String file) {
26
+ final parts = [
27
+ if (basePath != null && basePath!.isNotEmpty) basePath!,
28
+ bundleId,
29
+ file,
30
+ ];
31
+ return parts.join('/');
32
+ }
33
+ }
34
+
35
+ /// Construct a [Backend] for the configured provider.
36
+ ///
37
+ /// Supports `supabase`, `postgres`, `cloudflare`, and `aws`. Credential
38
+ /// precedence is: explicit factory/flag > environment variable > JSON config
39
+ /// (see the `resolve*Config` helpers in `config.dart`). Test seams let callers
40
+ /// inject mock clients (e.g. `postgresClientFactory`).
41
+ Backend resolveBackend(
42
+ FlutterPatcherConfig config, {
43
+ SupabaseClientFactory? supabaseClientFactory,
44
+ D1ClientFactory? d1ClientFactory,
45
+ R2S3ClientFactory? r2ClientFactory,
46
+ PostgresClientFactory? postgresClientFactory,
47
+ AwsS3ClientLike Function(S3DatabaseConfig config)? awsS3ClientFactory,
48
+ AwsCloudFrontClientLike Function(S3DatabaseConfig config)?
49
+ awsCloudFrontClientFactory,
50
+ AwsS3ClientLike Function(AwsS3StorageConfig config)? awsStorageClientFactory,
51
+ StandaloneClientFactory? standaloneClientFactory,
52
+ }) {
53
+ switch (config.provider) {
54
+ case 'supabase':
55
+ final dbConfig = resolveSupabaseConfig(
56
+ config,
57
+ clientFactory: supabaseClientFactory,
58
+ );
59
+ final storageConfig = resolveSupabaseStorageConfig(
60
+ config,
61
+ clientFactory: supabaseClientFactory,
62
+ );
63
+ final db = supabaseDatabase(dbConfig)();
64
+ final storage = supabaseStorage(storageConfig);
65
+ final node = storage.profiles.node;
66
+ if (node == null) {
67
+ throw StateError('Supabase storage plugin exposes no node profile.');
68
+ }
69
+ return Backend(
70
+ db: db,
71
+ storage: node,
72
+ basePath: storageConfig.basePath,
73
+ );
74
+
75
+ case 'postgres':
76
+ final dbConfig = resolvePostgresDatabaseConfig(
77
+ config,
78
+ clientFactory: postgresClientFactory,
79
+ );
80
+ final storageConfig = resolvePostgresStorageConfig(
81
+ config,
82
+ clientFactory: postgresClientFactory,
83
+ );
84
+ final db = postgresDatabase(dbConfig)();
85
+ final storage = postgresStorage(storageConfig);
86
+ final node = storage.profiles.node;
87
+ if (node == null) {
88
+ throw StateError('Postgres storage plugin exposes no node profile.');
89
+ }
90
+ return Backend(
91
+ db: db,
92
+ storage: node,
93
+ basePath: storageConfig.basePath,
94
+ );
95
+
96
+ case 'cloudflare':
97
+ final dbConfig = resolveCloudflareDatabaseConfig(
98
+ config,
99
+ clientFactory: d1ClientFactory,
100
+ );
101
+ final storageConfig = resolveCloudflareStorageConfig(
102
+ config,
103
+ clientFactory: r2ClientFactory,
104
+ );
105
+ final db = d1Database(dbConfig)();
106
+ final storage = r2Storage(storageConfig);
107
+ final node = storage.profiles.node;
108
+ if (node == null) {
109
+ throw StateError('R2 storage plugin exposes no node profile.');
110
+ }
111
+ return Backend(
112
+ db: db,
113
+ storage: node,
114
+ basePath: storageConfig.basePath,
115
+ );
116
+
117
+ case 'aws':
118
+ final dbConfig = resolveAwsDatabaseConfig(
119
+ config,
120
+ clientFactory: awsS3ClientFactory,
121
+ cloudfrontClientFactory: awsCloudFrontClientFactory,
122
+ );
123
+ final storageConfig = resolveAwsStorageConfig(
124
+ config,
125
+ clientFactory: awsStorageClientFactory,
126
+ );
127
+ final db = s3Database(dbConfig)();
128
+ final storage = s3Storage(storageConfig);
129
+ final node = storage.profiles.node;
130
+ if (node == null) {
131
+ throw StateError('S3 storage plugin exposes no node profile.');
132
+ }
133
+ return Backend(
134
+ db: db,
135
+ storage: node,
136
+ basePath: storageConfig.basePath,
137
+ );
138
+
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
+ default:
157
+ throw UnsupportedError(
158
+ 'Provider "${config.provider}" is not supported. '
159
+ 'Supported: supabase, postgres, cloudflare, aws, standalone.',
160
+ );
161
+ }
162
+ }
@@ -0,0 +1,56 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:args/args.dart';
4
+
5
+ import 'backend.dart';
6
+ import 'config.dart';
7
+
8
+ /// Run [body], mapping errors to a non-zero exit code with a clean message.
9
+
10
+ /// Run [body], mapping errors to a non-zero exit code with a clean message.
11
+ Future<int> runGuarded(Future<void> Function() body) async {
12
+ try {
13
+ await body();
14
+ return 0;
15
+ } on StateError catch (e) {
16
+ stderr.writeln('error: ${e.message}');
17
+ return 1;
18
+ } catch (e) {
19
+ stderr.writeln('error: $e');
20
+ return 1;
21
+ }
22
+ }
23
+
24
+ /// Resolve a [Backend] from config, or return the injected override (tests).
25
+ Backend requireBackend(FlutterPatcherConfig? config, {Backend? override}) {
26
+ if (override != null) return override;
27
+ if (config == null) {
28
+ throw StateError(
29
+ 'No configuration found. Run `flutter_patcher init` or set '
30
+ 'SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY.',
31
+ );
32
+ }
33
+ return resolveBackend(config);
34
+ }
35
+
36
+ /// Apply a `--backend` flag override to the loaded config.
37
+ ///
38
+ /// Returns `cfg` unchanged when no `--backend` flag is present. When the flag
39
+ /// is set but no config exists yet, a minimal config with that provider is
40
+ /// created (so env-driven credentials still resolve).
41
+ FlutterPatcherConfig? effectiveConfig(
42
+ FlutterPatcherConfig? cfg,
43
+ ArgResults argResults,
44
+ ) {
45
+ final backend = argResults['backend'] as String?;
46
+ if (backend == null) return cfg;
47
+ final base = cfg ??
48
+ FlutterPatcherConfig(
49
+ provider: backend,
50
+ supabase: const SupabaseConfigJson(),
51
+ postgres: const PostgresConfigJson(),
52
+ cloudflare: const CloudflareConfigJson(),
53
+ aws: const AwsConfigJson(),
54
+ );
55
+ return base.copyWith(provider: backend);
56
+ }
@@ -0,0 +1,99 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:args/command_runner.dart';
4
+ import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
5
+
6
+ /// `flutter_patcher build` — package a release APK into a device-compatible
7
+ /// OTA patch (`dist/patch.zip`) that the `deploy` command can publish.
8
+ ///
9
+ /// Includes every ABI found in the APK so a single bundle installs on all
10
+ /// device architectures.
11
+ class BuildCommand extends Command<int> {
12
+ BuildCommand() {
13
+ argParser
14
+ ..addOption(
15
+ 'apk',
16
+ abbr: 'a',
17
+ help: 'Path to the release APK to extract libapp.so and assets from.',
18
+ )
19
+ ..addMultiOption(
20
+ 'assets',
21
+ help: 'Asset keys to include as overlay updates (repeatable).',
22
+ )
23
+ ..addOption(
24
+ 'version',
25
+ help: 'Patch version string (stored in manifest.version).',
26
+ )
27
+ ..addOption(
28
+ 'target-version-code',
29
+ help: 'Host APK versionCode this patch targets (integer).',
30
+ )
31
+ ..addOption(
32
+ 'abi',
33
+ help: 'Preferred ABI to report (default: all ABIs found in APK).',
34
+ )
35
+ ..addOption(
36
+ 'out',
37
+ abbr: 'o',
38
+ defaultsTo: 'dist',
39
+ help: 'Output directory for patch.zip and manifest.json.',
40
+ );
41
+ }
42
+
43
+ @override
44
+ String get name => 'build';
45
+
46
+ @override
47
+ String get description =>
48
+ 'Build a device-compatible OTA patch (patch.zip) from a release APK.';
49
+
50
+ @override
51
+ Future<int> run() async {
52
+ final results = argResults!;
53
+ final apk = results['apk'] as String?;
54
+ if (apk == null || apk.isEmpty) {
55
+ stderr.writeln('error: missing required --apk <path>');
56
+ return 64;
57
+ }
58
+ final version = results['version'] as String?;
59
+ if (version == null || version.isEmpty) {
60
+ stderr.writeln('error: missing required --version <string>');
61
+ return 64;
62
+ }
63
+ final targetVersionCodeRaw = results['target-version-code'] as String?;
64
+ if (targetVersionCodeRaw == null || targetVersionCodeRaw.isEmpty) {
65
+ stderr.writeln('error: missing required --target-version-code <int>');
66
+ return 64;
67
+ }
68
+ final targetVersionCode = int.tryParse(targetVersionCodeRaw);
69
+ if (targetVersionCode == null) {
70
+ stderr.writeln('error: --target-version-code must be an integer');
71
+ return 64;
72
+ }
73
+
74
+ try {
75
+ final result = await packPatch(
76
+ apkPath: apk,
77
+ version: version,
78
+ targetVersionCode: targetVersionCode,
79
+ abi: results['abi'] as String?,
80
+ requestedAssets: results['assets'] as List<String>,
81
+ out: results['out'] as String,
82
+ );
83
+ stdout.writeln('[build] version: ${result.version}');
84
+ stdout.writeln('[build] targetVersionCode: ${result.targetVersionCode}');
85
+ stdout.writeln('[build] abis: ${result.abis.join(', ')}');
86
+ stdout.writeln('[build] bundle md5: ${result.md5}');
87
+ stdout.writeln('[build] overlay assets: ${result.assetCount}');
88
+ stdout.writeln('[build] payload: ${result.payloadPath}');
89
+ stdout.writeln('[build] manifest: ${result.manifestPath}');
90
+ return 0;
91
+ } on PackException catch (e) {
92
+ stderr.writeln('error: ${e.message}');
93
+ return e.exitCode;
94
+ } on Object catch (e) {
95
+ stderr.writeln('error: $e');
96
+ return 1;
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,151 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:args/args.dart';
4
+ import 'package:args/command_runner.dart';
5
+
6
+ import '../backend.dart';
7
+ import '../cli_base.dart';
8
+ import '../config.dart';
9
+ import '../operations.dart';
10
+
11
+ /// `flutter_patcher bundle` — manage bundles.
12
+ class BundleCommand extends Command<int> {
13
+ BundleCommand({this.config, this.backendOverride});
14
+
15
+ final FlutterPatcherConfig? config;
16
+ final Backend? backendOverride;
17
+
18
+ @override
19
+ String get name => 'bundle';
20
+
21
+ @override
22
+ String get description => 'Manage bundles (list / delete / promote).';
23
+
24
+ @override
25
+ Future<int> run() => runGuarded(() async {
26
+ print(description);
27
+ print('');
28
+ print('Subcommands:');
29
+ print(' list List bundles');
30
+ print(' delete <id> Delete a bundle');
31
+ print(' promote <id> -c <c> Promote a bundle to a channel');
32
+ });
33
+ }
34
+
35
+ class BundleListCommand extends Command<int> {
36
+ BundleListCommand({this.config, this.backendOverride});
37
+
38
+ final FlutterPatcherConfig? config;
39
+ final Backend? backendOverride;
40
+
41
+ @override
42
+ String get name => 'list';
43
+
44
+ @override
45
+ String get description => 'List bundles.';
46
+
47
+ @override
48
+ ArgParser get argParser => ArgParser()
49
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
50
+ ..addOption('channel', abbr: 'c', help: 'Filter by channel.')
51
+ ..addOption('platform', abbr: 'p', help: 'Filter by platform.')
52
+ ..addOption('enabled', help: 'Filter by enabled (true/false).')
53
+ ..addOption('limit', abbr: 'l', defaultsTo: '20', help: 'Page size.');
54
+
55
+ @override
56
+ Future<int> run() => runGuarded(() async {
57
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
58
+ final backend = requireBackend(cfg, override: backendOverride);
59
+ final enabledRaw = argResults!['enabled'] as String?;
60
+ final enabled = enabledRaw == null
61
+ ? null
62
+ : (enabledRaw == 'true' || enabledRaw == '1');
63
+ final res = await listBundles(
64
+ backend,
65
+ ListOptions(
66
+ channel: argResults!['channel'] as String?,
67
+ platform: argResults!['platform'] as String?,
68
+ enabled: enabled,
69
+ limit: int.parse(argResults!['limit'] as String),
70
+ ),
71
+ );
72
+ if (res.data.isEmpty) {
73
+ stdout.writeln('(no bundles)');
74
+ return;
75
+ }
76
+ stdout.writeln('id channel '
77
+ 'enabled platform target');
78
+ for (final b in res.data) {
79
+ final target = b.targetAppVersion ?? b.fingerprintHash ?? '-';
80
+ stdout.writeln('${b.id} ${b.channel.padRight(12)} '
81
+ '${(b.enabled ? 'yes' : 'no').padRight(7)} '
82
+ '${b.platform.value.padRight(7)} $target');
83
+ }
84
+ stdout.writeln('');
85
+ stdout.writeln('total: ${res.pagination.total}');
86
+ });
87
+ }
88
+
89
+ class BundleDeleteCommand extends Command<int> {
90
+ BundleDeleteCommand({this.config, this.backendOverride});
91
+
92
+ final FlutterPatcherConfig? config;
93
+ final Backend? backendOverride;
94
+
95
+ @override
96
+ String get name => 'delete';
97
+
98
+ @override
99
+ String get description => 'Delete a bundle by id.';
100
+
101
+ @override
102
+ ArgParser get argParser => ArgParser()
103
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
104
+ ..addOption('id', help: 'Bundle id.');
105
+
106
+ @override
107
+ Future<int> run() => runGuarded(() async {
108
+ final id = argResults!['id'] as String?;
109
+ if (id == null || id.isEmpty) {
110
+ throw StateError('Usage: flutter_patcher bundle delete <id>');
111
+ }
112
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
113
+ final backend = requireBackend(cfg, override: backendOverride);
114
+ await deleteBundle(backend, id);
115
+ stdout.writeln('Deleted bundle $id');
116
+ });
117
+ }
118
+
119
+ class BundlePromoteCommand extends Command<int> {
120
+ BundlePromoteCommand({this.config, this.backendOverride});
121
+
122
+ final FlutterPatcherConfig? config;
123
+ final Backend? backendOverride;
124
+
125
+ @override
126
+ String get name => 'promote';
127
+
128
+ @override
129
+ String get description => 'Promote a bundle to a channel.';
130
+
131
+ @override
132
+ ArgParser get argParser => ArgParser()
133
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
134
+ ..addOption('id', help: 'Bundle id.')
135
+ ..addOption('channel', abbr: 'c', help: 'Target channel.');
136
+
137
+ @override
138
+ Future<int> run() => runGuarded(() async {
139
+ final id = argResults!['id'] as String?;
140
+ final channel = argResults!['channel'] as String?;
141
+ if (id == null || id.isEmpty || channel == null || channel.isEmpty) {
142
+ throw StateError(
143
+ 'Usage: flutter_patcher bundle promote <id> --channel <channel>',
144
+ );
145
+ }
146
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
147
+ final backend = requireBackend(cfg, override: backendOverride);
148
+ await promoteBundle(backend, id, channel);
149
+ stdout.writeln('Promoted bundle $id to channel $channel');
150
+ });
151
+ }
@@ -0,0 +1,140 @@
1
+ import 'dart:io';
2
+
3
+ import 'package:args/args.dart';
4
+ import 'package:args/command_runner.dart';
5
+
6
+ import '../backend.dart';
7
+ import '../cli_base.dart';
8
+ import '../config.dart';
9
+ import '../operations.dart';
10
+
11
+ /// `flutter_patcher channel` — manage channels.
12
+ class ChannelCommand extends Command<int> {
13
+ final FlutterPatcherConfig? config;
14
+ final Backend? backendOverride;
15
+
16
+ ChannelCommand({this.config, this.backendOverride}) {
17
+ addSubcommand(ChannelListCommand(config: config, backendOverride: backendOverride));
18
+ addSubcommand(ChannelGetCommand(config: config, backendOverride: backendOverride));
19
+ addSubcommand(ChannelSetCommand(config: config, backendOverride: backendOverride));
20
+ }
21
+
22
+ @override
23
+ String get name => 'channel';
24
+
25
+ @override
26
+ String get description => 'Manage channels (list / get / set).';
27
+
28
+ @override
29
+ Future<int> run() => runGuarded(() async {
30
+ print(description);
31
+ print('');
32
+ print('Subcommands:');
33
+ print(' list List channels');
34
+ print(' get <channel> Show the live bundle for a channel');
35
+ print(' set <c> <id> Promote a bundle to a channel');
36
+ });
37
+ }
38
+
39
+ class ChannelListCommand extends Command<int> {
40
+ ChannelListCommand({this.config, this.backendOverride});
41
+
42
+ final FlutterPatcherConfig? config;
43
+ final Backend? backendOverride;
44
+
45
+ @override
46
+ String get name => 'list';
47
+
48
+ @override
49
+ String get description => 'List channels.';
50
+
51
+ @override
52
+ ArgParser get argParser => ArgParser()
53
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.');
54
+
55
+ @override
56
+ Future<int> run() => runGuarded(() async {
57
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
58
+ final backend = requireBackend(cfg, override: backendOverride);
59
+ final channels = await listChannels(backend);
60
+ if (channels.isEmpty) {
61
+ stdout.writeln('(no channels)');
62
+ return;
63
+ }
64
+ for (final c in channels) {
65
+ stdout.writeln(' $c');
66
+ }
67
+ });
68
+ }
69
+
70
+ class ChannelGetCommand extends Command<int> {
71
+ ChannelGetCommand({this.config, this.backendOverride});
72
+
73
+ final FlutterPatcherConfig? config;
74
+ final Backend? backendOverride;
75
+
76
+ @override
77
+ String get name => 'get';
78
+
79
+ @override
80
+ String get description => 'Show the live (enabled) bundle for a channel.';
81
+
82
+ @override
83
+ ArgParser get argParser => ArgParser()
84
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
85
+ ..addOption('channel', help: 'Channel.');
86
+
87
+ @override
88
+ Future<int> run() => runGuarded(() async {
89
+ final channel = argResults!['channel'] as String?;
90
+ if (channel == null || channel.isEmpty) {
91
+ throw StateError('Usage: flutter_patcher channel get <channel>');
92
+ }
93
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
94
+ final backend = requireBackend(cfg, override: backendOverride);
95
+ final bundle = await getChannel(backend, channel);
96
+ if (bundle == null) {
97
+ stdout.writeln('(no live bundle on channel "$channel")');
98
+ return;
99
+ }
100
+ stdout.writeln('channel "$channel" -> bundle ${bundle.id} '
101
+ '(${bundle.platform.value})');
102
+ });
103
+ }
104
+
105
+ class ChannelSetCommand extends Command<int> {
106
+ ChannelSetCommand({this.config, this.backendOverride});
107
+
108
+ final FlutterPatcherConfig? config;
109
+ final Backend? backendOverride;
110
+
111
+ @override
112
+ String get name => 'set';
113
+
114
+ @override
115
+ String get description => 'Promote a bundle to a channel.';
116
+
117
+ @override
118
+ ArgParser get argParser => ArgParser()
119
+ ..addOption('backend', abbr: 'b', help: 'Backend provider.')
120
+ ..addOption('channel', help: 'Channel.')
121
+ ..addOption('bundle-id', help: 'Bundle id.');
122
+
123
+ @override
124
+ Future<int> run() => runGuarded(() async {
125
+ final channel = argResults!['channel'] as String?;
126
+ final bundleId = argResults!['bundle-id'] as String?;
127
+ if (channel == null ||
128
+ channel.isEmpty ||
129
+ bundleId == null ||
130
+ bundleId.isEmpty) {
131
+ throw StateError(
132
+ 'Usage: flutter_patcher channel set --channel <c> --bundle-id <id>',
133
+ );
134
+ }
135
+ final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
136
+ final backend = requireBackend(cfg, override: backendOverride);
137
+ await promoteBundle(backend, bundleId, channel);
138
+ stdout.writeln('Channel "$channel" -> bundle $bundleId');
139
+ });
140
+ }