@_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.
- package/bin/flutter-patcher.js +48 -39
- package/dart-src/packages/cli-tools/.gitkeep +0 -0
- package/dart-src/packages/cli-tools/bin/flutter_patcher.dart +9 -0
- package/dart-src/packages/cli-tools/lib/flutter_patcher_cli.dart +73 -0
- package/dart-src/packages/cli-tools/lib/src/backend.dart +162 -0
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +56 -0
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +99 -0
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +151 -0
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +140 -0
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +113 -0
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +55 -0
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +95 -0
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +48 -0
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +29 -0
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +82 -0
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +55 -0
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +132 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +41 -0
- package/dart-src/packages/cli-tools/lib/src/config.dart +715 -0
- package/dart-src/packages/cli-tools/lib/src/operations.dart +206 -0
- package/dart-src/packages/cli-tools/lib/src/pack.dart +417 -0
- package/dart-src/packages/cli-tools/lib/src/sign.dart +60 -0
- package/dart-src/packages/cli-tools/lib/src/util.dart +86 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0001_hot-updater_init.sql +16 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0002_hot-updater_0.13.0.sql +8 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0003_hot-updater_0.18.0.sql +38 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0004_hot-updater_0.29.0.sql +11 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/0005_hot-updater_0.31.0.sql +24 -0
- package/dart-src/packages/cli-tools/migrations/cloudflare/bundles.sql +42 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250103114225_init.sql +228 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250314000000_hot-updater_0.13.0.sql +134 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20250516000000_hot-updater_0.18.0.sql +194 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20251014000000_hot-updater_0.21.0.sql +184 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260401000000_hot-updater_0.29.0.sql +503 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260414000000_hot-updater_0.30.0.sql +50 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260422000000_hot-updater_0.31.0.sql +20 -0
- package/dart-src/packages/cli-tools/migrations/supabase/20260520014100_hot-updater_rls.sql +48 -0
- package/dart-src/packages/cli-tools/pubspec.yaml +37 -0
- package/dart-src/packages/core/lib/flutter_patcher_core.dart +29 -0
- package/dart-src/packages/core/lib/src/app_update_info.dart +111 -0
- package/dart-src/packages/core/lib/src/bundle.dart +191 -0
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +39 -0
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +47 -0
- package/dart-src/packages/core/lib/src/changed_asset.dart +77 -0
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +78 -0
- package/dart-src/packages/core/lib/src/metadata.dart +31 -0
- package/dart-src/packages/core/lib/src/patch_info.dart +284 -0
- package/dart-src/packages/core/lib/src/platform.dart +15 -0
- package/dart-src/packages/core/lib/src/rollout.dart +209 -0
- package/dart-src/packages/core/lib/src/semver.dart +70 -0
- package/dart-src/packages/core/lib/src/semver_range.dart +358 -0
- package/dart-src/packages/core/lib/src/semver_version.dart +94 -0
- package/dart-src/packages/core/lib/src/status.dart +24 -0
- package/dart-src/packages/core/lib/src/strategy.dart +10 -0
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +30 -0
- package/dart-src/packages/core/lib/src/uuid.dart +45 -0
- package/dart-src/packages/core/pubspec.yaml +9 -0
- package/dart-src/packages/core/tool/dbg.dart +6 -0
- package/dart-src/plugins/aws/.gitkeep +0 -0
- package/dart-src/plugins/aws/lib/flutter_patcher_aws.dart +12 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +207 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +130 -0
- package/dart-src/plugins/aws/lib/src/aws_config.dart +59 -0
- package/dart-src/plugins/aws/lib/src/aws_database.dart +198 -0
- package/dart-src/plugins/aws/lib/src/aws_lambda_edge_storage.dart +72 -0
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +312 -0
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +125 -0
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +20 -0
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +123 -0
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +136 -0
- package/dart-src/plugins/aws/pubspec.yaml +22 -0
- package/dart-src/plugins/cloudflare/.gitkeep +0 -0
- package/dart-src/plugins/cloudflare/lib/flutter_patcher_cloudflare.dart +18 -0
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +29 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +93 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +131 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_client.dart +66 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_config.dart +30 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_database.dart +16 -0
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +314 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +50 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +287 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +23 -0
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +110 -0
- package/dart-src/plugins/cloudflare/migrations/0001_hot-updater_init.sql +16 -0
- package/dart-src/plugins/cloudflare/migrations/0002_hot-updater_0.13.0.sql +8 -0
- package/dart-src/plugins/cloudflare/migrations/0003_hot-updater_0.18.0.sql +38 -0
- package/dart-src/plugins/cloudflare/migrations/0004_hot-updater_0.29.0.sql +11 -0
- package/dart-src/plugins/cloudflare/migrations/0005_hot-updater_0.31.0.sql +24 -0
- package/dart-src/plugins/cloudflare/pubspec.yaml +20 -0
- package/dart-src/plugins/cloudflare/sql/bundles.sql +42 -0
- package/dart-src/plugins/plugin-core/lib/flutter_patcher_plugin_core.dart +42 -0
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +94 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +44 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +259 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work_store.dart +20 -0
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +29 -0
- package/dart-src/plugins/plugin-core/lib/src/compression_format.dart +63 -0
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +22 -0
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +853 -0
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +525 -0
- package/dart-src/plugins/plugin-core/lib/src/create_storage_key_builder.dart +15 -0
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +130 -0
- package/dart-src/plugins/plugin-core/lib/src/filter_compatible_app_versions.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/generate_min_bundle_id.dart +19 -0
- package/dart-src/plugins/plugin-core/lib/src/get_update_info.dart +241 -0
- package/dart-src/plugins/plugin-core/lib/src/is_object.dart +5 -0
- package/dart-src/plugins/plugin-core/lib/src/legacy_asset_storage_layout.dart +9 -0
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +112 -0
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +44 -0
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +70 -0
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +62 -0
- package/dart-src/plugins/plugin-core/lib/src/resolve_update_info_from_bundles.dart +43 -0
- package/dart-src/plugins/plugin-core/lib/src/storage_profile.dart +27 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +454 -0
- package/dart-src/plugins/plugin-core/lib/src/uuidv7.dart +41 -0
- package/dart-src/plugins/plugin-core/pubspec.yaml +15 -0
- package/dart-src/plugins/postgres/.gitkeep +0 -0
- package/dart-src/plugins/postgres/lib/flutter_patcher_postgres.dart +17 -0
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +130 -0
- package/dart-src/plugins/postgres/lib/src/postgres_client.dart +90 -0
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +35 -0
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +302 -0
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +120 -0
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +205 -0
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +132 -0
- package/dart-src/plugins/postgres/pubspec.yaml +18 -0
- package/dart-src/plugins/postgres/sql/bundles.sql +45 -0
- package/dart-src/plugins/postgres/sql/get_target_app_version_list.sql +21 -0
- package/dart-src/plugins/postgres/sql/get_update_info.spec.ts +171 -0
- package/dart-src/plugins/postgres/sql/get_update_info_by_app_version.sql +126 -0
- package/dart-src/plugins/postgres/sql/get_update_info_by_fingerprint_hash.sql +125 -0
- package/dart-src/plugins/postgres/sql/hash_user_id.sql +30 -0
- package/dart-src/plugins/postgres/sql/is_device_eligible.sql +237 -0
- package/dart-src/plugins/postgres/sql/prepareSql.ts +11 -0
- package/dart-src/plugins/standalone/.gitkeep +0 -0
- package/dart-src/plugins/standalone/lib/flutter_patcher_standalone.dart +12 -0
- package/dart-src/plugins/standalone/lib/src/standalone_config.dart +107 -0
- package/dart-src/plugins/standalone/lib/src/standalone_database.dart +233 -0
- package/dart-src/plugins/standalone/lib/src/standalone_storage.dart +175 -0
- package/dart-src/plugins/standalone/pubspec.yaml +18 -0
- package/dart-src/plugins/supabase/lib/edge.dart +10 -0
- package/dart-src/plugins/supabase/lib/flutter_patcher_supabase.dart +21 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +129 -0
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +154 -0
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +516 -0
- package/dart-src/plugins/supabase/lib/src/supabase_config.dart +34 -0
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +422 -0
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_database.dart +32 -0
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +99 -0
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +145 -0
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +298 -0
- package/dart-src/plugins/supabase/lib/src/types.dart +130 -0
- package/dart-src/plugins/supabase/pubspec.yaml +19 -0
- package/dart-src/plugins/supabase/supabase/edge-functions/index.ts +44 -0
- package/dart-src/plugins/supabase/supabase/edge-functions/runtime.docker.integration.spec.ts +968 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250103114225_init.sql +228 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
- package/dart-src/plugins/supabase/supabase/migrations/20250516000000_hot-updater_0.18.0.sql +194 -0
- package/dart-src/plugins/supabase/supabase/migrations/20251014000000_hot-updater_0.21.0.sql +184 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260401000000_hot-updater_0.29.0.sql +503 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260414000000_hot-updater_0.30.0.sql +50 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260422000000_hot-updater_0.31.0.sql +20 -0
- package/dart-src/plugins/supabase/supabase/migrations/20260520014100_hot-updater_rls.sql +48 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +53 -25
- /package/bin/{flutter-patcher-linux → flutter-patcher-linux-x64} +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import 'package:path/path.dart' show basename;
|
|
2
|
+
|
|
3
|
+
import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart'
|
|
4
|
+
show
|
|
5
|
+
NodeStorageProfile,
|
|
6
|
+
RuntimeStorageProfile,
|
|
7
|
+
StorageObject,
|
|
8
|
+
createStorageKeyBuilder,
|
|
9
|
+
getContentType,
|
|
10
|
+
parseStorageUri;
|
|
11
|
+
|
|
12
|
+
import 'r2_config.dart' show R2S3StorageConfig, resolveR2Client;
|
|
13
|
+
import 'r2_s3_client.dart' show R2S3ClientLike, readFileBytes, writeFileBytes;
|
|
14
|
+
|
|
15
|
+
void _ensureExpectedBucket(String bucket, String bucketName) {
|
|
16
|
+
if (bucket != bucketName) {
|
|
17
|
+
throw ArgumentError(
|
|
18
|
+
'Bucket name mismatch: expected "$bucketName", but found "$bucket".',
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Build the node (deploy/CLI) storage profile for R2 S3.
|
|
24
|
+
NodeStorageProfile createS3StorageProfile(R2S3StorageConfig config) {
|
|
25
|
+
final R2S3ClientLike client = resolveR2Client(config);
|
|
26
|
+
final getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
27
|
+
|
|
28
|
+
return _R2NodeProfile(client, config, getStorageKey);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Build the runtime (update-check) storage profile for R2 S3.
|
|
32
|
+
RuntimeStorageProfile createS3RuntimeStorageProfile(R2S3StorageConfig config) {
|
|
33
|
+
final R2S3ClientLike client = resolveR2Client(config);
|
|
34
|
+
return _R2RuntimeProfile(client, config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class _R2NodeProfile implements NodeStorageProfile {
|
|
38
|
+
_R2NodeProfile(this._client, this._config, this._getStorageKey);
|
|
39
|
+
|
|
40
|
+
final R2S3ClientLike _client;
|
|
41
|
+
final R2S3StorageConfig _config;
|
|
42
|
+
final String Function(String, [String, String, String]) _getStorageKey;
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
Future<Map<String, String>> upload(String key, String filePath) async {
|
|
46
|
+
final bytes = await readFileBytes(filePath);
|
|
47
|
+
final contentType = getContentType(filePath);
|
|
48
|
+
final filename = basename(filePath);
|
|
49
|
+
final storageKey = _getStorageKey(key, filename);
|
|
50
|
+
|
|
51
|
+
await _client.putObject(storageKey, bytes, contentType);
|
|
52
|
+
return {'storageUri': 'r2://${_config.bucketName}/$storageKey'};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
Future<bool> exists(String storageUri) async {
|
|
57
|
+
final parsed = parseStorageUri(storageUri, 'r2');
|
|
58
|
+
_ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
59
|
+
return _client.headObject(parsed.key);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
Future<void> delete(String storageUri) async {
|
|
64
|
+
final parsed = parseStorageUri(storageUri, 'r2');
|
|
65
|
+
_ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
66
|
+
await _client.deleteObject(parsed.key);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@override
|
|
70
|
+
Future<void> downloadFile(String storageUri, String filePath) async {
|
|
71
|
+
final parsed = parseStorageUri(storageUri, 'r2');
|
|
72
|
+
_ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
73
|
+
final bytes = await _client.getObjectAsBytes(parsed.key);
|
|
74
|
+
await writeFileBytes(filePath, bytes);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@override
|
|
78
|
+
Future<List<StorageObject>> listObjects([String? prefix]) =>
|
|
79
|
+
_client.listObjects(prefix);
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
Future<void> deleteObjects(List<String> keys) =>
|
|
83
|
+
_client.deleteObjects(keys);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class _R2RuntimeProfile implements RuntimeStorageProfile {
|
|
87
|
+
_R2RuntimeProfile(this._client, this._config);
|
|
88
|
+
|
|
89
|
+
final R2S3ClientLike _client;
|
|
90
|
+
final R2S3StorageConfig _config;
|
|
91
|
+
|
|
92
|
+
@override
|
|
93
|
+
Future<Map<String, String>> getDownloadUrl(String storageUri) async {
|
|
94
|
+
final parsed = parseStorageUri(storageUri, 'r2');
|
|
95
|
+
_ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
96
|
+
final fileUrl = await _client.getSignedUrl(parsed.key);
|
|
97
|
+
return {'fileUrl': fileUrl};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
Future<String?> readText(String storageUri) async {
|
|
102
|
+
final parsed = parseStorageUri(storageUri, 'r2');
|
|
103
|
+
_ensureExpectedBucket(parsed.bucket, _config.bucketName);
|
|
104
|
+
try {
|
|
105
|
+
return await _client.getObjectAsString(parsed.key);
|
|
106
|
+
} catch (_) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Migration number: 0001 2025-01-27T08:37:50.374Z
|
|
2
|
+
-- HotUpdater.bundles
|
|
3
|
+
|
|
4
|
+
CREATE TABLE bundles (
|
|
5
|
+
id TEXT PRIMARY KEY,
|
|
6
|
+
platform TEXT NOT NULL CHECK (platform IN ('ios', 'android')),
|
|
7
|
+
target_app_version TEXT NOT NULL,
|
|
8
|
+
should_force_update INTEGER NOT NULL CHECK (should_force_update IN (0, 1)),
|
|
9
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
10
|
+
file_url TEXT NOT NULL,
|
|
11
|
+
file_hash TEXT NOT NULL,
|
|
12
|
+
git_commit_hash TEXT,
|
|
13
|
+
message TEXT
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- Migration number: 0003 2025-05-18T16:25:12.486Z
|
|
2
|
+
-- HotUpdater.bundles
|
|
3
|
+
|
|
4
|
+
ALTER TABLE bundles ADD COLUMN fingerprint_hash TEXT;
|
|
5
|
+
ALTER TABLE bundles ADD COLUMN storage_uri TEXT;
|
|
6
|
+
ALTER TABLE bundles ADD COLUMN metadata JSONB DEFAULT '{}';
|
|
7
|
+
|
|
8
|
+
UPDATE bundles
|
|
9
|
+
SET storage_uri = 'r2://%%BUCKET_NAME%%/' || id || '/bundle.zip'
|
|
10
|
+
WHERE storage_uri IS NULL;
|
|
11
|
+
|
|
12
|
+
CREATE TABLE bundles_temp (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
platform TEXT NOT NULL,
|
|
15
|
+
should_force_update INTEGER NOT NULL,
|
|
16
|
+
enabled INTEGER NOT NULL,
|
|
17
|
+
file_hash TEXT NOT NULL,
|
|
18
|
+
git_commit_hash TEXT,
|
|
19
|
+
message TEXT,
|
|
20
|
+
channel TEXT NOT NULL DEFAULT 'production',
|
|
21
|
+
storage_uri TEXT NOT NULL,
|
|
22
|
+
target_app_version TEXT,
|
|
23
|
+
fingerprint_hash TEXT,
|
|
24
|
+
metadata JSONB DEFAULT '{}',
|
|
25
|
+
CHECK ((target_app_version IS NOT NULL) OR (fingerprint_hash IS NOT NULL))
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
INSERT INTO bundles_temp
|
|
29
|
+
SELECT id, platform, should_force_update, enabled, file_hash, git_commit_hash, message, channel, storage_uri, target_app_version, fingerprint_hash, metadata
|
|
30
|
+
FROM bundles;
|
|
31
|
+
|
|
32
|
+
DROP TABLE bundles;
|
|
33
|
+
|
|
34
|
+
ALTER TABLE bundles_temp RENAME TO bundles;
|
|
35
|
+
|
|
36
|
+
CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
|
|
37
|
+
CREATE INDEX bundles_fingerprint_hash_idx ON bundles(fingerprint_hash);
|
|
38
|
+
CREATE INDEX bundles_channel_idx ON bundles(channel);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Migration number: 0004 2025-12-21T00:00:00.000Z
|
|
2
|
+
|
|
3
|
+
-- HotUpdater.bundles
|
|
4
|
+
|
|
5
|
+
ALTER TABLE bundles ADD COLUMN rollout_cohort_count INTEGER DEFAULT 1000
|
|
6
|
+
CHECK (rollout_cohort_count >= 0 AND rollout_cohort_count <= 1000);
|
|
7
|
+
|
|
8
|
+
CREATE INDEX IF NOT EXISTS bundles_rollout_idx ON bundles(rollout_cohort_count);
|
|
9
|
+
|
|
10
|
+
-- SQLite doesn't have array type, store as JSON text
|
|
11
|
+
ALTER TABLE bundles ADD COLUMN target_cohorts TEXT;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- Migration number: 0005 2026-04-22T00:00:00.000Z
|
|
2
|
+
|
|
3
|
+
-- HotUpdater.bundle_artifact_columns
|
|
4
|
+
|
|
5
|
+
ALTER TABLE bundles ADD COLUMN manifest_storage_uri TEXT;
|
|
6
|
+
ALTER TABLE bundles ADD COLUMN manifest_file_hash TEXT;
|
|
7
|
+
ALTER TABLE bundles ADD COLUMN asset_base_storage_uri TEXT;
|
|
8
|
+
|
|
9
|
+
CREATE TABLE bundle_patches (
|
|
10
|
+
id TEXT PRIMARY KEY,
|
|
11
|
+
bundle_id TEXT NOT NULL,
|
|
12
|
+
base_bundle_id TEXT NOT NULL,
|
|
13
|
+
base_file_hash TEXT NOT NULL,
|
|
14
|
+
patch_file_hash TEXT NOT NULL,
|
|
15
|
+
patch_storage_uri TEXT NOT NULL,
|
|
16
|
+
order_index INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
FOREIGN KEY (bundle_id) REFERENCES bundles(id) ON DELETE CASCADE,
|
|
18
|
+
FOREIGN KEY (base_bundle_id) REFERENCES bundles(id) ON DELETE CASCADE
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE INDEX IF NOT EXISTS bundle_patches_bundle_id_idx
|
|
22
|
+
ON bundle_patches(bundle_id);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS bundle_patches_base_bundle_id_idx
|
|
24
|
+
ON bundle_patches(base_bundle_id);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: flutter_patcher_cloudflare
|
|
2
|
+
description: Cloudflare D1 + R2 storage plugins for flutter_patcher (faithful port of hot-updater plugins/cloudflare).
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
publish_to: none
|
|
5
|
+
|
|
6
|
+
environment:
|
|
7
|
+
sdk: ">=3.3.0 <4.0.0"
|
|
8
|
+
|
|
9
|
+
dependencies:
|
|
10
|
+
flutter_patcher_core:
|
|
11
|
+
path: ../../packages/core
|
|
12
|
+
flutter_patcher_plugin_core:
|
|
13
|
+
path: ../../plugins/plugin-core
|
|
14
|
+
crypto: ^3.0.0
|
|
15
|
+
http: ^1.2.0
|
|
16
|
+
path: ^1.9.0
|
|
17
|
+
|
|
18
|
+
dev_dependencies:
|
|
19
|
+
test: ^1.25.0
|
|
20
|
+
lints: ^4.0.0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- HotUpdater.bundles
|
|
2
|
+
|
|
3
|
+
CREATE TABLE bundles (
|
|
4
|
+
id TEXT PRIMARY KEY,
|
|
5
|
+
platform TEXT NOT NULL CHECK (platform IN ('ios', 'android')),
|
|
6
|
+
target_app_version TEXT,
|
|
7
|
+
should_force_update INTEGER NOT NULL CHECK (should_force_update IN (0, 1)),
|
|
8
|
+
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
9
|
+
file_hash TEXT NOT NULL,
|
|
10
|
+
git_commit_hash TEXT,
|
|
11
|
+
message TEXT,
|
|
12
|
+
channel TEXT NOT NULL DEFAULT 'production',
|
|
13
|
+
storage_uri TEXT NOT NULL,
|
|
14
|
+
fingerprint_hash TEXT,
|
|
15
|
+
metadata JSONB DEFAULT '{}',
|
|
16
|
+
manifest_storage_uri TEXT,
|
|
17
|
+
manifest_file_hash TEXT,
|
|
18
|
+
asset_base_storage_uri TEXT,
|
|
19
|
+
rollout_cohort_count INTEGER DEFAULT 1000
|
|
20
|
+
CHECK (rollout_cohort_count >= 0 AND rollout_cohort_count <= 1000),
|
|
21
|
+
target_cohorts TEXT,
|
|
22
|
+
CHECK ((target_app_version IS NOT NULL) OR (fingerprint_hash IS NOT NULL))
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE TABLE bundle_patches (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
bundle_id TEXT NOT NULL,
|
|
28
|
+
base_bundle_id TEXT NOT NULL,
|
|
29
|
+
base_file_hash TEXT NOT NULL,
|
|
30
|
+
patch_file_hash TEXT NOT NULL,
|
|
31
|
+
patch_storage_uri TEXT NOT NULL,
|
|
32
|
+
order_index INTEGER NOT NULL DEFAULT 0,
|
|
33
|
+
FOREIGN KEY (bundle_id) REFERENCES bundles(id) ON DELETE CASCADE,
|
|
34
|
+
FOREIGN KEY (base_bundle_id) REFERENCES bundles(id) ON DELETE CASCADE
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
|
|
38
|
+
CREATE INDEX bundles_fingerprint_hash_idx ON bundles(fingerprint_hash);
|
|
39
|
+
CREATE INDEX bundles_channel_idx ON bundles(channel);
|
|
40
|
+
CREATE INDEX bundles_rollout_idx ON bundles(rollout_cohort_count);
|
|
41
|
+
CREATE INDEX bundle_patches_bundle_id_idx ON bundle_patches(bundle_id);
|
|
42
|
+
CREATE INDEX bundle_patches_base_bundle_id_idx ON bundle_patches(base_bundle_id);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
library;
|
|
2
|
+
|
|
3
|
+
// Re-export core types for convenience.
|
|
4
|
+
export 'package:flutter_patcher_core/flutter_patcher_core.dart'
|
|
5
|
+
show
|
|
6
|
+
Bundle,
|
|
7
|
+
GetBundlesArgs,
|
|
8
|
+
Platform,
|
|
9
|
+
UpdateInfo,
|
|
10
|
+
semverSatisfies;
|
|
11
|
+
|
|
12
|
+
// Plugin-core types.
|
|
13
|
+
export 'src/types.dart';
|
|
14
|
+
|
|
15
|
+
// Helpers.
|
|
16
|
+
export 'src/calculate_pagination.dart';
|
|
17
|
+
export 'src/query_bundles.dart';
|
|
18
|
+
export 'src/paginate_bundles.dart';
|
|
19
|
+
export 'src/filter_compatible_app_versions.dart';
|
|
20
|
+
export 'src/parse_storage_uri.dart';
|
|
21
|
+
export 'src/content_addressed_assets.dart';
|
|
22
|
+
export 'src/compression_format.dart';
|
|
23
|
+
export 'src/storage_profile.dart';
|
|
24
|
+
export 'src/create_storage_key_builder.dart';
|
|
25
|
+
export 'src/is_object.dart';
|
|
26
|
+
export 'src/asset_storage_layout.dart';
|
|
27
|
+
export 'src/bundle_storage_layout.dart';
|
|
28
|
+
export 'src/legacy_asset_storage_layout.dart';
|
|
29
|
+
|
|
30
|
+
// Factories.
|
|
31
|
+
export 'src/bundle_unit_of_work.dart';
|
|
32
|
+
export 'src/bundle_unit_of_work_store.dart'
|
|
33
|
+
show getRequestBundleUnitOfWork, clearUnitOfWorkStore;
|
|
34
|
+
export 'src/request_update_bundle_state.dart';
|
|
35
|
+
export 'src/create_database_plugin.dart';
|
|
36
|
+
export 'src/create_blob_database_plugin.dart';
|
|
37
|
+
export 'src/create_storage_plugin.dart';
|
|
38
|
+
export 'src/generate_min_bundle_id.dart';
|
|
39
|
+
export 'src/uuidv7.dart';
|
|
40
|
+
export 'src/get_update_info.dart';
|
|
41
|
+
export 'src/resolve_update_info_from_bundles.dart';
|
|
42
|
+
export 'src/resolve_update_info_from_bundles.dart';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import 'content_addressed_assets.dart' show getContentAddressedAssetStoragePath;
|
|
2
|
+
import 'legacy_asset_storage_layout.dart' show getLegacyManifestAssetStoragePath;
|
|
3
|
+
|
|
4
|
+
/// The two supported asset storage layout strategies.
|
|
5
|
+
typedef AssetStorageLayout = String;
|
|
6
|
+
|
|
7
|
+
const String assetLayoutContentAddressed = 'content-addressed';
|
|
8
|
+
const String assetLayoutLegacyFiles = 'legacy-files';
|
|
9
|
+
|
|
10
|
+
/// Whether [assetPath] is a brotli-compressed manifest path
|
|
11
|
+
/// (e.g. `index.android.bundle` → `index.android.bundle.br`).
|
|
12
|
+
bool isBrotliManifestAssetPath(String assetPath) {
|
|
13
|
+
return RegExp(r'(^|/)index\.[^/]+\.bundle$')
|
|
14
|
+
.hasMatch(assetPath.replaceAll('\\', '/'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Append `.br` to brotli manifest asset paths, pass through otherwise.
|
|
18
|
+
String getManifestAssetDownloadPath(String assetPath) {
|
|
19
|
+
return isBrotliManifestAssetPath(assetPath) ? '$assetPath.br' : assetPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Build a full storage URI by joining a base URI with a relative path.
|
|
23
|
+
///
|
|
24
|
+
/// Segments of [relativePath] are percent-encoded individually.
|
|
25
|
+
String createStorageUriWithRelativePath({
|
|
26
|
+
required String baseStorageUri,
|
|
27
|
+
required String relativePath,
|
|
28
|
+
}) {
|
|
29
|
+
final storageUrl = Uri.parse(baseStorageUri);
|
|
30
|
+
final normalizedBasePath =
|
|
31
|
+
storageUrl.path.replaceAll(RegExp(r'/+$'), '');
|
|
32
|
+
final normalizedRelativePath = relativePath
|
|
33
|
+
.replaceAll('\\', '/')
|
|
34
|
+
.split('/')
|
|
35
|
+
.where((s) => s.isNotEmpty)
|
|
36
|
+
.map(Uri.encodeComponent)
|
|
37
|
+
.join('/');
|
|
38
|
+
|
|
39
|
+
return storageUrl
|
|
40
|
+
.replace(path: '$normalizedBasePath/$normalizedRelativePath')
|
|
41
|
+
.toString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Determine the asset storage layout from the base URI.
|
|
45
|
+
///
|
|
46
|
+
/// Returns `'content-addressed'` if the URI path ends with `/assets`,
|
|
47
|
+
/// otherwise `'legacy-files'`.
|
|
48
|
+
AssetStorageLayout getAssetStorageLayout(String assetBaseStorageUri) {
|
|
49
|
+
final pathname =
|
|
50
|
+
Uri.parse(assetBaseStorageUri).path.replaceAll(RegExp(r'/+$'), '');
|
|
51
|
+
return pathname.endsWith('/assets') || pathname == '/assets'
|
|
52
|
+
? assetLayoutContentAddressed
|
|
53
|
+
: assetLayoutLegacyFiles;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Convenience check: is the base URI using content-addressed layout?
|
|
57
|
+
bool isContentAddressedAssetBaseStorageUri(String assetBaseStorageUri) {
|
|
58
|
+
return getAssetStorageLayout(assetBaseStorageUri) ==
|
|
59
|
+
assetLayoutContentAddressed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Compute the storage path for a manifest asset, dispatching on layout.
|
|
63
|
+
String getManifestAssetStoragePath({
|
|
64
|
+
required String assetBaseStorageUri,
|
|
65
|
+
required String assetPath,
|
|
66
|
+
required String fileHash,
|
|
67
|
+
}) {
|
|
68
|
+
final layout = getAssetStorageLayout(assetBaseStorageUri);
|
|
69
|
+
|
|
70
|
+
if (layout == assetLayoutContentAddressed) {
|
|
71
|
+
return getContentAddressedAssetStoragePath(
|
|
72
|
+
assetPath: assetPath,
|
|
73
|
+
fileHash: fileHash,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return getLegacyManifestAssetStoragePath(assetPath: assetPath);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Build a full storage URI for a manifest asset.
|
|
81
|
+
String resolveManifestAssetStorageUri({
|
|
82
|
+
required String assetBaseStorageUri,
|
|
83
|
+
required String assetPath,
|
|
84
|
+
required String fileHash,
|
|
85
|
+
}) {
|
|
86
|
+
return createStorageUriWithRelativePath(
|
|
87
|
+
baseStorageUri: assetBaseStorageUri,
|
|
88
|
+
relativePath: getManifestAssetStoragePath(
|
|
89
|
+
assetBaseStorageUri: assetBaseStorageUri,
|
|
90
|
+
assetPath: assetPath,
|
|
91
|
+
fileHash: fileHash,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/// The top-level prefix for bundle storage keys.
|
|
2
|
+
const String bundleStoragePrefix = 'bundles';
|
|
3
|
+
|
|
4
|
+
/// Build a storage key for a bundle, e.g. `bundles/<bundleId>/patch.zip`.
|
|
5
|
+
String createBundleStorageKey(String bundleId, [List<String> relativePaths = const []]) {
|
|
6
|
+
return [bundleStoragePrefix, bundleId, ...relativePaths]
|
|
7
|
+
.where((s) => s.isNotEmpty)
|
|
8
|
+
.join('/');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/// Replace the bundle-id segment in a storage URI with a relative path.
|
|
12
|
+
///
|
|
13
|
+
/// For example, given a storage URI containing `.../bundles/<bundleId>/patch.zip`
|
|
14
|
+
/// and a [relativePath] of `files/manifest.json`, this returns a URI pointing
|
|
15
|
+
/// to `.../files/manifest.json`.
|
|
16
|
+
String createStorageRootUriWithPath(
|
|
17
|
+
String storageUri,
|
|
18
|
+
String bundleId,
|
|
19
|
+
String relativePath,
|
|
20
|
+
) {
|
|
21
|
+
final storageUrl = Uri.parse(storageUri);
|
|
22
|
+
final segments = storageUrl.path.split('/').where((s) => s.isNotEmpty).toList();
|
|
23
|
+
final bundleIndex = segments.lastIndexOf(bundleId);
|
|
24
|
+
if (bundleIndex < 0) {
|
|
25
|
+
throw StateError(
|
|
26
|
+
'Storage URI does not contain bundle id: $bundleId',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
final rootSegments = segments.sublist(0, bundleIndex);
|
|
31
|
+
if (rootSegments.isNotEmpty && rootSegments.last == bundleStoragePrefix) {
|
|
32
|
+
rootSegments.removeLast();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
final relativeSegments = relativePath
|
|
36
|
+
.split('/')
|
|
37
|
+
.where((s) => s.isNotEmpty)
|
|
38
|
+
.map(Uri.encodeComponent)
|
|
39
|
+
.toList();
|
|
40
|
+
|
|
41
|
+
return storageUrl
|
|
42
|
+
.replace(path: '/${[...rootSegments, ...relativeSegments].join('/')}')
|
|
43
|
+
.toString();
|
|
44
|
+
}
|