@_nazmiforreal/flutter-ota 0.1.0 → 0.1.2

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 (167) hide show
  1. package/bin/flutter-patcher.js +48 -39
  2. package/dart-src/packages/cli-tools/.gitkeep +0 -0
  3. package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
  4. package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
  5. package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
  6. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
  7. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
  8. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
  9. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
  10. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
  11. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
  12. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
  13. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
  14. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
  15. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
  16. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
  17. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
  18. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
  19. package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
  20. package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
  21. package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
  22. package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
  23. package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
  24. package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
  25. package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
  26. package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
  27. package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
  28. package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
  29. package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
  30. package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
  31. package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
  32. package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
  33. package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
  34. package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
  35. package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
  36. package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
  37. package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
  38. package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
  39. package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
  40. package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
  41. package/dart-src/packages/core/lib/src/bundle.dart +191 -0
  42. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
  43. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
  44. package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
  45. package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
  46. package/dart-src/packages/core/lib/src/metadata.dart +31 -0
  47. package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
  48. package/dart-src/packages/core/lib/src/platform.dart +15 -0
  49. package/dart-src/packages/core/lib/src/rollout.dart +209 -0
  50. package/dart-src/packages/core/lib/src/semver.dart +70 -0
  51. package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
  52. package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
  53. package/dart-src/packages/core/lib/src/status.dart +24 -0
  54. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  55. package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
  56. package/dart-src/packages/core/lib/src/uuid.dart +45 -0
  57. package/dart-src/packages/core/pubspec.yaml +9 -0
  58. package/dart-src/packages/core/tool/dbg.dart +6 -0
  59. package/dart-src/plugins/aws/.gitkeep +0 -0
  60. package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
  61. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
  62. package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
  63. package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
  64. package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
  65. package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
  66. package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
  67. package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
  68. package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
  69. package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
  70. package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
  71. package/dart-src/plugins/aws/pubspec.yaml +22 -0
  72. package/dart-src/plugins/cloudflare/.gitkeep +0 -0
  73. package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
  74. package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
  75. package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
  76. package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
  77. package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
  78. package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
  79. package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
  80. package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
  81. package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
  82. package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
  83. package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
  84. package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
  85. package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
  86. package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
  87. package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
  88. package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
  89. package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
  90. package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
  91. package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
  92. package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
  93. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
  94. package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
  95. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
  96. package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
  97. package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
  98. package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
  99. package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
  100. package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
  101. package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
  102. package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
  103. package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
  104. package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
  105. package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
  106. package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
  107. package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
  108. package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
  109. package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
  110. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
  111. package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
  112. package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
  113. package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
  114. package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
  115. package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
  116. package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
  117. package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
  118. package/dart-src/plugins/postgres/.gitkeep +0 -0
  119. package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
  120. package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
  121. package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
  122. package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
  123. package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
  124. package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
  125. package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
  126. package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
  127. package/dart-src/plugins/postgres/pubspec.yaml +18 -0
  128. package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
  129. package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
  130. package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
  131. package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
  132. package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
  133. package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
  134. package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
  135. package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
  136. package/dart-src/plugins/standalone/.gitkeep +0 -0
  137. package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
  138. package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
  139. package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
  140. package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
  141. package/dart-src/plugins/standalone/pubspec.yaml +18 -0
  142. package/dart-src/plugins/supabase/lib/edge.dart +10 -0
  143. package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
  144. package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
  145. package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
  146. package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
  147. package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
  148. package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
  149. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
  150. package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
  151. package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
  152. package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
  153. package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
  154. package/dart-src/plugins/supabase/pubspec.yaml +19 -0
  155. package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
  156. package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
  157. package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
  158. package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
  159. package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
  160. package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
  161. package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
  162. package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
  163. package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
  164. package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
  165. package/package.json +2 -1
  166. package/scripts/postinstall.js +53 -25
  167. /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
@@ -3,57 +3,66 @@
3
3
 
4
4
  // Launcher for the flutter_patcher CLI.
5
5
  //
6
- // Resolves the prebuilt Dart binary shipped next to this script (platform
7
- // specific), falling back to `dart run` when Dart is available and no binary
8
- // is present. This lets `npm i -g flutter-patcher` work on any machine that
9
- // either ships a prebuilt binary or has the Dart SDK installed.
6
+ // Resolves a platform+arch-specific prebuilt binary (`flutter-patcher-<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.
10
11
 
