@_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
|
@@ -2,14 +2,14 @@ import 'dart:convert';
|
|
|
2
2
|
import 'dart:io';
|
|
3
3
|
|
|
4
4
|
import 'package:args/args.dart';
|
|
5
|
-
import 'package:
|
|
5
|
+
import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
|
|
6
6
|
import 'package:http/http.dart' as http;
|
|
7
7
|
import 'package:path/path.dart' as p;
|
|
8
8
|
import 'package:postgres/postgres.dart';
|
|
9
9
|
|
|
10
10
|
import '../ui/ui.dart';
|
|
11
11
|
|
|
12
|
-
/// `
|
|
12
|
+
/// `flutter_ota_kit migrate` — run backend SQL migrations.
|
|
13
13
|
class MigrateCommand extends FlutterPatcherCommand {
|
|
14
14
|
@override
|
|
15
15
|
String get name => 'migrate';
|
|
@@ -28,13 +28,17 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
28
28
|
'runs migrations without a separate Postgres connection.')
|
|
29
29
|
..addOption('migrations-dir',
|
|
30
30
|
help: 'Directory of *.sql migration files (ordered by name).')
|
|
31
|
-
..addFlag('dry-run', help: 'Print migrations instead of applying them.');
|
|
31
|
+
..addFlag('dry-run', abbr: 'd', help: 'Print migrations instead of applying them.');
|
|
32
32
|
|
|
33
|
-
/// Default migrations dir lives next to the CLI entrypoint;
|
|
34
|
-
///
|
|
33
|
+
/// Default migrations dir lives next to the CLI entrypoint; each backend has
|
|
34
|
+
/// its own dialect's migrations (cloudflare = D1 SQLite, postgres = plain
|
|
35
|
+
/// Postgres, supabase = PostgREST, aws = no relational SQL).
|
|
35
36
|
String defaultMigrationsDir(String provider) {
|
|
36
37
|
final sub = switch (provider) {
|
|
38
|
+
'supabase' => 'supabase',
|
|
39
|
+
'postgres' => 'postgres',
|
|
37
40
|
'cloudflare' => 'cloudflare',
|
|
41
|
+
'aws' => 'aws',
|
|
38
42
|
_ => 'supabase',
|
|
39
43
|
};
|
|
40
44
|
return p.join(p.dirname(Platform.script.path), '..', 'migrations', sub);
|
|
@@ -68,6 +72,23 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
68
72
|
@override
|
|
69
73
|
Future<int> run() => runGuarded(() async {
|
|
70
74
|
final provider = argResults!['backend'] as String? ?? 'supabase';
|
|
75
|
+
|
|
76
|
+
// Backends with no relational SQL handled out-of-band (S3 blob store).
|
|
77
|
+
// Short-circuit before any SQL discovery so a missing or empty
|
|
78
|
+
// (README-only) migrations dir does not abort.
|
|
79
|
+
if (provider == 'aws') {
|
|
80
|
+
final dir = argResults!['migrations-dir'] as String? ??
|
|
81
|
+
defaultMigrationsDir(provider);
|
|
82
|
+
banner('migrate');
|
|
83
|
+
box('migrate', [
|
|
84
|
+
'Migrations for "$provider" do not run through this command:',
|
|
85
|
+
' • aws → S3 blob store, no relational SQL (see $dir/README.md)',
|
|
86
|
+
'',
|
|
87
|
+
'See the README under $dir for the intended setup.',
|
|
88
|
+
]);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
final dir = argResults!['migrations-dir'] as String? ??
|
|
72
93
|
defaultMigrationsDir(provider);
|
|
73
94
|
final files = _listMigrations(dir);
|
|
@@ -81,23 +102,30 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
81
102
|
return;
|
|
82
103
|
}
|
|
83
104
|
|
|
84
|
-
if (provider == 'cloudflare'
|
|
105
|
+
if (provider == 'cloudflare') {
|
|
85
106
|
banner('migrate');
|
|
86
107
|
box('migrate', [
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
108
|
+
'Cloudflare (D1 SQLite) migrations run via `wrangler d1 execute`:',
|
|
109
|
+
' wrangler d1 execute <DB> --file=$dir/0001_hot-updater_init.sql',
|
|
110
|
+
' (apply each *.sql under $dir in filename order)',
|
|
90
111
|
'',
|
|
91
|
-
'The SQL files under $dir show the intended schema.',
|
|
112
|
+
'The SQL files under $dir show the intended D1 schema.',
|
|
92
113
|
]);
|
|
93
114
|
return;
|
|
94
115
|
}
|
|
95
116
|
|
|
96
117
|
if (provider == 'supabase') {
|
|
118
|
+
final cfg = loadConfig() ??
|
|
119
|
+
FlutterPatcherConfig(
|
|
120
|
+
provider: 'supabase',
|
|
121
|
+
supabase: const SupabaseConfigJson(),
|
|
122
|
+
);
|
|
97
123
|
final mgmtKey = argResults!['management-key'] as String? ??
|
|
98
|
-
Platform.environment['SUPABASE_MANAGEMENT_KEY']
|
|
124
|
+
Platform.environment['SUPABASE_MANAGEMENT_KEY'] ??
|
|
125
|
+
cfg.supabase.managementKey;
|
|
99
126
|
final pgUrl = argResults!['database-url'] as String? ??
|
|
100
|
-
Platform.environment['DATABASE_URL']
|
|
127
|
+
Platform.environment['DATABASE_URL'] ??
|
|
128
|
+
cfg.supabase.databaseUrl;
|
|
101
129
|
if (mgmtKey != null && mgmtKey.isNotEmpty) {
|
|
102
130
|
await _runViaManagementApi(mgmtKey, files);
|
|
103
131
|
return;
|
|
@@ -108,19 +136,26 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
108
136
|
}
|
|
109
137
|
throw StateError(
|
|
110
138
|
'For the supabase backend, provide either:\n'
|
|
111
|
-
' • --management-key (or SUPABASE_MANAGEMENT_KEY
|
|
112
|
-
'Management API key, OR\n'
|
|
113
|
-
' • --database-url (or DATABASE_URL) — a
|
|
139
|
+
' • --management-key (or SUPABASE_MANAGEMENT_KEY, or set it via '
|
|
140
|
+
'`flutter-ota init`) — a Supabase Management API key, OR\n'
|
|
141
|
+
' • --database-url (or DATABASE_URL, or set it via `init`) — a '
|
|
142
|
+
'Postgres connection string.',
|
|
114
143
|
);
|
|
115
144
|
}
|
|
116
145
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
if (provider == 'postgres') {
|
|
147
|
+
final pgUrl = argResults!['database-url'] as String? ??
|
|
148
|
+
Platform.environment['DATABASE_URL'];
|
|
149
|
+
if (pgUrl == null || pgUrl.isEmpty) {
|
|
150
|
+
throw StateError(
|
|
151
|
+
'For the postgres backend, provide --database-url or set DATABASE_URL.',
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
await _runViaPostgres(pgUrl, files);
|
|
155
|
+
return;
|
|
122
156
|
}
|
|
123
|
-
|
|
157
|
+
|
|
158
|
+
throw StateError('Unknown backend provider: "$provider".');
|
|
124
159
|
});
|
|
125
160
|
|
|
126
161
|
/// Run migrations through the Supabase Management API (no Postgres creds
|
|
@@ -220,7 +255,7 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
220
255
|
);
|
|
221
256
|
try {
|
|
222
257
|
await conn.execute(
|
|
223
|
-
'CREATE TABLE IF NOT EXISTS
|
|
258
|
+
'CREATE TABLE IF NOT EXISTS _flutter_ota_kit_migrations '
|
|
224
259
|
'(name text primary key, applied_at timestamptz default now())',
|
|
225
260
|
queryMode: QueryMode.simple,
|
|
226
261
|
);
|
|
@@ -228,7 +263,7 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
228
263
|
final name = p.basename(file.path);
|
|
229
264
|
final escaped = name.replaceAll("'", "''");
|
|
230
265
|
final existing = await conn.execute(
|
|
231
|
-
"SELECT 1 FROM
|
|
266
|
+
"SELECT 1 FROM _flutter_ota_kit_migrations WHERE name = '$escaped'",
|
|
232
267
|
queryMode: QueryMode.simple,
|
|
233
268
|
);
|
|
234
269
|
if (existing.isNotEmpty) {
|
|
@@ -239,7 +274,7 @@ class MigrateCommand extends FlutterPatcherCommand {
|
|
|
239
274
|
await conn.execute(stmt, queryMode: QueryMode.simple);
|
|
240
275
|
}
|
|
241
276
|
await conn.execute(
|
|
242
|
-
"INSERT INTO
|
|
277
|
+
"INSERT INTO _flutter_ota_kit_migrations(name) VALUES ('$escaped')",
|
|
243
278
|
queryMode: QueryMode.simple,
|
|
244
279
|
);
|
|
245
280
|
step('applied $name');
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import 'package:args/args.dart';
|
|
2
|
-
import 'package:
|
|
3
|
-
import 'package:
|
|
2
|
+
import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
|
|
3
|
+
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
|
|
4
4
|
|
|
5
5
|
import '../ui/ui.dart';
|
|
6
6
|
|
|
7
|
-
/// `
|
|
7
|
+
/// `flutter_ota_kit rollback` — roll a channel back to a previous bundle.
|
|
8
8
|
class RollbackCommand extends FlutterPatcherCommand {
|
|
9
9
|
RollbackCommand({this.config, this.backendOverride});
|
|
10
10
|
|
|
@@ -22,7 +22,7 @@ class RollbackCommand extends FlutterPatcherCommand {
|
|
|
22
22
|
ArgParser get argParser => ArgParser()
|
|
23
23
|
..addOption('backend', abbr: 'b', help: 'Backend provider.')
|
|
24
24
|
..addOption('channel', abbr: 'c', help: 'Channel to roll back.')
|
|
25
|
-
..addOption('bundle-id', help: 'Roll back to this specific bundle id.')
|
|
25
|
+
..addOption('bundle-id', abbr: 'i', help: 'Roll back to this specific bundle id.')
|
|
26
26
|
..addOption('platform', abbr: 'p', help: 'Platform filter (e.g. android).');
|
|
27
27
|
|
|
28
28
|
@override
|
|
@@ -30,7 +30,7 @@ class RollbackCommand extends FlutterPatcherCommand {
|
|
|
30
30
|
final channel = argResults!['channel'] as String?;
|
|
31
31
|
if (channel == null || channel.isEmpty) {
|
|
32
32
|
throw PackException(
|
|
33
|
-
'Usage:
|
|
33
|
+
'Usage: flutter_ota_kit rollback --channel <channel> [--bundle-id <id>]',
|
|
34
34
|
64,
|
|
35
35
|
);
|
|
36
36
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import 'package:args/args.dart';
|
|
2
|
+
import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
|
|
3
|
+
|
|
4
|
+
import '../ui/ui.dart';
|
|
5
|
+
|
|
6
|
+
/// `flutter_ota_kit storage` — inspect and clean the blob storage bucket
|
|
7
|
+
/// directly from the CLI (no need to open the provider dashboard).
|
|
8
|
+
class StorageCommand extends FlutterPatcherCommand {
|
|
9
|
+
StorageCommand({this.config, this.backendOverride}) {
|
|
10
|
+
addSubcommand(StorageListCommand(config: config, backendOverride: backendOverride));
|
|
11
|
+
addSubcommand(StorageDeleteCommand(config: config, backendOverride: backendOverride));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
final FlutterPatcherConfig? config;
|
|
15
|
+
final Backend? backendOverride;
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
String get name => 'storage';
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
String get description =>
|
|
22
|
+
'Inspect and manage bundle storage objects (find / delete orphans).';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class StorageListCommand extends FlutterPatcherCommand {
|
|
26
|
+
StorageListCommand({this.config, this.backendOverride});
|
|
27
|
+
|
|
28
|
+
final FlutterPatcherConfig? config;
|
|
29
|
+
final Backend? backendOverride;
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
String get name => 'list';
|
|
33
|
+
|
|
34
|
+
@override
|
|
35
|
+
String get description => 'List storage objects.';
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
ArgParser get argParser => ArgParser()
|
|
39
|
+
..addOption('backend', abbr: 'b', help: 'Backend provider.')
|
|
40
|
+
..addOption('prefix', help: 'Key prefix filter (e.g. bundles).');
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
Future<int> run() => runGuarded(() async {
|
|
44
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
45
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
46
|
+
final prefix = argResults!['prefix'] as String?;
|
|
47
|
+
banner('storage · list');
|
|
48
|
+
final objects = await backend.storage.listObjects(
|
|
49
|
+
prefix == null || prefix.isEmpty ? null : prefix,
|
|
50
|
+
);
|
|
51
|
+
if (objects.isEmpty) {
|
|
52
|
+
step('(no objects)');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
final lines = <String>[];
|
|
56
|
+
for (final o in objects) {
|
|
57
|
+
final size = o.size >= 1024 * 1024
|
|
58
|
+
? '${(o.size / (1024 * 1024)).toStringAsFixed(2)} MB'
|
|
59
|
+
: o.size >= 1024
|
|
60
|
+
? '${(o.size / 1024).toStringAsFixed(1)} KB'
|
|
61
|
+
: '${o.size} B';
|
|
62
|
+
lines
|
|
63
|
+
..add(kv('key', cyan(o.key)))
|
|
64
|
+
..add(kv('size', size))
|
|
65
|
+
..add('');
|
|
66
|
+
}
|
|
67
|
+
box('${objects.length} objects', lines);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class StorageDeleteCommand extends FlutterPatcherCommand {
|
|
72
|
+
StorageDeleteCommand({this.config, this.backendOverride});
|
|
73
|
+
|
|
74
|
+
final FlutterPatcherConfig? config;
|
|
75
|
+
final Backend? backendOverride;
|
|
76
|
+
|
|
77
|
+
@override
|
|
78
|
+
String get name => 'delete';
|
|
79
|
+
|
|
80
|
+
@override
|
|
81
|
+
String get description => 'Delete storage object(s) by key or full URI.';
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
ArgParser get argParser => ArgParser()
|
|
85
|
+
..addOption('backend', abbr: 'b', help: 'Backend provider.')
|
|
86
|
+
..addMultiOption('key', help: 'Storage key to delete (repeatable).')
|
|
87
|
+
..addOption('uri', help: 'Full storage URI to delete.');
|
|
88
|
+
|
|
89
|
+
@override
|
|
90
|
+
Future<int> run() => runGuarded(() async {
|
|
91
|
+
final keys = (argResults!['key'] as List<String>?) ?? <String>[];
|
|
92
|
+
final uri = argResults!['uri'] as String?;
|
|
93
|
+
if (keys.isEmpty && (uri == null || uri.isEmpty)) {
|
|
94
|
+
throw PackException(
|
|
95
|
+
'Usage: flutter-ota storage delete --key <key> [--key <key>...] '
|
|
96
|
+
'| --uri <storageUri>',
|
|
97
|
+
64,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
101
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
102
|
+
banner('storage · delete');
|
|
103
|
+
if (keys.isNotEmpty) {
|
|
104
|
+
await spinner(
|
|
105
|
+
() => backend.storage.deleteObjects(keys),
|
|
106
|
+
'Deleting ${keys.length} object(s)',
|
|
107
|
+
done: 'Deleted',
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
await spinner(
|
|
111
|
+
() => backend.storage.delete(uri!),
|
|
112
|
+
'Deleting $uri',
|
|
113
|
+
done: 'Deleted',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import 'dart:convert';
|
|
2
2
|
import 'dart:io';
|
|
3
3
|
|
|
4
|
-
import 'package:
|
|
4
|
+
import 'package:flutter_ota_kit_aws/flutter_ota_kit_aws.dart';
|
|
5
5
|
import 'package:postgres/postgres.dart';
|
|
6
|
-
import 'package:
|
|
7
|
-
import 'package:
|
|
8
|
-
|
|
9
|
-
import 'package:
|
|
6
|
+
import 'package:flutter_ota_kit_cloudflare/flutter_ota_kit_cloudflare.dart';
|
|
7
|
+
import 'package:flutter_ota_kit_postgres/flutter_ota_kit_postgres.dart';
|
|
8
|
+
|
|
9
|
+
import 'package:flutter_ota_kit_supabase/flutter_ota_kit_supabase.dart';
|
|
10
10
|
import 'package:path/path.dart' as p;
|
|
11
11
|
|
|
12
|
-
/// Resolved configuration for the
|
|
12
|
+
/// Resolved configuration for the flutter_ota_kit CLI.
|
|
13
13
|
///
|
|
14
14
|
/// Mirrors hot-updater's config precedence: explicit flags > environment
|
|
15
|
-
/// variables > the project `.
|
|
16
|
-
/// `~/.
|
|
15
|
+
/// variables > the project `.otat/config.json` (falling back to the global
|
|
16
|
+
/// `~/.otat/config.json`). All CLI working state lives under `.otat/` so it can
|
|
17
|
+
/// be git-ignored as a single unit.
|
|
17
18
|
class FlutterPatcherConfig {
|
|
18
19
|
FlutterPatcherConfig({
|
|
19
20
|
required this.provider,
|
|
@@ -21,7 +22,6 @@ class FlutterPatcherConfig {
|
|
|
21
22
|
this.postgres = const PostgresConfigJson(),
|
|
22
23
|
this.cloudflare = const CloudflareConfigJson(),
|
|
23
24
|
this.aws = const AwsConfigJson(),
|
|
24
|
-
this.standalone = const StandaloneConfigJson(),
|
|
25
25
|
this.channel = 'production',
|
|
26
26
|
this.platform = 'android',
|
|
27
27
|
this.source = './dist',
|
|
@@ -43,9 +43,6 @@ class FlutterPatcherConfig {
|
|
|
43
43
|
aws: AwsConfigJson.fromJson(
|
|
44
44
|
(json['aws'] as Map?)?.cast<String, dynamic>() ?? {},
|
|
45
45
|
),
|
|
46
|
-
standalone: StandaloneConfigJson.fromJson(
|
|
47
|
-
(json['standalone'] as Map?)?.cast<String, dynamic>() ?? {},
|
|
48
|
-
),
|
|
49
46
|
channel: json['channel'] as String? ?? 'production',
|
|
50
47
|
platform: json['platform'] as String? ?? 'android',
|
|
51
48
|
source: json['source'] as String? ?? './dist',
|
|
@@ -57,7 +54,6 @@ class FlutterPatcherConfig {
|
|
|
57
54
|
final PostgresConfigJson postgres;
|
|
58
55
|
final CloudflareConfigJson cloudflare;
|
|
59
56
|
final AwsConfigJson aws;
|
|
60
|
-
final StandaloneConfigJson standalone;
|
|
61
57
|
final String channel;
|
|
62
58
|
final String platform;
|
|
63
59
|
final String source;
|
|
@@ -69,7 +65,6 @@ class FlutterPatcherConfig {
|
|
|
69
65
|
'postgres': postgres.toJson(),
|
|
70
66
|
'cloudflare': cloudflare.toJson(),
|
|
71
67
|
'aws': aws.toJson(),
|
|
72
|
-
'standalone': standalone.toJson(),
|
|
73
68
|
'channel': channel,
|
|
74
69
|
'platform': platform,
|
|
75
70
|
'source': source,
|
|
@@ -82,7 +77,6 @@ class FlutterPatcherConfig {
|
|
|
82
77
|
PostgresConfigJson? postgres,
|
|
83
78
|
CloudflareConfigJson? cloudflare,
|
|
84
79
|
AwsConfigJson? aws,
|
|
85
|
-
StandaloneConfigJson? standalone,
|
|
86
80
|
String? channel,
|
|
87
81
|
String? platform,
|
|
88
82
|
String? source,
|
|
@@ -94,7 +88,6 @@ class FlutterPatcherConfig {
|
|
|
94
88
|
postgres: postgres ?? this.postgres,
|
|
95
89
|
cloudflare: cloudflare ?? this.cloudflare,
|
|
96
90
|
aws: aws ?? this.aws,
|
|
97
|
-
standalone: standalone ?? this.standalone,
|
|
98
91
|
channel: channel ?? this.channel,
|
|
99
92
|
platform: platform ?? this.platform,
|
|
100
93
|
source: source ?? this.source,
|
|
@@ -110,6 +103,8 @@ class SupabaseConfigJson {
|
|
|
110
103
|
this.anonKey,
|
|
111
104
|
this.bucket,
|
|
112
105
|
this.basePath,
|
|
106
|
+
this.managementKey,
|
|
107
|
+
this.databaseUrl,
|
|
113
108
|
});
|
|
114
109
|
|
|
115
110
|
factory SupabaseConfigJson.fromJson(Map<String, dynamic> json) =>
|
|
@@ -119,6 +114,8 @@ class SupabaseConfigJson {
|
|
|
119
114
|
anonKey: json['anonKey'] as String?,
|
|
120
115
|
bucket: json['bucket'] as String?,
|
|
121
116
|
basePath: json['basePath'] as String?,
|
|
117
|
+
managementKey: json['managementKey'] as String?,
|
|
118
|
+
databaseUrl: json['databaseUrl'] as String?,
|
|
122
119
|
);
|
|
123
120
|
|
|
124
121
|
final String? url;
|
|
@@ -126,6 +123,8 @@ class SupabaseConfigJson {
|
|
|
126
123
|
final String? anonKey;
|
|
127
124
|
final String? bucket;
|
|
128
125
|
final String? basePath;
|
|
126
|
+
final String? managementKey;
|
|
127
|
+
final String? databaseUrl;
|
|
129
128
|
|
|
130
129
|
Map<String, dynamic> toJson() => {
|
|
131
130
|
if (url != null) 'url': url,
|
|
@@ -133,16 +132,20 @@ class SupabaseConfigJson {
|
|
|
133
132
|
if (anonKey != null) 'anonKey': anonKey,
|
|
134
133
|
if (bucket != null) 'bucket': bucket,
|
|
135
134
|
if (basePath != null) 'basePath': basePath,
|
|
135
|
+
if (managementKey != null) 'managementKey': managementKey,
|
|
136
|
+
if (databaseUrl != null) 'databaseUrl': databaseUrl,
|
|
136
137
|
};
|
|
137
138
|
}
|
|
138
139
|
|
|
139
|
-
/// Search order for a config file on disk.
|
|
140
|
+
/// Search order for a config file on disk. Both the project config and the
|
|
141
|
+
/// global config live under a `.otat/` directory so all CLI state is grouped
|
|
142
|
+
/// in one place (and can be git-ignored together).
|
|
140
143
|
List<File> configCandidates() {
|
|
141
|
-
final cwd = File(p.join(Directory.current.path, '.
|
|
144
|
+
final cwd = File(p.join(Directory.current.path, '.otat', 'config.json'));
|
|
142
145
|
final home = Platform.environment['HOME'] ??
|
|
143
146
|
Platform.environment['USERPROFILE'] ??
|
|
144
147
|
'';
|
|
145
|
-
final global = File(p.join(home, '.
|
|
148
|
+
final global = File(p.join(home, '.otat', 'config.json'));
|
|
146
149
|
return [cwd, global];
|
|
147
150
|
}
|
|
148
151
|
|
|
@@ -162,7 +165,7 @@ FlutterPatcherConfig? loadConfig() {
|
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
/// Persist a config. By default writes the project file (cwd); pass
|
|
165
|
-
/// [global: true] to write the global `~/.
|
|
168
|
+
/// [global: true] to write the global `~/.flutter_ota_kit/config.json`.
|
|
166
169
|
void saveConfig(FlutterPatcherConfig config, {bool global = false}) {
|
|
167
170
|
final file = global ? configCandidates().last : configCandidates().first;
|
|
168
171
|
file.parent.createSync(recursive: true);
|
|
@@ -228,7 +231,7 @@ SupabaseServiceRoleConfig resolveSupabaseConfig(
|
|
|
228
231
|
if (resolvedUrl == null || resolvedUrl.isEmpty) {
|
|
229
232
|
throw StateError(
|
|
230
233
|
'Supabase URL is required. Set it via --url, SUPABASE_URL, or '
|
|
231
|
-
'`
|
|
234
|
+
'`flutter_ota_kit config set supabase.url <url>`.',
|
|
232
235
|
);
|
|
233
236
|
}
|
|
234
237
|
|
|
@@ -368,7 +371,7 @@ PostgresConfig resolvePostgresDatabaseConfig(
|
|
|
368
371
|
if (resolvedHost == null || resolvedHost.isEmpty) {
|
|
369
372
|
throw StateError(
|
|
370
373
|
'Postgres host is required. Set --pg-host, POSTGRES_HOST, or '
|
|
371
|
-
'`
|
|
374
|
+
'`flutter_ota_kit config set postgres.host <host>`.',
|
|
372
375
|
);
|
|
373
376
|
}
|
|
374
377
|
|
|
@@ -485,7 +488,7 @@ D1DatabaseConfig resolveCloudflareDatabaseConfig(
|
|
|
485
488
|
resolvedToken == null) {
|
|
486
489
|
throw StateError(
|
|
487
490
|
'Cloudflare requires accountId, d1DatabaseId and apiToken. Set them via '
|
|
488
|
-
'flags, CLOUDFLARE_* env vars, or `
|
|
491
|
+
'flags, CLOUDFLARE_* env vars, or `flutter_ota_kit config set cloudflare.*`.',
|
|
489
492
|
);
|
|
490
493
|
}
|
|
491
494
|
|
|
@@ -526,7 +529,7 @@ R2S3StorageConfig resolveCloudflareStorageConfig(
|
|
|
526
529
|
resolvedSecret == null) {
|
|
527
530
|
throw StateError(
|
|
528
531
|
'Cloudflare R2 requires accountId, accessKeyId and secretAccessKey. Set '
|
|
529
|
-
'them via flags, R2_* env vars, or `
|
|
532
|
+
'them via flags, R2_* env vars, or `flutter_ota_kit config set cloudflare.*`.',
|
|
530
533
|
);
|
|
531
534
|
}
|
|
532
535
|
|
|
@@ -612,7 +615,7 @@ S3DatabaseConfig resolveAwsDatabaseConfig(
|
|
|
612
615
|
resolvedSecret == null) {
|
|
613
616
|
throw StateError(
|
|
614
617
|
'AWS requires bucket, region, accessKeyId and secretAccessKey. Set them '
|
|
615
|
-
'via flags, AWS_* env vars, or `
|
|
618
|
+
'via flags, AWS_* env vars, or `flutter_ota_kit config set aws.*`.',
|
|
616
619
|
);
|
|
617
620
|
}
|
|
618
621
|
|
|
@@ -656,7 +659,7 @@ AwsS3StorageConfig resolveAwsStorageConfig(
|
|
|
656
659
|
resolvedSecret == null) {
|
|
657
660
|
throw StateError(
|
|
658
661
|
'AWS requires bucket, region, accessKeyId and secretAccessKey. Set them '
|
|
659
|
-
'via flags, AWS_* env vars, or `
|
|
662
|
+
'via flags, AWS_* env vars, or `flutter_ota_kit config set aws.*`.',
|
|
660
663
|
);
|
|
661
664
|
}
|
|
662
665
|
|
|
@@ -671,55 +674,3 @@ AwsS3StorageConfig resolveAwsStorageConfig(
|
|
|
671
674
|
clientFactory: clientFactory ?? defaultAwsS3ClientFactory,
|
|
672
675
|
);
|
|
673
676
|
}
|
|
674
|
-
|
|
675
|
-
/// Standalone (self-hosted) section of [FlutterPatcherConfig].
|
|
676
|
-
class StandaloneConfigJson {
|
|
677
|
-
const StandaloneConfigJson({this.baseUrl});
|
|
678
|
-
|
|
679
|
-
factory StandaloneConfigJson.fromJson(Map<String, dynamic> json) =>
|
|
680
|
-
StandaloneConfigJson(baseUrl: json['baseUrl'] as String?);
|
|
681
|
-
|
|
682
|
-
final String? baseUrl;
|
|
683
|
-
|
|
684
|
-
Map<String, dynamic> toJson() =>
|
|
685
|
-
{if (baseUrl != null) 'baseUrl': baseUrl};
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/// Resolve the standalone repository (database) config.
|
|
689
|
-
StandaloneRepositoryConfig resolveStandaloneDatabaseConfig(
|
|
690
|
-
FlutterPatcherConfig config, {
|
|
691
|
-
String? baseUrl,
|
|
692
|
-
StandaloneClientFactory? clientFactory,
|
|
693
|
-
}) {
|
|
694
|
-
final env = Platform.environment;
|
|
695
|
-
final resolvedBaseUrl =
|
|
696
|
-
baseUrl ?? env['STANDALONE_BASE_URL'] ?? config.standalone.baseUrl;
|
|
697
|
-
if (resolvedBaseUrl == null || resolvedBaseUrl.isEmpty) {
|
|
698
|
-
throw StateError(
|
|
699
|
-
'Standalone baseUrl is required. Set --base-url, STANDALONE_BASE_URL, '
|
|
700
|
-
'or `flutter_patcher config set standalone.baseUrl <url>`.',
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
return StandaloneRepositoryConfig(
|
|
704
|
-
baseUrl: resolvedBaseUrl,
|
|
705
|
-
clientFactory: clientFactory,
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/// Resolve the standalone storage config (shares the same self-hosted base).
|
|
710
|
-
StandaloneStorageConfig resolveStandaloneStorageConfig(
|
|
711
|
-
FlutterPatcherConfig config, {
|
|
712
|
-
String? baseUrl,
|
|
713
|
-
StandaloneClientFactory? clientFactory,
|
|
714
|
-
}) {
|
|
715
|
-
final db = resolveStandaloneDatabaseConfig(
|
|
716
|
-
config,
|
|
717
|
-
baseUrl: baseUrl,
|
|
718
|
-
clientFactory: clientFactory,
|
|
719
|
-
);
|
|
720
|
-
return StandaloneStorageConfig(
|
|
721
|
-
baseUrl: db.baseUrl,
|
|
722
|
-
commonHeaders: db.commonHeaders,
|
|
723
|
-
clientFactory: clientFactory,
|
|
724
|
-
);
|
|
725
|
-
}
|
|
@@ -2,8 +2,8 @@ import 'dart:convert';
|
|
|
2
2
|
import 'dart:io';
|
|
3
3
|
|
|
4
4
|
import 'package:crypto/crypto.dart';
|
|
5
|
-
import 'package:
|
|
6
|
-
import 'package:
|
|
5
|
+
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
|
|
6
|
+
import 'package:flutter_ota_kit_plugin_core/flutter_ota_kit_plugin_core.dart';
|
|
7
7
|
|
|
8
8
|
import 'backend.dart';
|
|
9
9
|
import 'sign.dart';
|
|
@@ -61,7 +61,7 @@ Future<Bundle> deployBundle(Backend backend, DeployOptions opts) async {
|
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
// Resolve the artifact to upload. If the source is already a zip (e.g. the
|
|
64
|
-
// `patch.zip` produced by `
|
|
64
|
+
// `patch.zip` produced by `flutter_ota_kit build`/`pack`), upload it directly.
|
|
65
65
|
// Otherwise, if the source directory contains a `patch.zip`, use that. As a
|
|
66
66
|
// fallback, zip the source directory (for pre-built bundle layouts).
|
|
67
67
|
final sourceFile = File(opts.source);
|
|
@@ -93,7 +93,7 @@ Future<Bundle> deployBundle(Backend backend, DeployOptions opts) async {
|
|
|
93
93
|
: null;
|
|
94
94
|
|
|
95
95
|
final bundleId = opts.bundleId ?? uuidV7();
|
|
96
|
-
final key =
|
|
96
|
+
final key = bundleId;
|
|
97
97
|
final uploaded = await backend.storage.upload(key, zipPath);
|
|
98
98
|
final storageUri = uploaded['storageUri'];
|
|
99
99
|
if (storageUri == null || storageUri.isEmpty) {
|
|
@@ -33,7 +33,7 @@ class PackResult {
|
|
|
33
33
|
/// Pack a release APK into a device-compatible OTA patch (`patch.zip`).
|
|
34
34
|
///
|
|
35
35
|
/// Mirrors the standalone `bin/pack.dart` CLI, but as a library usable by the
|
|
36
|
-
/// `
|
|
36
|
+
/// `flutter_ota_kit build` command. Includes **every** ABI found in the APK so
|
|
37
37
|
/// a single bundle serves all device ABIs (the device SDK picks its ABI).
|
|
38
38
|
Future<PackResult> packPatch({
|
|
39
39
|
required String apkPath,
|
|
@@ -73,6 +73,7 @@ Future<PackResult> packPatch({
|
|
|
73
73
|
version: version,
|
|
74
74
|
targetVersionCode: targetVersionCode,
|
|
75
75
|
requestedAssets: requestedAssets,
|
|
76
|
+
abi: abi,
|
|
76
77
|
onProgress: onProgress,
|
|
77
78
|
);
|
|
78
79
|
return result;
|
|
@@ -85,8 +86,13 @@ PackResult _writePatchPackage({
|
|
|
85
86
|
required String version,
|
|
86
87
|
required int targetVersionCode,
|
|
87
88
|
required List<String> requestedAssets,
|
|
89
|
+
String? abi,
|
|
88
90
|
void Function(int done, int total, String label)? onProgress,
|
|
89
91
|
}) {
|
|
92
|
+
// When an ABI is requested, restrict the patch to only that ABI instead of
|
|
93
|
+
// shipping every architecture found in the APK.
|
|
94
|
+
final effectiveLibs =
|
|
95
|
+
abi == null ? libs : libs.where((e) => e.$1 == abi).toList();
|
|
90
96
|
final patchFiles = <String, _PatchAssetFile>{};
|
|
91
97
|
final operations = <Map<String, dynamic>>[];
|
|
92
98
|
var baseManifestSize = 0;
|
|
@@ -144,7 +150,7 @@ PackResult _writePatchPackage({
|
|
|
144
150
|
}
|
|
145
151
|
|
|
146
152
|
final libManifest = <String, dynamic>{
|
|
147
|
-
for (final (currentAbi, soBytes) in
|
|
153
|
+
for (final (currentAbi, soBytes) in effectiveLibs) currentAbi: {
|
|
148
154
|
'path': 'lib/$currentAbi/libapp.so',
|
|
149
155
|
'md5': md5.convert(soBytes).toString(),
|
|
150
156
|
},
|
|
@@ -172,10 +178,10 @@ PackResult _writePatchPackage({
|
|
|
172
178
|
|
|
173
179
|
final package = Archive()
|
|
174
180
|
..addFile(_jsonArchiveFile('manifest.json', zipManifest));
|
|
175
|
-
final progressTotal =
|
|
181
|
+
final progressTotal = effectiveLibs.length + 1;
|
|
176
182
|
onProgress?.call(0, progressTotal, 'libapp.so');
|
|
177
183
|
var progressDone = 0;
|
|
178
|
-
for (final (currentAbi, soBytes) in
|
|
184
|
+
for (final (currentAbi, soBytes) in effectiveLibs) {
|
|
179
185
|
package.addFile(
|
|
180
186
|
ArchiveFile('lib/$currentAbi/libapp.so', soBytes.length, soBytes));
|
|
181
187
|
progressDone++;
|
|
@@ -210,7 +216,7 @@ PackResult _writePatchPackage({
|
|
|
210
216
|
'version': version,
|
|
211
217
|
'md5': payloadMd5,
|
|
212
218
|
'targetVersionCode': targetVersionCode,
|
|
213
|
-
'abis':
|
|
219
|
+
'abis': effectiveLibs.map((e) => e.$1).toList(),
|
|
214
220
|
'payload': 'patch.zip',
|
|
215
221
|
};
|
|
216
222
|
_writeJson(File('${outDir.path}/manifest.json'), outerManifest);
|
|
@@ -218,7 +224,7 @@ PackResult _writePatchPackage({
|
|
|
218
224
|
return PackResult(
|
|
219
225
|
payloadPath: outZip.path,
|
|
220
226
|
manifestPath: '${outDir.path}/manifest.json',
|
|
221
|
-
abis:
|
|
227
|
+
abis: effectiveLibs.map((e) => e.$1).toList(),
|
|
222
228
|
md5: payloadMd5,
|
|
223
229
|
version: version,
|
|
224
230
|
targetVersionCode: targetVersionCode,
|