@_nazmiforreal/flutter-ota 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/bin/{flutter-patcher-linux-x64 → flutter-ota-linux-x64} +0 -0
- package/bin/{flutter-patcher.js → flutter-ota.js} +4 -4
- package/dart-src/packages/cli-tools/bin/{flutter_patcher.dart → flutter_ota_kit.dart} +2 -2
- package/dart-src/packages/cli-tools/lib/{flutter_patcher_cli.dart → flutter_ota_kit_cli.dart} +5 -3
- package/dart-src/packages/cli-tools/lib/src/backend.dart +6 -25
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +10 -4
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +5 -3
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +279 -10
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +7 -7
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +5 -5
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +4 -4
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +4 -4
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +3 -3
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +294 -45
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +1 -1
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +59 -24
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +5 -5
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +117 -0
- package/dart-src/packages/cli-tools/lib/src/config.dart +29 -78
- package/dart-src/packages/cli-tools/lib/src/operations.dart +4 -4
- package/dart-src/packages/cli-tools/lib/src/pack.dart +12 -6
- package/dart-src/packages/cli-tools/lib/src/runner.dart +21 -3
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +2 -2
- package/dart-src/packages/cli-tools/lib/src/verbose.dart +3 -0
- package/dart-src/packages/cli-tools/migrations/aws/README.md +7 -0
- package/dart-src/packages/cli-tools/migrations/postgres/bundles.sql +45 -0
- package/dart-src/packages/cli-tools/migrations/postgres/get_target_app_version_list.sql +21 -0
- package/dart-src/packages/cli-tools/migrations/postgres/get_update_info_by_app_version.sql +126 -0
- package/dart-src/packages/cli-tools/migrations/postgres/get_update_info_by_fingerprint_hash.sql +125 -0
- package/dart-src/packages/cli-tools/migrations/postgres/hash_user_id.sql +30 -0
- package/dart-src/packages/cli-tools/migrations/postgres/is_device_eligible.sql +237 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +11 -0
- package/dart-src/packages/cli-tools/pubspec.yaml +8 -10
- package/dart-src/packages/cli-tools/test/backends_test.dart +4 -4
- package/dart-src/packages/cli-tools/test/build_pack_test.dart +1 -1
- package/dart-src/packages/cli-tools/test/cli_test.dart +3 -3
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +2 -2
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +7 -7
- package/dart-src/packages/core/lib/{flutter_patcher_core.dart → flutter_ota_kit_core.dart} +1 -1
- package/dart-src/packages/core/pubspec.yaml +2 -2
- package/dart-src/packages/core/test/rollout_test.dart +1 -1
- package/dart-src/packages/core/test/semver_test.dart +1 -1
- package/dart-src/packages/core/test/types_test.dart +2 -2
- package/dart-src/packages/core/test/uuid_test.dart +1 -1
- package/dart-src/packages/core/tool/dbg.dart +1 -1
- package/dart-src/plugins/aws/lib/{flutter_patcher_aws.dart → flutter_ota_kit_aws.dart} +0 -2
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +1 -1
- package/dart-src/plugins/aws/lib/src/aws_database.dart +1 -1
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +1 -1
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +1 -1
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +25 -3
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +1 -1
- package/dart-src/plugins/aws/pubspec.yaml +4 -4
- package/dart-src/plugins/aws/test/aws_database_test.dart +3 -3
- package/dart-src/plugins/aws/test/aws_storage_test.dart +2 -2
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +2 -2
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +2 -2
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +1 -1
- package/dart-src/plugins/cloudflare/pubspec.yaml +4 -4
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +2 -2
- package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +2 -2
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +2 -2
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +2 -2
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +2 -2
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +2 -2
- package/dart-src/plugins/plugin-core/lib/{flutter_patcher_plugin_core.dart → flutter_ota_kit_plugin_core.dart} +1 -1
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/types.dart +2 -2
- package/dart-src/plugins/plugin-core/pubspec.yaml +3 -3
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +3 -3
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +5 -5
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +2 -2
- package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +3 -3
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +1 -1
- package/dart-src/plugins/postgres/lib/{flutter_patcher_postgres.dart → flutter_ota_kit_postgres.dart} +1 -1
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +1 -1
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +2 -2
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +2 -2
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +9 -9
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +1 -1
- package/dart-src/plugins/postgres/pubspec.yaml +4 -4
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +2 -2
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +2 -2
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +2 -2
- package/dart-src/plugins/supabase/lib/edge.dart +1 -1
- package/dart-src/plugins/supabase/lib/{flutter_patcher_supabase.dart → flutter_ota_kit_supabase.dart} +1 -1
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +1 -1
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +5 -1
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +2 -2
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +1 -1
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +1 -1
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +2 -5
- package/dart-src/plugins/supabase/pubspec.yaml +4 -4
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +5 -5
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +3 -3
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +3 -3
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +2 -2
- package/migrations/aws/README.md +7 -0
- package/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
- package/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
- package/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
- package/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
- package/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
- package/migrations/cloudflare/bundles.sql +42 -0
- package/migrations/postgres/bundles.sql +45 -0
- package/migrations/postgres/get_target_app_version_list.sql +21 -0
- package/migrations/postgres/get_update_info_by_app_version.sql +126 -0
- package/migrations/postgres/get_update_info_by_fingerprint_hash.sql +125 -0
- package/migrations/postgres/hash_user_id.sql +30 -0
- package/migrations/postgres/is_device_eligible.sql +237 -0
- package/package.json +4 -4
- package/scripts/postinstall.js +15 -15
- package/scripts/vendor.sh +1 -1
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +0 -496
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +0 -743
- package/dart-src/packages/cli-tools/pubspec.lock +0 -638
- package/dart-src/packages/cli-tools/test/standalone_backend_test.dart +0 -315
- package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +0 -72
- package/dart-src/plugins/aws/test/aws_lambda_edge_storage_test.dart +0 -190
- package/dart-src/plugins/standalone/.gitkeep +0 -0
- package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +0 -12
- package/dart-src/plugins/standalone/lib/src/standalone_config.dart +0 -107
- package/dart-src/plugins/standalone/lib/src/standalone_database.dart +0 -233
- package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +0 -175
- package/dart-src/plugins/standalone/pubspec.yaml +0 -18
- package/dart-src/plugins/standalone/test/standalone_test.dart +0 -264
- /package/dart-src/plugins/cloudflare/lib/{flutter_patcher_cloudflare.dart → flutter_ota_kit_cloudflare.dart} +0 -0
|
@@ -6,7 +6,7 @@ import 'package:args/args.dart';
|
|
|
6
6
|
import '../cli_base.dart';
|
|
7
7
|
import '../config.dart';
|
|
8
8
|
|
|
9
|
-
/// `
|
|
9
|
+
/// `flutter_ota_kit config` — get/set/list config values.
|
|
10
10
|
class ConfigCommand extends FlutterPatcherCommand {
|
|
11
11
|
ConfigCommand() {
|
|
12
12
|
addSubcommand(ConfigGetCommand());
|
|
@@ -52,13 +52,13 @@ class ConfigGetCommand extends FlutterPatcherCommand {
|
|
|
52
52
|
String get description => 'Print a config value by dot-path (e.g. supabase.url).';
|
|
53
53
|
|
|
54
54
|
@override
|
|
55
|
-
ArgParser get argParser => ArgParser()..addOption('key', help: 'Dot-path key.');
|
|
55
|
+
ArgParser get argParser => ArgParser()..addOption('key', abbr: 'k', help: 'Dot-path key.');
|
|
56
56
|
|
|
57
57
|
@override
|
|
58
58
|
Future<int> run() => runGuarded(() async {
|
|
59
59
|
final key = argResults!['key'] as String?;
|
|
60
60
|
if (key == null || key.isEmpty) {
|
|
61
|
-
throw StateError('Usage:
|
|
61
|
+
throw StateError('Usage: flutter_ota_kit config get <key>');
|
|
62
62
|
}
|
|
63
63
|
final value = readPath(_loadProjectJson(), key);
|
|
64
64
|
if (value == null) {
|
|
@@ -80,7 +80,7 @@ class ConfigSetCommand extends FlutterPatcherCommand {
|
|
|
80
80
|
|
|
81
81
|
@override
|
|
82
82
|
ArgParser get argParser => ArgParser()
|
|
83
|
-
..addOption('key', help: 'Dot-path key.')
|
|
83
|
+
..addOption('key', abbr: 'k', help: 'Dot-path key.')
|
|
84
84
|
..addOption('value', help: 'Value.');
|
|
85
85
|
|
|
86
86
|
@override
|
|
@@ -88,7 +88,7 @@ class ConfigSetCommand extends FlutterPatcherCommand {
|
|
|
88
88
|
final key = argResults!['key'] as String?;
|
|
89
89
|
final value = argResults!['value'] as String?;
|
|
90
90
|
if (key == null || key.isEmpty || value == null) {
|
|
91
|
-
throw StateError('Usage:
|
|
91
|
+
throw StateError('Usage: flutter_ota_kit config set <key> <value>');
|
|
92
92
|
}
|
|
93
93
|
final json = _loadProjectJson();
|
|
94
94
|
writePath(json, key, value);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import 'dart:io';
|
|
2
2
|
|
|
3
3
|
import 'package:args/args.dart';
|
|
4
|
-
import 'package:
|
|
4
|
+
import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
|
|
5
5
|
import 'package:path/path.dart' as p;
|
|
6
6
|
|
|
7
7
|
import '../ui/ui.dart';
|
|
8
8
|
|
|
9
|
-
/// `
|
|
9
|
+
/// `flutter_ota_kit console` — open the web console.
|
|
10
10
|
class ConsoleCommand extends FlutterPatcherCommand {
|
|
11
11
|
@override
|
|
12
12
|
String get name => 'console';
|
|
13
13
|
|
|
14
14
|
@override
|
|
15
|
-
String get description => 'Open the
|
|
15
|
+
String get description => 'Open the flutter_ota_kit web console.';
|
|
16
16
|
|
|
17
17
|
@override
|
|
18
18
|
ArgParser get argParser => ArgParser()
|
|
@@ -49,7 +49,7 @@ class ConsoleCommand extends FlutterPatcherCommand {
|
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
banner('console');
|
|
52
|
-
box('
|
|
52
|
+
box('flutter_ota_kit console', [
|
|
53
53
|
'Open the web console with Flutter:',
|
|
54
54
|
'',
|
|
55
55
|
kv('dir', consoleDir.path),
|
|
@@ -9,7 +9,7 @@ import '../operations.dart';
|
|
|
9
9
|
import '../ui/ui.dart';
|
|
10
10
|
import '../util.dart';
|
|
11
11
|
|
|
12
|
-
/// `
|
|
12
|
+
/// `flutter_ota_kit deploy` — zip + upload + register a new bundle.
|
|
13
13
|
class DeployCommand extends FlutterPatcherCommand {
|
|
14
14
|
DeployCommand({this.config, this.backendOverride});
|
|
15
15
|
|
|
@@ -31,14 +31,14 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
31
31
|
..addOption('channel', abbr: 'c', help: 'Target channel.')
|
|
32
32
|
..addOption('platform', abbr: 'p', defaultsTo: 'android', help: 'Platform.')
|
|
33
33
|
..addOption('message', abbr: 'm', help: 'Release message.')
|
|
34
|
-
..addFlag('force', help: 'Force the update on clients.')
|
|
34
|
+
..addFlag('force', abbr: 'f', help: 'Force the update on clients.')
|
|
35
35
|
..addOption('target-app-version',
|
|
36
36
|
help: 'Semver range target (XOR with fingerprint-hash).')
|
|
37
37
|
..addOption('fingerprint-hash',
|
|
38
38
|
help: 'Fingerprint hash target (XOR with target-app-version).')
|
|
39
|
-
..addOption('key', help: 'Path to Ed25519 private key file (sign bundle).')
|
|
39
|
+
..addOption('key', abbr: 'k', help: 'Path to Ed25519 private key file (sign bundle).')
|
|
40
40
|
..addOption('git-commit-hash', help: 'Git commit hash (auto-detected).')
|
|
41
|
-
..addOption('bundle-id', help: 'Explicit bundle id (uuidv7 by default).');
|
|
41
|
+
..addOption('bundle-id', abbr: 'i', help: 'Explicit bundle id (uuidv7 by default).');
|
|
42
42
|
|
|
43
43
|
@override
|
|
44
44
|
Future<int> run() => runGuarded(() async {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import 'dart:io';
|
|
2
2
|
|
|
3
3
|
import 'package:args/args.dart';
|
|
4
|
-
import 'package:
|
|
4
|
+
import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
|
|
5
5
|
|
|
6
6
|
import '../ui/ui.dart';
|
|
7
7
|
|
|
8
|
-
/// `
|
|
8
|
+
/// `flutter_ota_kit doctor` — environment + backend connectivity check.
|
|
9
9
|
class DoctorCommand extends FlutterPatcherCommand {
|
|
10
10
|
DoctorCommand({this.config, this.backendOverride});
|
|
11
11
|
|
|
@@ -36,7 +36,7 @@ class DoctorCommand extends FlutterPatcherCommand {
|
|
|
36
36
|
];
|
|
37
37
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
38
38
|
if (cfg == null) {
|
|
39
|
-
lines.add(kv('config', yellow('not found — run `
|
|
39
|
+
lines.add(kv('config', yellow('not found — run `flutter_ota_kit init`')));
|
|
40
40
|
box('doctor', lines);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
@@ -5,7 +5,7 @@ import 'package:args/args.dart';
|
|
|
5
5
|
import '../cli_base.dart';
|
|
6
6
|
import '../util.dart';
|
|
7
7
|
|
|
8
|
-
/// `
|
|
8
|
+
/// `flutter_ota_kit fingerprint` — compute a build-time fingerprint hash.
|
|
9
9
|
class FingerprintCommand extends FlutterPatcherCommand {
|
|
10
10
|
@override
|
|
11
11
|
String get name => 'fingerprint';
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import 'dart:io';
|
|
2
2
|
|
|
3
3
|
import 'package:args/args.dart';
|
|
4
|
-
import 'package:
|
|
4
|
+
import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
|
|
5
5
|
|
|
6
6
|
import '../ui/ui.dart';
|
|
7
7
|
|
|
8
|
-
/// `
|
|
8
|
+
/// `flutter_ota_kit init <backend>` — scaffold a Flutter project for OTA updates.
|
|
9
9
|
///
|
|
10
|
-
///
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
///
|
|
14
|
-
///
|
|
10
|
+
/// Beyond writing the `.otat/config.json` (so every other `flutter_ota_kit` command
|
|
11
|
+
/// knows the backend), it also drops the files a host app actually needs:
|
|
12
|
+
///
|
|
13
|
+
/// * adds the `flutter_ota_kit` pub dependency to `pubspec.yaml`,
|
|
14
|
+
/// * adds the `INTERNET` permission to the Android manifest (the auto-init
|
|
15
|
+
/// `ContentProvider` is merged in automatically by the plugin),
|
|
16
|
+
/// * generates `lib/flutter_ota_kit_setup.dart` that wires the selected backend
|
|
17
|
+
/// and enables zero-click forced updates.
|
|
18
|
+
///
|
|
19
|
+
/// All CLI working state is stored under `.otat/`.
|
|
15
20
|
class InitCommand extends FlutterPatcherCommand {
|
|
16
21
|
InitCommand({this.config, this.backendOverride});
|
|
17
22
|
|
|
@@ -23,16 +28,19 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
23
28
|
|
|
24
29
|
@override
|
|
25
30
|
String get description =>
|
|
26
|
-
'Scaffold
|
|
31
|
+
'Scaffold flutter_ota_kit integration files + .otat config for the current '
|
|
32
|
+
'Flutter project. Usage: flutter_ota_kit init <supabase|postgres|cloudflare|aws>';
|
|
27
33
|
|
|
28
34
|
@override
|
|
29
35
|
ArgParser get argParser => ArgParser()
|
|
30
36
|
..addOption('provider',
|
|
31
|
-
abbr: 'p',
|
|
32
|
-
|
|
37
|
+
abbr: 'p',
|
|
38
|
+
defaultsTo: 'supabase',
|
|
39
|
+
help: 'Backend provider (also accepted as the first positional argument).')
|
|
40
|
+
..addOption('channel', abbr: 'c', defaultsTo: 'production', help: 'Default channel.')
|
|
33
41
|
..addOption('platform', defaultsTo: 'android', help: 'Default platform.')
|
|
34
|
-
..addOption('source', defaultsTo: './dist', help: 'Default deploy source.')
|
|
35
|
-
..addFlag('global', help: 'Write to the global ~/.
|
|
42
|
+
..addOption('source', abbr: 's', defaultsTo: './dist', help: 'Default deploy source.')
|
|
43
|
+
..addFlag('global', help: 'Write to the global ~/.otat config (no scaffolding).')
|
|
36
44
|
..addFlag('force', abbr: 'f', help: 'Overwrite an existing config.');
|
|
37
45
|
|
|
38
46
|
String? _p(String label, String? current) {
|
|
@@ -44,9 +52,52 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
44
52
|
return trimmed.isEmpty ? current : trimmed;
|
|
45
53
|
}
|
|
46
54
|
|
|
55
|
+
/// Backends `init` can scaffold, in display order.
|
|
56
|
+
static const List<String> _providers = [
|
|
57
|
+
'supabase',
|
|
58
|
+
'postgres',
|
|
59
|
+
'cloudflare',
|
|
60
|
+
'aws',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/// Interactive backend picker. Returns the chosen provider, defaulting to
|
|
64
|
+
/// [requested] when the user just presses enter.
|
|
65
|
+
String _chooseProvider(String requested) {
|
|
66
|
+
stdout.writeln(cyan('Select a backend provider:'));
|
|
67
|
+
for (var i = 0; i < _providers.length; i++) {
|
|
68
|
+
final marker = _providers[i] == requested ? '*' : ' ';
|
|
69
|
+
stdout.writeln(' ${dim(marker)} ${i + 1}. ${_providers[i]}');
|
|
70
|
+
}
|
|
71
|
+
stdout.write('Provider ${dim('[1]')}: ');
|
|
72
|
+
final line = stdin.readLineSync()?.trim();
|
|
73
|
+
if (line == null || line.isEmpty) return requested;
|
|
74
|
+
final n = int.tryParse(line);
|
|
75
|
+
if (n != null && n >= 1 && n <= _providers.length) {
|
|
76
|
+
return _providers[n - 1];
|
|
77
|
+
}
|
|
78
|
+
return line;
|
|
79
|
+
}
|
|
80
|
+
|
|
47
81
|
@override
|
|
48
82
|
Future<int> run() => runGuarded(() async {
|
|
49
|
-
|
|
83
|
+
// A positional backend (`flutter_ota_kit init supabase`) takes precedence
|
|
84
|
+
// over the --provider flag.
|
|
85
|
+
final positional = argResults!.rest;
|
|
86
|
+
final requested = positional.isNotEmpty
|
|
87
|
+
? positional.first
|
|
88
|
+
: (argResults!['provider'] as String);
|
|
89
|
+
final provider =
|
|
90
|
+
stdin.hasTerminal ? _chooseProvider(requested) : requested;
|
|
91
|
+
if (!_providers.contains(provider)) {
|
|
92
|
+
throw PackException(
|
|
93
|
+
'Unknown provider "$provider". Choose one of: '
|
|
94
|
+
'${_providers.join(', ')}.',
|
|
95
|
+
64,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
final global = argResults!['global'] as bool;
|
|
100
|
+
final file = global
|
|
50
101
|
? configCandidates().last
|
|
51
102
|
: configCandidates().first;
|
|
52
103
|
if (file.existsSync() && !(argResults!['force'] as bool)) {
|
|
@@ -56,9 +107,10 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
56
107
|
);
|
|
57
108
|
}
|
|
58
109
|
|
|
59
|
-
final
|
|
60
|
-
|
|
61
|
-
final platform =
|
|
110
|
+
final channel =
|
|
111
|
+
argResults!['channel'] as String? ?? 'production';
|
|
112
|
+
final platform =
|
|
113
|
+
argResults!['platform'] as String? ?? 'android';
|
|
62
114
|
final source = argResults!['source'] as String? ?? './dist';
|
|
63
115
|
final env = Platform.environment;
|
|
64
116
|
|
|
@@ -76,13 +128,14 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
76
128
|
postgres: PostgresConfigJson(
|
|
77
129
|
host: _p('Postgres host', env['POSTGRES_HOST']),
|
|
78
130
|
port: _p('Postgres port', env['POSTGRES_PORT'] ?? '5432'),
|
|
79
|
-
database:
|
|
131
|
+
database:
|
|
132
|
+
_p('Postgres database', env['POSTGRES_DB'] ?? 'postgres'),
|
|
80
133
|
username: _p('Postgres username', env['POSTGRES_USER']),
|
|
81
134
|
password: _p('Postgres password', env['POSTGRES_PASSWORD']),
|
|
82
135
|
sslMode: _p('Postgres sslmode', env['POSTGRES_SSLMODE']),
|
|
83
136
|
basePath: _p('Storage base path', env['POSTGRES_BASE_PATH']),
|
|
84
|
-
servingBaseUrl:
|
|
85
|
-
|
|
137
|
+
servingBaseUrl: _p(
|
|
138
|
+
'Serving base url', env['POSTGRES_SERVING_BASE_URL']),
|
|
86
139
|
),
|
|
87
140
|
);
|
|
88
141
|
case 'cloudflare':
|
|
@@ -93,13 +146,15 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
93
146
|
source: source,
|
|
94
147
|
supabase: const SupabaseConfigJson(),
|
|
95
148
|
cloudflare: CloudflareConfigJson(
|
|
96
|
-
accountId:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
_p('
|
|
102
|
-
|
|
149
|
+
accountId:
|
|
150
|
+
_p('Cloudflare account id', env['CLOUDFLARE_ACCOUNT_ID']),
|
|
151
|
+
d1DatabaseId: _p(
|
|
152
|
+
'Cloudflare D1 database id', env['CLOUDFLARE_D1_DATABASE_ID']),
|
|
153
|
+
apiToken:
|
|
154
|
+
_p('Cloudflare API token', env['CLOUDFLARE_API_TOKEN']),
|
|
155
|
+
r2Bucket: _p('R2 bucket', env['R2_BUCKET'] ?? 'bundles'),
|
|
156
|
+
r2AccessKeyId:
|
|
157
|
+
_p('R2 access key id', env['R2_ACCESS_KEY_ID']),
|
|
103
158
|
r2SecretAccessKey:
|
|
104
159
|
_p('R2 secret access key', env['R2_SECRET_ACCESS_KEY']),
|
|
105
160
|
r2BasePath: _p('R2 base path', env['R2_BASE_PATH']),
|
|
@@ -115,23 +170,14 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
115
170
|
aws: AwsConfigJson(
|
|
116
171
|
bucket: _p('AWS bucket', env['AWS_BUCKET']),
|
|
117
172
|
region: _p('AWS region', env['AWS_REGION']),
|
|
118
|
-
accessKeyId:
|
|
173
|
+
accessKeyId:
|
|
174
|
+
_p('AWS access key id', env['AWS_ACCESS_KEY_ID']),
|
|
119
175
|
secretAccessKey:
|
|
120
176
|
_p('AWS secret access key', env['AWS_SECRET_ACCESS_KEY']),
|
|
121
177
|
endpoint: _p('AWS endpoint', env['AWS_ENDPOINT']),
|
|
122
178
|
basePath: _p('AWS base path', env['AWS_BASE_PATH']),
|
|
123
|
-
sessionToken:
|
|
124
|
-
|
|
125
|
-
);
|
|
126
|
-
case 'standalone':
|
|
127
|
-
cfg = FlutterPatcherConfig(
|
|
128
|
-
provider: provider,
|
|
129
|
-
channel: channel,
|
|
130
|
-
platform: platform,
|
|
131
|
-
source: source,
|
|
132
|
-
supabase: const SupabaseConfigJson(),
|
|
133
|
-
standalone: StandaloneConfigJson(
|
|
134
|
-
baseUrl: _p('Standalone base url', env['STANDALONE_BASE_URL']),
|
|
179
|
+
sessionToken:
|
|
180
|
+
_p('AWS session token', env['AWS_SESSION_TOKEN']),
|
|
135
181
|
),
|
|
136
182
|
);
|
|
137
183
|
case 'supabase':
|
|
@@ -143,20 +189,223 @@ class InitCommand extends FlutterPatcherCommand {
|
|
|
143
189
|
source: source,
|
|
144
190
|
supabase: SupabaseConfigJson(
|
|
145
191
|
url: _p('Supabase URL', env['SUPABASE_URL']),
|
|
146
|
-
serviceRoleKey:
|
|
147
|
-
|
|
192
|
+
serviceRoleKey: _p(
|
|
193
|
+
'Supabase service role key', env['SUPABASE_SERVICE_ROLE_KEY']),
|
|
148
194
|
anonKey: _p('Supabase anon key', env['SUPABASE_ANON_KEY']),
|
|
149
195
|
bucket: _p('Storage bucket', env['SUPABASE_BUCKET'] ?? 'bundles'),
|
|
150
196
|
basePath: _p('Storage base path', env['SUPABASE_BASE_PATH']),
|
|
197
|
+
managementKey: _p(
|
|
198
|
+
'Supabase Management API key (optional, used by `migrate`)',
|
|
199
|
+
env['SUPABASE_MANAGEMENT_KEY']),
|
|
200
|
+
databaseUrl: _p(
|
|
201
|
+
'Postgres DATABASE_URL (optional, used by `migrate`)',
|
|
202
|
+
env['SUPABASE_DATABASE_URL']),
|
|
151
203
|
),
|
|
152
204
|
);
|
|
153
205
|
}
|
|
154
206
|
|
|
155
|
-
saveConfig(cfg, global:
|
|
207
|
+
saveConfig(cfg, global: global);
|
|
156
208
|
step('Wrote ${file.path}');
|
|
209
|
+
|
|
210
|
+
if (global) {
|
|
211
|
+
stdout.writeln(
|
|
212
|
+
' ${yellow('⚠')} Secrets are stored in plaintext under ~/.otat. '
|
|
213
|
+
'Restrict permissions and use a secrets manager in production.',
|
|
214
|
+
);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Scaffold the host project integration files.
|
|
219
|
+
await _scaffoldProject(cfg, provider);
|
|
220
|
+
|
|
221
|
+
stdout.writeln();
|
|
222
|
+
stdout.writeln(cyan('Next steps:'));
|
|
223
|
+
stdout.writeln(' 1. ${dim('flutter pub get')}');
|
|
224
|
+
stdout.writeln(
|
|
225
|
+
' 2. In lib/main.dart call ${green('await setupFlutterOta();')} '
|
|
226
|
+
'right after WidgetsFlutterBinding.ensureInitialized() (before runApp).');
|
|
227
|
+
if (provider == 'supabase') {
|
|
228
|
+
stdout.writeln(
|
|
229
|
+
' 3. Provision the backend once: ${dim('flutter_ota_kit migrate supabase')}');
|
|
230
|
+
} else if (provider == 'postgres') {
|
|
231
|
+
stdout.writeln(
|
|
232
|
+
' 3. Provision the backend: ${dim('flutter_ota_kit migrate postgres')}');
|
|
233
|
+
}
|
|
157
234
|
stdout.writeln(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
);
|
|
235
|
+
' 4. Build a patch: ${dim('flutter_ota_kit build --name 1.0.1')} '
|
|
236
|
+
'then ${dim('flutter_ota_kit deploy')}');
|
|
161
237
|
});
|
|
238
|
+
|
|
239
|
+
/// Generate the integration files (pubspec dep, manifest permission, setup dart
|
|
240
|
+
/// file, .gitignore entry) inside the current directory.
|
|
241
|
+
Future<void> _scaffoldProject(FlutterPatcherConfig cfg, String provider) async {
|
|
242
|
+
_addPubDependency();
|
|
243
|
+
_addInternetPermission();
|
|
244
|
+
await _writeSetupFile(cfg, provider);
|
|
245
|
+
_ensureGitignore();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
void _addPubDependency() {
|
|
249
|
+
final pubspec = File('pubspec.yaml');
|
|
250
|
+
if (!pubspec.existsSync()) {
|
|
251
|
+
stdout.writeln(yellow(' ⚠ pubspec.yaml not found; skipping dependency injection.'));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
var content = pubspec.readAsStringSync();
|
|
255
|
+
if (content.contains(RegExp(r'^\s*flutter_ota_kit\s*:', multiLine: true))) {
|
|
256
|
+
step('pubspec.yaml already references flutter_ota_kit');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const dep = ' flutter_ota_kit: ^0.1.4\n';
|
|
260
|
+
final marker = '\ndependencies:';
|
|
261
|
+
final idx = content.indexOf(marker);
|
|
262
|
+
if (idx == -1) {
|
|
263
|
+
stdout.writeln(yellow(' ⚠ could not find a `dependencies:` block; add `flutter_ota_kit` manually.'));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
final insertAt = idx + marker.length;
|
|
267
|
+
content = content.replaceRange(insertAt, insertAt, '\n$dep');
|
|
268
|
+
pubspec.writeAsStringSync(content);
|
|
269
|
+
step('Added `flutter_ota_kit` dependency to pubspec.yaml');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
void _addInternetPermission() {
|
|
273
|
+
final mf = File('android/app/src/main/AndroidManifest.xml');
|
|
274
|
+
if (!mf.existsSync()) {
|
|
275
|
+
stdout.writeln(yellow(' ⚠ AndroidManifest.xml not found; skipping INTERNET permission.'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
var content = mf.readAsStringSync();
|
|
279
|
+
if (content.contains('android.permission.INTERNET')) {
|
|
280
|
+
step('INTERNET permission already present');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
final match = RegExp(r'<manifest[^>]*>').firstMatch(content);
|
|
284
|
+
if (match == null) {
|
|
285
|
+
stdout.writeln(yellow(' ⚠ could not locate <manifest> tag; add INTERNET permission manually.'));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
final insertAt = match.end;
|
|
289
|
+
content = content.replaceRange(
|
|
290
|
+
insertAt,
|
|
291
|
+
insertAt,
|
|
292
|
+
'\n <uses-permission android:name="android.permission.INTERNET" />',
|
|
293
|
+
);
|
|
294
|
+
mf.writeAsStringSync(content);
|
|
295
|
+
step('Added INTERNET permission to android/app/src/main/AndroidManifest.xml');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
Future<void> _writeSetupFile(FlutterPatcherConfig cfg, String provider) async {
|
|
299
|
+
final dir = Directory('lib');
|
|
300
|
+
if (!dir.existsSync()) dir.createSync(recursive: true);
|
|
301
|
+
final file = File('lib/flutter_ota_kit_setup.dart');
|
|
302
|
+
file.writeAsStringSync(_setupBody(cfg, provider));
|
|
303
|
+
step('Wrote lib/flutter_ota_kit_setup.dart');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
void _ensureGitignore() {
|
|
307
|
+
final gi = File('.gitignore');
|
|
308
|
+
final content = gi.existsSync() ? gi.readAsStringSync() : '';
|
|
309
|
+
if (content.contains('.otat/')) {
|
|
310
|
+
step('.gitignore already ignores .otat/');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
final next = content.isEmpty ? '.otat/\n' : '$content\n.otat/\n';
|
|
314
|
+
gi.writeAsStringSync(next);
|
|
315
|
+
step('Added .otat/ to .gitignore');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
String _q(String? s) => s == null ? "''" : "'${s.replaceAll("'", "\\'")}'";
|
|
319
|
+
|
|
320
|
+
/// Build the body of `lib/flutter_ota_kit_setup.dart` for the selected backend.
|
|
321
|
+
String _setupBody(FlutterPatcherConfig cfg, String provider) {
|
|
322
|
+
final channel = _q(cfg.channel);
|
|
323
|
+
final strategy = 'UpdateStrategy.appVersion';
|
|
324
|
+
final appVersion =
|
|
325
|
+
"const String.fromEnvironment('APP_VERSION', defaultValue: '1.0.0')";
|
|
326
|
+
|
|
327
|
+
late final String configure;
|
|
328
|
+
switch (provider) {
|
|
329
|
+
case 'postgres':
|
|
330
|
+
final c = cfg.postgres;
|
|
331
|
+
configure = '''
|
|
332
|
+
FlutterPatcher.configurePostgres(PostgresUpdateConfig(
|
|
333
|
+
host: ${_q(c.host)},
|
|
334
|
+
port: ${_q(c.port ?? '5432')},
|
|
335
|
+
database: ${_q(c.database)},
|
|
336
|
+
username: ${_q(c.username)},
|
|
337
|
+
password: ${_q(c.password)},
|
|
338
|
+
servingBaseUrl: ${_q(c.servingBaseUrl)},
|
|
339
|
+
channel: $channel,
|
|
340
|
+
platform: Platform.android,
|
|
341
|
+
updateStrategy: $strategy,
|
|
342
|
+
appVersion: $appVersion,
|
|
343
|
+
));''';
|
|
344
|
+
case 'cloudflare':
|
|
345
|
+
final c = cfg.cloudflare;
|
|
346
|
+
configure = '''
|
|
347
|
+
FlutterPatcher.configureCloudflare(CloudflareUpdateConfig(
|
|
348
|
+
databaseId: ${_q(c.d1DatabaseId)},
|
|
349
|
+
accountId: ${_q(c.accountId)},
|
|
350
|
+
cloudflareApiToken: ${_q(c.apiToken)},
|
|
351
|
+
bucketName: ${_q(c.r2Bucket ?? 'bundles')},
|
|
352
|
+
accessKeyId: ${_q(c.r2AccessKeyId)},
|
|
353
|
+
secretAccessKey: ${_q(c.r2SecretAccessKey)},
|
|
354
|
+
basePath: ${_q(c.r2BasePath)},
|
|
355
|
+
channel: $channel,
|
|
356
|
+
platform: Platform.android,
|
|
357
|
+
updateStrategy: $strategy,
|
|
358
|
+
appVersion: $appVersion,
|
|
359
|
+
));''';
|
|
360
|
+
case 'aws':
|
|
361
|
+
final c = cfg.aws;
|
|
362
|
+
configure = '''
|
|
363
|
+
FlutterPatcher.configureAws(AwsUpdateConfig(
|
|
364
|
+
bucketName: ${_q(c.bucket)},
|
|
365
|
+
region: ${_q(c.region)},
|
|
366
|
+
accessKeyId: ${_q(c.accessKeyId)},
|
|
367
|
+
secretAccessKey: ${_q(c.secretAccessKey)},
|
|
368
|
+
basePath: ${_q(c.basePath)},
|
|
369
|
+
endpoint: ${_q(c.endpoint)},
|
|
370
|
+
channel: $channel,
|
|
371
|
+
platform: Platform.android,
|
|
372
|
+
updateStrategy: $strategy,
|
|
373
|
+
appVersion: $appVersion,
|
|
374
|
+
));''';
|
|
375
|
+
case 'supabase':
|
|
376
|
+
default:
|
|
377
|
+
final c = cfg.supabase;
|
|
378
|
+
configure = '''
|
|
379
|
+
FlutterPatcher.configureSupabase(SupabaseUpdateConfig(
|
|
380
|
+
supabaseUrl: ${_q(c.url)},
|
|
381
|
+
anonKey: ${_q(c.anonKey)},
|
|
382
|
+
bucket: ${_q(c.bucket ?? 'bundles')},
|
|
383
|
+
channel: $channel,
|
|
384
|
+
platform: Platform.android,
|
|
385
|
+
updateStrategy: $strategy,
|
|
386
|
+
appVersion: $appVersion,
|
|
387
|
+
sdkVersion: '1.0.0',
|
|
388
|
+
));''';
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return '''
|
|
392
|
+
import 'dart:io' show Platform;
|
|
393
|
+
|
|
394
|
+
import 'package:flutter_ota_kit/flutter_ota_kit.dart';
|
|
395
|
+
|
|
396
|
+
// Generated by `flutter_ota_kit init $provider`.
|
|
397
|
+
//
|
|
398
|
+
// Secrets are hard-coded here for a zero-config quick start. For production,
|
|
399
|
+
// prefer reading them from --dart-define / environment variables instead.
|
|
400
|
+
//
|
|
401
|
+
// Call [setupFlutterOta] once in `main()`, after
|
|
402
|
+
// WidgetsFlutterBinding.ensureInitialized() and before runApp(). It wires the
|
|
403
|
+
// $provider update source and enables zero-click forced updates (the process
|
|
404
|
+
// restarts automatically when a forced bundle is available).
|
|
405
|
+
Future<void> setupFlutterOta() async {
|
|
406
|
+
$configure
|
|
407
|
+
await FlutterPatcher.init(autoApplyUpdates: true);
|
|
408
|
+
}
|
|
409
|
+
''';
|
|
410
|
+
}
|
|
162
411
|
}
|
|
@@ -8,7 +8,7 @@ import '../config.dart';
|
|
|
8
8
|
import '../sign.dart';
|
|
9
9
|
import '../ui/ui.dart';
|
|
10
10
|
|
|
11
|
-
/// `
|
|
11
|
+
/// `flutter_ota_kit keys` — generate an Ed25519 keypair for bundle signing.
|
|
12
12
|
class KeysCommand extends FlutterPatcherCommand {
|
|
13
13
|
KeysCommand({this.config, this.backendOverride});
|
|
14
14
|
|