11
12
  const { spawnSync } = require('child_process');
12
13
  const fs = require('fs');
13
14
  const path = require('path');
14
15
 
15
- const platform =
16
- process.platform === 'win32'
17
- ? 'windows'
18
- : process.platform === 'darwin'
19
- ? 'macos'
20
- : 'linux';
16
+ function platform() {
17
+ if (process.platform === 'win32') return 'windows';
18
+ if (process.platform === 'darwin') return 'macos';
19
+ return 'linux';
20
+ }
21
+
22
+ function arch() {
23
+ // Only x64/arm64 matter for Dart AOT; everything else falls back to Dart VM.
24
+ return process.arch === 'arm64' ? 'arm64' : 'x64';
25
+ }
26
+
27
+ const platformName = platform();
28
+ const archName = arch();
21
29
  const ext = process.platform === 'win32' ? '.exe' : '';
22
30
  const binDir = __dirname;
23
- const binName = `flutter-patcher-${platform}${ext}`;
31
+ const binName = `flutter-patcher-${platformName}-${archName}${ext}`;
24
32
  const binPath = path.join(binDir, binName);
25
33
 
26
- function run(bin, args) {
27
- const res = spawnSync(bin, args, { stdio: 'inherit', windowsHide: false });
34
+ const sourceEntry = path.join(
35
+ binDir, '..', 'dart-src', 'packages', 'cli-tools', 'bin', 'flutter_patcher.dart',
36
+ );
37
+
38
+ function run(bin, args, cwd) {
39
+ const res = spawnSync(bin, args, { stdio: 'inherit', windowsHide: false, cwd });
28
40
  process.exit(res.status == null ? 1 : res.status);
29
41
  }
30
42
 
31
43
  if (fs.existsSync(binPath)) {
32
44
  run(binPath, process.argv.slice(2));
33
- return;
45
+ } else if (
46
+ spawnSync('dart', ['--version'], { stdio: 'ignore' }).status === 0 &&
47
+ fs.existsSync(sourceEntry)
48
+ ) {
49
+ // Ensure dependencies are resolved before running from source.
50
+ const cliDir = path.dirname(path.dirname(sourceEntry));
51
+ const getRes = spawnSync('dart', ['pub', 'get'], { stdio: 'inherit', cwd: cliDir });
52
+ if (getRes.status !== 0) {
53
+ console.error(
54
+ 'flutter-patcher: `dart pub get` failed. Install dependencies or build ' +
55
+ `a prebuilt manually:\n cd ${cliDir} && dart compile exe ` +
56
+ `bin/flutter_patcher.dart -o ${binPath}`,
57
+ );
58
+ process.exit(1);
59
+ }
60
+ run('dart', [sourceEntry, ...process.argv.slice(2)]);
61
+ } else {
62
+ console.error(
63
+ `flutter-patcher: no prebuilt binary for ${platformName}-${archName} and ` +
64
+ 'the Dart SDK was not found. Install Dart (https://dart.dev) or use a ' +
65
+ 'platform/arch with a shipped prebuilt binary.',
66
+ );
67
+ process.exit(1);
34
68
  }
35
-
36
- // Fallback: run from source via the Dart SDK (works when this npm package is
37
- // installed from the monorepo, since the Dart package lives at
38
- // ../../packages/cli-tools/bin/flutter_patcher.dart).
39
- const repoEntry = path.join(
40
- binDir,
41
- '..',
42
- '..',
43
- '..',
44
- 'packages',
45
- 'cli-tools',
46
- 'bin',
47
- 'flutter-patcher.dart',
48
- );
49
- const dartCheck = spawnSync('dart', ['--version'], { stdio: 'ignore' });
50
- if (dartCheck.status === 0 && fs.existsSync(repoEntry)) {
51
- run('dart', [repoEntry, ...process.argv.slice(2)]);
52
- return;
53
- }
54
-
55
- console.error(
56
- 'flutter-patcher: no prebuilt binary for this platform and Dart SDK not ' +
57
- 'found. Install Dart (https://dart.dev) or ship a prebuilt binary.',
58
- );
59
- process.exit(1);
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
+ }