@_nazmiforreal/flutter-ota 0.1.12 → 0.1.13
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 +1 -1
- package/bin/flutter-ota-linux-x64 +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +46 -76
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +140 -166
- package/dart-src/packages/cli-tools/README.md +1 -1
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +30 -17
- package/dart-src/packages/cli-tools/lib/src/backend.dart +30 -31
- package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/commands/build.dart +44 -43
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +265 -239
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +75 -66
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +39 -35
- package/dart-src/packages/cli-tools/lib/src/commands/console.dart +30 -27
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +88 -72
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +66 -29
- package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +10 -6
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +290 -181
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +24 -23
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +111 -97
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +43 -34
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +57 -53
- package/dart-src/packages/cli-tools/lib/src/config.dart +227 -93
- package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -8
- package/dart-src/packages/cli-tools/lib/src/pack.dart +26 -25
- package/dart-src/packages/cli-tools/lib/src/pocketbase/data_bootstrap.dart +39 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/hooks/bundles.pb.js +56 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/installer.dart +189 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +123 -0
- package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +237 -0
- package/dart-src/packages/cli-tools/lib/src/runner.dart +36 -15
- package/dart-src/packages/cli-tools/lib/src/sign.dart +13 -5
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -13
- package/dart-src/packages/cli-tools/lib/src/util.dart +3 -1
- package/dart-src/packages/cli-tools/pubspec.lock +69 -116
- package/dart-src/packages/cli-tools/pubspec.yaml +25 -7
- package/dart-src/packages/cli-tools/test/backends_test.dart +62 -32
- package/dart-src/packages/cli-tools/test/build_pack_test.dart +4 -4
- package/dart-src/packages/cli-tools/test/cli_test.dart +89 -42
- package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +8 -13
- package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +23 -18
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +197 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +70 -46
- package/dart-src/packages/cli-tools/test/pocketbase_installer_test.dart +72 -0
- package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +107 -0
- package/dart-src/packages/cli-tools/test/pocketbase_smoke_test.dart +59 -0
- package/dart-src/packages/core/README.md +2 -2
- package/dart-src/packages/core/example/lib/main.dart +69 -0
- package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +1 -1
- package/dart-src/packages/core/lib/src/app_update_info.dart +17 -15
- package/dart-src/packages/core/lib/src/bundle.dart +102 -94
- package/dart-src/packages/core/lib/src/bundle_artifacts.dart +1 -2
- package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +15 -16
- package/dart-src/packages/core/lib/src/changed_asset.dart +24 -22
- package/dart-src/packages/core/lib/src/get_bundles_args.dart +13 -13
- package/dart-src/packages/core/lib/src/metadata.dart +9 -7
- package/dart-src/packages/core/lib/src/patch_info.dart +8 -7
- package/dart-src/packages/core/lib/src/platform.dart +4 -3
- package/dart-src/packages/core/lib/src/rollout.dart +14 -11
- package/dart-src/packages/core/lib/src/semver_range.dart +49 -39
- package/dart-src/packages/core/lib/src/semver_version.dart +8 -7
- package/dart-src/packages/core/lib/src/status.dart +4 -3
- package/dart-src/packages/core/lib/src/update_bundle_params.dart +7 -7
- package/dart-src/packages/core/lib/src/uuid.dart +1 -2
- package/dart-src/packages/core/pubspec.yaml +6 -7
- package/dart-src/packages/core/test/rollout_test.dart +15 -8
- package/dart-src/packages/core/test/semver_test.dart +5 -2
- package/dart-src/packages/core/test/types_test.dart +26 -24
- package/dart-src/packages/core/test/uuid_test.dart +3 -1
- package/dart-src/packages/core/tool/dbg.dart +1 -0
- package/dart-src/plugins/aws/CHANGELOG.md +6 -0
- package/dart-src/plugins/aws/example/lib/main.dart +60 -0
- package/dart-src/plugins/aws/lib/flutter_ota_kit_aws.dart +5 -1
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +11 -16
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_signer.dart +7 -2
- package/dart-src/plugins/aws/lib/src/aws_database.dart +14 -18
- package/dart-src/plugins/aws/lib/src/aws_s3_client.dart +23 -18
- package/dart-src/plugins/aws/lib/src/aws_ssm_client.dart +5 -4
- package/dart-src/plugins/aws/lib/src/aws_storage.dart +2 -3
- package/dart-src/plugins/aws/lib/src/aws_storage_profile.dart +10 -16
- package/dart-src/plugins/aws/lib/src/with_cloudfront_signed_url.dart +6 -9
- package/dart-src/plugins/aws/pubspec.yaml +15 -10
- package/dart-src/plugins/aws/test/aws_database_test.dart +51 -57
- package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +8 -13
- package/dart-src/plugins/cloudflare/CHANGELOG.md +7 -0
- package/dart-src/plugins/cloudflare/example/lib/main.dart +62 -0
- package/dart-src/plugins/cloudflare/lib/flutter_ota_kit_cloudflare.dart +4 -2
- package/dart-src/plugins/cloudflare/lib/src/cloudflare_worker_database.dart +3 -3
- package/dart-src/plugins/cloudflare/lib/src/d1_build_where.dart +3 -2
- package/dart-src/plugins/cloudflare/lib/src/d1_bundle_mapper.dart +24 -27
- package/dart-src/plugins/cloudflare/lib/src/d1_database_plugin.dart +22 -30
- package/dart-src/plugins/cloudflare/lib/src/r2_config.dart +1 -1
- package/dart-src/plugins/cloudflare/lib/src/r2_s3_client.dart +21 -29
- package/dart-src/plugins/cloudflare/lib/src/r2_storage.dart +7 -7
- package/dart-src/plugins/cloudflare/lib/src/r2_storage_profile.dart +7 -15
- package/dart-src/plugins/cloudflare/pubspec.yaml +14 -9
- package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +46 -42
- package/dart-src/plugins/cloudflare/test/d1_database_test.dart +100 -90
- package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +23 -18
- package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +8 -13
- package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +1 -4
- package/dart-src/plugins/plugin-core/CHANGELOG.md +16 -0
- package/dart-src/plugins/plugin-core/example/lib/main.dart +61 -0
- package/dart-src/plugins/plugin-core/lib/flutter_ota_kit_plugin_core.dart +3 -7
- package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +5 -5
- package/dart-src/plugins/plugin-core/lib/src/bundle_helpers.dart +23 -0
- package/dart-src/plugins/plugin-core/lib/src/bundle_storage_layout.dart +16 -10
- package/dart-src/plugins/plugin-core/lib/src/bundle_unit_of_work.dart +31 -26
- package/dart-src/plugins/plugin-core/lib/src/calculate_pagination.dart +5 -1
- package/dart-src/plugins/plugin-core/lib/src/content_addressed_assets.dart +1 -1
- package/dart-src/plugins/plugin-core/lib/src/create_blob_database_plugin.dart +78 -71
- package/dart-src/plugins/plugin-core/lib/src/create_database_plugin.dart +56 -35
- package/dart-src/plugins/plugin-core/lib/src/create_storage_plugin.dart +10 -13
- package/dart-src/plugins/plugin-core/lib/src/paginate_bundles.dart +30 -17
- package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +3 -1
- package/dart-src/plugins/plugin-core/lib/src/query_bundles.dart +5 -3
- package/dart-src/plugins/plugin-core/lib/src/request_update_bundle_state.dart +2 -4
- package/dart-src/plugins/plugin-core/lib/src/storage_helpers.dart +18 -0
- package/dart-src/plugins/plugin-core/lib/src/types.dart +7 -6
- package/dart-src/plugins/plugin-core/pubspec.yaml +12 -9
- package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +13 -9
- package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +28 -15
- package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +254 -122
- package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +36 -32
- package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +18 -15
- package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +8 -3
- package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +682 -693
- package/dart-src/plugins/plugin-core/test/helpers_test.dart +45 -21
- package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +76 -13
- package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +20 -9
- package/dart-src/plugins/pocketbase/CHANGELOG.md +8 -0
- package/dart-src/plugins/pocketbase/LICENSE +21 -0
- package/dart-src/plugins/pocketbase/README.md +54 -0
- package/dart-src/plugins/pocketbase/example/lib/main.dart +62 -0
- package/dart-src/plugins/pocketbase/lib/flutter_ota_kit_pocketbase.dart +28 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_bundle_mapper.dart +121 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +349 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_config.dart +61 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_database.dart +278 -0
- package/dart-src/plugins/pocketbase/lib/src/pocketbase_storage.dart +255 -0
- package/dart-src/plugins/pocketbase/pubspec.yaml +26 -0
- package/dart-src/plugins/pocketbase/test/pb_uri_test.dart +12 -0
- package/dart-src/plugins/postgres/CHANGELOG.md +6 -0
- package/dart-src/plugins/postgres/example/lib/main.dart +67 -0
- package/dart-src/plugins/postgres/lib/flutter_ota_kit_postgres.dart +3 -6
- package/dart-src/plugins/postgres/lib/src/postgres_bundle_mapper.dart +32 -52
- package/dart-src/plugins/postgres/lib/src/postgres_config.dart +3 -1
- package/dart-src/plugins/postgres/lib/src/postgres_database.dart +28 -38
- package/dart-src/plugins/postgres/lib/src/postgres_get_update_info.dart +16 -14
- package/dart-src/plugins/postgres/lib/src/postgres_storage.dart +26 -14
- package/dart-src/plugins/postgres/lib/src/postgres_types.dart +27 -28
- package/dart-src/plugins/postgres/pubspec.yaml +14 -9
- package/dart-src/plugins/postgres/test/mock_postgres_client.dart +62 -39
- package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +3 -4
- package/dart-src/plugins/postgres/test/postgres_database_test.dart +102 -73
- package/dart-src/plugins/postgres/test/postgres_storage_runtime_test.dart +60 -0
- package/dart-src/plugins/supabase/CHANGELOG.md +17 -0
- package/dart-src/plugins/supabase/README.md +2 -2
- package/dart-src/plugins/supabase/example/lib/main.dart +63 -0
- package/dart-src/plugins/supabase/lib/flutter_ota_kit_supabase.dart +2 -0
- package/dart-src/plugins/supabase/lib/src/error_message.dart +11 -0
- package/dart-src/plugins/supabase/lib/src/supabase_bundle_mapper.dart +34 -58
- package/dart-src/plugins/supabase/lib/src/supabase_client_adapter.dart +3 -13
- package/dart-src/plugins/supabase/lib/src/supabase_client_http.dart +34 -52
- package/dart-src/plugins/supabase/lib/src/supabase_database.dart +38 -30
- package/dart-src/plugins/supabase/lib/src/supabase_edge_function_storage.dart +21 -29
- package/dart-src/plugins/supabase/lib/src/supabase_signed_url_batcher.dart +7 -19
- package/dart-src/plugins/supabase/lib/src/supabase_storage.dart +18 -29
- package/dart-src/plugins/supabase/lib/src/types.dart +26 -26
- package/dart-src/plugins/supabase/pubspec.yaml +14 -9
- package/dart-src/plugins/supabase/test/mock_supabase_client.dart +57 -57
- package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +30 -27
- package/dart-src/plugins/supabase/test/supabase_database_test.dart +26 -35
- package/dart-src/plugins/supabase/test/supabase_storage_test.dart +27 -34
- package/package.json +3 -3
- package/scripts/vendor.sh +1 -1
|
@@ -91,8 +91,9 @@ PackResult _writePatchPackage({
|
|
|
91
91
|
}) {
|
|
92
92
|
// When an ABI is requested, restrict the patch to only that ABI instead of
|
|
93
93
|
// shipping every architecture found in the APK.
|
|
94
|
-
final effectiveLibs =
|
|
95
|
-
|
|
94
|
+
final effectiveLibs = abi == null
|
|
95
|
+
? libs
|
|
96
|
+
: libs.where((e) => e.$1 == abi).toList();
|
|
96
97
|
final patchFiles = <String, _PatchAssetFile>{};
|
|
97
98
|
final operations = <Map<String, dynamic>>[];
|
|
98
99
|
var baseManifestSize = 0;
|
|
@@ -115,17 +116,12 @@ PackResult _writePatchPackage({
|
|
|
115
116
|
for (final key in requestedAssets) {
|
|
116
117
|
final variantsRaw = assetManifest[key];
|
|
117
118
|
if (variantsRaw is! List) {
|
|
118
|
-
throw PackException(
|
|
119
|
-
'asset key not found in AssetManifest.bin: $key');
|
|
119
|
+
throw PackException('asset key not found in AssetManifest.bin: $key');
|
|
120
120
|
}
|
|
121
121
|
final variants = variantsRaw
|
|
122
122
|
.map((variant) => Map<String, dynamic>.from(variant as Map))
|
|
123
123
|
.toList(growable: false);
|
|
124
|
-
operations.add({
|
|
125
|
-
'op': 'upsert',
|
|
126
|
-
'key': key,
|
|
127
|
-
'variants': variants,
|
|
128
|
-
});
|
|
124
|
+
operations.add({'op': 'upsert', 'key': key, 'variants': variants});
|
|
129
125
|
for (final variant in variants) {
|
|
130
126
|
final assetPath = variant['asset'];
|
|
131
127
|
if (assetPath is! String || assetPath.isEmpty) {
|
|
@@ -150,10 +146,11 @@ PackResult _writePatchPackage({
|
|
|
150
146
|
}
|
|
151
147
|
|
|
152
148
|
final libManifest = <String, dynamic>{
|
|
153
|
-
for (final (currentAbi, soBytes) in effectiveLibs)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
for (final (currentAbi, soBytes) in effectiveLibs)
|
|
150
|
+
currentAbi: {
|
|
151
|
+
'path': 'lib/$currentAbi/libapp.so',
|
|
152
|
+
'md5': md5.convert(soBytes).toString(),
|
|
153
|
+
},
|
|
157
154
|
};
|
|
158
155
|
|
|
159
156
|
final zipManifest = <String, dynamic>{
|
|
@@ -167,11 +164,13 @@ PackResult _writePatchPackage({
|
|
|
167
164
|
'manifestPatch': 'manifest_patch.json',
|
|
168
165
|
'prefix': 'assets/',
|
|
169
166
|
'files': patchFiles.values
|
|
170
|
-
.map(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
.map(
|
|
168
|
+
(file) => {
|
|
169
|
+
'path': file.path,
|
|
170
|
+
'md5': file.md5Hex,
|
|
171
|
+
'size': file.bytes.length,
|
|
172
|
+
},
|
|
173
|
+
)
|
|
175
174
|
.toList(),
|
|
176
175
|
},
|
|
177
176
|
};
|
|
@@ -183,7 +182,8 @@ PackResult _writePatchPackage({
|
|
|
183
182
|
var progressDone = 0;
|
|
184
183
|
for (final (currentAbi, soBytes) in effectiveLibs) {
|
|
185
184
|
package.addFile(
|
|
186
|
-
|
|
185
|
+
ArchiveFile('lib/$currentAbi/libapp.so', soBytes.length, soBytes),
|
|
186
|
+
);
|
|
187
187
|
progressDone++;
|
|
188
188
|
onProgress?.call(progressDone, progressTotal, 'lib/$currentAbi/libapp.so');
|
|
189
189
|
}
|
|
@@ -204,8 +204,7 @@ PackResult _writePatchPackage({
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// archive 3.x returns List<int>?, 4.x returns List<int>; coerce to bytes.
|
|
207
|
-
|
|
208
|
-
final List<int> packageBytes = ZipEncoder().encode(package) ?? const <int>[];
|
|
207
|
+
final List<int> packageBytes = ZipEncoder().encode(package);
|
|
209
208
|
final outZip = File('${outDir.path}/patch.zip');
|
|
210
209
|
outZip.writeAsBytesSync(packageBytes);
|
|
211
210
|
onProgress?.call(progressTotal, progressTotal, 'patch.zip');
|
|
@@ -233,8 +232,9 @@ PackResult _writePatchPackage({
|
|
|
233
232
|
}
|
|
234
233
|
|
|
235
234
|
ArchiveFile _jsonArchiveFile(String name, Map<String, dynamic> json) {
|
|
236
|
-
final bytes =
|
|
237
|
-
|
|
235
|
+
final bytes = utf8.encode(
|
|
236
|
+
'${const JsonEncoder.withIndent(' ').convert(json)}\n',
|
|
237
|
+
);
|
|
238
238
|
return ArchiveFile(name, bytes.length, bytes);
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -271,7 +271,8 @@ ArchiveFile? _findFile(Archive archive, String name) {
|
|
|
271
271
|
|
|
272
272
|
void _writeJson(File file, Map<String, dynamic> json) {
|
|
273
273
|
file.writeAsStringSync(
|
|
274
|
-
|
|
274
|
+
'${const JsonEncoder.withIndent(' ').convert(json)}\n',
|
|
275
|
+
);
|
|
275
276
|
}
|
|
276
277
|
|
|
277
278
|
void _validateArchiveRelativePath(String path, String label) {
|
|
@@ -429,4 +430,4 @@ class _FlutterStandardMessageCodec {
|
|
|
429
430
|
throw PackException('truncated AssetManifest.bin');
|
|
430
431
|
}
|
|
431
432
|
}
|
|
432
|
-
}
|
|
433
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/// Bootstraps a fresh PocketBase data directory with the flutter_ota_kit
|
|
2
|
+
/// schema defaults (hooks, settings) the first time PB is started.
|
|
3
|
+
library;
|
|
4
|
+
|
|
5
|
+
import 'dart:io';
|
|
6
|
+
|
|
7
|
+
import 'package:path/path.dart' as p;
|
|
8
|
+
|
|
9
|
+
/// Copies the bundled hooks into `pb_data/pb_hooks/` so PB picks them up on
|
|
10
|
+
/// the next `serve`. Existing files are left alone so user edits are kept.
|
|
11
|
+
class PocketBaseDataBootstrap {
|
|
12
|
+
const PocketBaseDataBootstrap({
|
|
13
|
+
required this.dataDir,
|
|
14
|
+
required this.hooksSourceDir,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
final Directory dataDir;
|
|
18
|
+
final Directory hooksSourceDir;
|
|
19
|
+
|
|
20
|
+
Future<List<String>> install() async {
|
|
21
|
+
await dataDir.create(recursive: true);
|
|
22
|
+
final hooksDir = Directory(p.join(dataDir.path, 'pb_hooks'));
|
|
23
|
+
await hooksDir.create(recursive: true);
|
|
24
|
+
final installed = <String>[];
|
|
25
|
+
if (!hooksSourceDir.existsSync()) {
|
|
26
|
+
return installed;
|
|
27
|
+
}
|
|
28
|
+
for (final entry in hooksSourceDir.listSync()) {
|
|
29
|
+
if (entry is! File) continue;
|
|
30
|
+
if (!entry.path.endsWith('.pb.js')) continue;
|
|
31
|
+
final out = File(p.join(hooksDir.path, p.basename(entry.path)));
|
|
32
|
+
if (!await out.exists()) {
|
|
33
|
+
await entry.copy(out.path);
|
|
34
|
+
installed.add(p.basename(entry.path));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return installed;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/// <reference path="../pb_data/types.d.ts" />
|
|
2
|
+
|
|
3
|
+
// PocketBase JS hook: validate and enrich bundle records on create.
|
|
4
|
+
// Runs inside the PB Go binary's embedded JS VM (goja). Use $app.dao() to
|
|
5
|
+
// query, $app.settings() to read config, $apis.request() to call webhooks.
|
|
6
|
+
|
|
7
|
+
/** @type {OnRecordCreateRequestHandler} */
|
|
8
|
+
onRecordCreateRequest((e) => {
|
|
9
|
+
// Reject bundles that look malformed.
|
|
10
|
+
const b = e.record;
|
|
11
|
+
if (!b.get("channel") || !b.get("platform") || !b.get("file_hash")) {
|
|
12
|
+
throw new BadRequestError("channel, platform and file_hash are required");
|
|
13
|
+
}
|
|
14
|
+
if (b.getString("platform").length > 16) {
|
|
15
|
+
throw new BadRequestError("platform must be <= 16 chars");
|
|
16
|
+
}
|
|
17
|
+
if (b.getInt("rollout_cohort_count") > 1000) {
|
|
18
|
+
throw new BadRequestError("rollout_cohort_count must be 0..1000");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Append to the audit log.
|
|
22
|
+
const audit = $app.dao().findCollectionByNameOrId("audit_log");
|
|
23
|
+
const row = new Record(audit, {
|
|
24
|
+
action: "deploy",
|
|
25
|
+
bundle_id: b.get("id"),
|
|
26
|
+
channel: b.get("channel"),
|
|
27
|
+
details: JSON.stringify({
|
|
28
|
+
platform: b.get("platform"),
|
|
29
|
+
message: b.get("message"),
|
|
30
|
+
should_force_update: b.getBool("should_force_update"),
|
|
31
|
+
}),
|
|
32
|
+
});
|
|
33
|
+
$app.dao().save(row);
|
|
34
|
+
|
|
35
|
+
e.next();
|
|
36
|
+
}, "bundles");
|
|
37
|
+
|
|
38
|
+
/** @type {OnRecordUpdateRequestHandler} */
|
|
39
|
+
onRecordUpdateRequest((e) => {
|
|
40
|
+
const b = e.record;
|
|
41
|
+
if (b.getInt("rollout_cohort_count") > 1000) {
|
|
42
|
+
throw new BadRequestError("rollout_cohort_count must be 0..1000");
|
|
43
|
+
}
|
|
44
|
+
e.next();
|
|
45
|
+
}, "bundles");
|
|
46
|
+
|
|
47
|
+
/** @type {OnRecordDeleteRequestHandler} */
|
|
48
|
+
onRecordDeleteRequest((e) => {
|
|
49
|
+
const audit = $app.dao().findCollectionByNameOrId("audit_log");
|
|
50
|
+
const row = new Record(audit, {
|
|
51
|
+
action: "delete_bundle",
|
|
52
|
+
bundle_id: e.record.get("id"),
|
|
53
|
+
});
|
|
54
|
+
$app.dao().save(row);
|
|
55
|
+
e.next();
|
|
56
|
+
}, "bundles");
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/// Manages the PocketBase server binary — download, version, run, upgrade.
|
|
2
|
+
///
|
|
3
|
+
/// PocketBase is a single Go binary (~15MB) that the CLI can download
|
|
4
|
+
/// automatically on first run. The binary is cached in
|
|
5
|
+
/// `~/.flutter_ota_kit/pocketbase/` so subsequent `serve` invocations are
|
|
6
|
+
/// instant.
|
|
7
|
+
///
|
|
8
|
+
/// Supported platforms: linux/amd64, linux/arm64, macos/amd64, macos/arm64,
|
|
9
|
+
/// windows/amd64. Other combinations fall back to a helpful error.
|
|
10
|
+
library;
|
|
11
|
+
|
|
12
|
+
import 'dart:async';
|
|
13
|
+
import 'dart:io';
|
|
14
|
+
|
|
15
|
+
import 'package:archive/archive.dart';
|
|
16
|
+
import 'package:http/http.dart' as http;
|
|
17
|
+
import 'package:path/path.dart' as p;
|
|
18
|
+
|
|
19
|
+
/// Default PocketBase version managed by the CLI. Bumped in lockstep with
|
|
20
|
+
/// the rest of the stack.
|
|
21
|
+
const String kDefaultPocketBaseVersion = '0.22.21';
|
|
22
|
+
|
|
23
|
+
/// Where the binary + extracted files live.
|
|
24
|
+
class PocketBaseInstallPaths {
|
|
25
|
+
const PocketBaseInstallPaths({
|
|
26
|
+
required this.installDir,
|
|
27
|
+
required this.binaryPath,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/// `~/.flutter_ota_kit/pocketbase/<version>/`
|
|
31
|
+
final Directory installDir;
|
|
32
|
+
|
|
33
|
+
/// `~/.flutter_ota_kit/pocketbase/<version>/pocketbase`
|
|
34
|
+
/// (or `pocketbase.exe` on Windows).
|
|
35
|
+
final File binaryPath;
|
|
36
|
+
|
|
37
|
+
String get version => p.basename(installDir.path);
|
|
38
|
+
|
|
39
|
+
static PocketBaseInstallPaths resolve({
|
|
40
|
+
String? version = kDefaultPocketBaseVersion,
|
|
41
|
+
Directory? root,
|
|
42
|
+
}) {
|
|
43
|
+
final base =
|
|
44
|
+
root ??
|
|
45
|
+
Directory(
|
|
46
|
+
p.join(
|
|
47
|
+
Platform.environment['HOME'] ??
|
|
48
|
+
Platform.environment['USERPROFILE'] ??
|
|
49
|
+
'.',
|
|
50
|
+
'.flutter_ota_kit',
|
|
51
|
+
'pocketbase',
|
|
52
|
+
version!,
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
final exe = Platform.isWindows ? 'pocketbase.exe' : 'pocketbase';
|
|
56
|
+
return PocketBaseInstallPaths(
|
|
57
|
+
installDir: base,
|
|
58
|
+
binaryPath: File(p.join(base.path, exe)),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class _OsArch {
|
|
64
|
+
const _OsArch(this.os, this.arch, this.assetSuffix);
|
|
65
|
+
final String os;
|
|
66
|
+
final String arch;
|
|
67
|
+
final String assetSuffix;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_OsArch _detectOsArch() {
|
|
71
|
+
// Map Platform.operatingSystem + Platform.numberOfProcessors to a PB asset.
|
|
72
|
+
// PB asset names: linux_amd64, linux_arm64, darwin_amd64, darwin_arm64,
|
|
73
|
+
// windows_amd64.
|
|
74
|
+
final os = Platform.operatingSystem;
|
|
75
|
+
String assetOs;
|
|
76
|
+
if (os == 'linux') {
|
|
77
|
+
assetOs = 'linux';
|
|
78
|
+
} else if (os == 'macos') {
|
|
79
|
+
assetOs = 'darwin';
|
|
80
|
+
} else if (os == 'windows') {
|
|
81
|
+
assetOs = 'windows';
|
|
82
|
+
} else {
|
|
83
|
+
throw StateError('Unsupported OS for PocketBase: $os');
|
|
84
|
+
}
|
|
85
|
+
// Dart on ARM Macs returns 'macos' with arm64 in the version string; we
|
|
86
|
+
// detect arm64 via the Dart version since `Platform` doesn't expose arch
|
|
87
|
+
// directly in stable Dart.
|
|
88
|
+
final dartVersion = Platform.version.toLowerCase();
|
|
89
|
+
final isArm =
|
|
90
|
+
dartVersion.contains('arm64') || dartVersion.contains('aarch64');
|
|
91
|
+
final arch = isArm ? 'arm64' : 'amd64';
|
|
92
|
+
return _OsArch(os, arch, '${assetOs}_$arch');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Outcome of [PocketBaseInstaller.ensureInstalled].
|
|
96
|
+
class PocketBaseInstallResult {
|
|
97
|
+
const PocketBaseInstallResult({
|
|
98
|
+
required this.paths,
|
|
99
|
+
required this.alreadyInstalled,
|
|
100
|
+
required this.bytesDownloaded,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
final PocketBaseInstallPaths paths;
|
|
104
|
+
final bool alreadyInstalled;
|
|
105
|
+
final int bytesDownloaded;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Downloads, extracts, and verifies the PocketBase binary.
|
|
109
|
+
class PocketBaseInstaller {
|
|
110
|
+
PocketBaseInstaller({http.Client? httpClient, String? version})
|
|
111
|
+
: _http = httpClient ?? http.Client(),
|
|
112
|
+
_version = version ?? kDefaultPocketBaseVersion;
|
|
113
|
+
|
|
114
|
+
final http.Client _http;
|
|
115
|
+
final String _version;
|
|
116
|
+
|
|
117
|
+
/// Returns the install paths and the binary is guaranteed to exist on disk.
|
|
118
|
+
Future<PocketBaseInstallResult> ensureInstalled({
|
|
119
|
+
PocketBaseInstallPaths? paths,
|
|
120
|
+
void Function(double progress)? onProgress,
|
|
121
|
+
}) async {
|
|
122
|
+
final install = paths ?? PocketBaseInstallPaths.resolve(version: _version);
|
|
123
|
+
if (await install.binaryPath.exists()) {
|
|
124
|
+
return PocketBaseInstallResult(
|
|
125
|
+
paths: install,
|
|
126
|
+
alreadyInstalled: true,
|
|
127
|
+
bytesDownloaded: 0,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
await install.installDir.create(recursive: true);
|
|
131
|
+
|
|
132
|
+
final osArch = _detectOsArch();
|
|
133
|
+
final url = Uri.parse(
|
|
134
|
+
'https://github.com/pocketbase/pocketbase/releases/download/'
|
|
135
|
+
'v$_version/pocketbase_${_version}_${osArch.assetSuffix}.zip',
|
|
136
|
+
);
|
|
137
|
+
final req = http.Request('GET', url);
|
|
138
|
+
final streamed = await _http.send(req);
|
|
139
|
+
if (streamed.statusCode != 200) {
|
|
140
|
+
throw StateError(
|
|
141
|
+
'Failed to download PocketBase v$_version for ${osArch.assetSuffix}: '
|
|
142
|
+
'HTTP ${streamed.statusCode}. Check your internet connection or set '
|
|
143
|
+
'--pb-version to a known-good release.',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
final bytes = <int>[];
|
|
147
|
+
var received = 0;
|
|
148
|
+
final total = streamed.contentLength ?? 0;
|
|
149
|
+
await for (final chunk in streamed.stream) {
|
|
150
|
+
bytes.addAll(chunk);
|
|
151
|
+
received += chunk.length;
|
|
152
|
+
if (onProgress != null && total > 0) {
|
|
153
|
+
onProgress(received / total);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
final zipPath = File(p.join(install.installDir.path, 'pocketbase.zip'))
|
|
157
|
+
..writeAsBytesSync(bytes);
|
|
158
|
+
|
|
159
|
+
// Extract just the binary (skip the README/CHANGELOG that ship in the
|
|
160
|
+
// archive to keep the install dir tidy).
|
|
161
|
+
final archive = ZipDecoder().decodeBytes(bytes);
|
|
162
|
+
for (final entry in archive) {
|
|
163
|
+
if (!entry.isFile) continue;
|
|
164
|
+
final entryName = entry.name.split('/').last;
|
|
165
|
+
if (entryName == 'pocketbase' || entryName == 'pocketbase.exe') {
|
|
166
|
+
final out = File(p.join(install.installDir.path, entryName));
|
|
167
|
+
out.writeAsBytesSync(entry.content as List<int>);
|
|
168
|
+
if (!Platform.isWindows) {
|
|
169
|
+
Process.runSync('chmod', ['+x', out.path]);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
await zipPath.delete();
|
|
175
|
+
} catch (_) {
|
|
176
|
+
// best-effort cleanup
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return PocketBaseInstallResult(
|
|
180
|
+
paths: install,
|
|
181
|
+
alreadyInstalled: false,
|
|
182
|
+
bytesDownloaded: bytes.length,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Resolves the install paths without downloading.
|
|
187
|
+
PocketBaseInstallPaths paths() =>
|
|
188
|
+
PocketBaseInstallPaths.resolve(version: _version);
|
|
189
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/// Spawns the PocketBase binary as a child process, capturing its stdout/stderr
|
|
2
|
+
/// and providing clean start/stop semantics.
|
|
3
|
+
///
|
|
4
|
+
/// PB reads its data directory from the `--dir` flag (default `./pb_data`).
|
|
5
|
+
/// The CLI's installer pre-creates that directory and copies hooks into
|
|
6
|
+
/// `pb_data/pb_hooks/` before the first start.
|
|
7
|
+
library;
|
|
8
|
+
|
|
9
|
+
import 'dart:async';
|
|
10
|
+
import 'dart:io';
|
|
11
|
+
|
|
12
|
+
import 'package:path/path.dart' as p;
|
|
13
|
+
|
|
14
|
+
class PocketBaseProcess {
|
|
15
|
+
PocketBaseProcess._(this.process, this.dataDir, this.binaryPath);
|
|
16
|
+
|
|
17
|
+
final Process process;
|
|
18
|
+
final Directory dataDir;
|
|
19
|
+
final File binaryPath;
|
|
20
|
+
|
|
21
|
+
bool get isRunning => true; // Process instance is always present after start.
|
|
22
|
+
|
|
23
|
+
int get pid => process.pid;
|
|
24
|
+
|
|
25
|
+
Future<int> get exitCode => process.exitCode;
|
|
26
|
+
|
|
27
|
+
/// Returns the stdout/stderr streams merged.
|
|
28
|
+
Stream<List<int>> get output => process.stdout;
|
|
29
|
+
Stream<List<int>> get errors => process.stderr;
|
|
30
|
+
|
|
31
|
+
/// Send SIGINT (Ctrl+C) for a graceful shutdown, then SIGKILL if needed.
|
|
32
|
+
Future<void> stop({Duration timeout = const Duration(seconds: 5)}) async {
|
|
33
|
+
if (Platform.isWindows) {
|
|
34
|
+
process.kill();
|
|
35
|
+
} else {
|
|
36
|
+
process.kill(ProcessSignal.sigint);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await process.exitCode.timeout(timeout);
|
|
40
|
+
} on TimeoutException {
|
|
41
|
+
process.kill(ProcessSignal.sigkill);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class PocketBaseProcessManager {
|
|
47
|
+
PocketBaseProcessManager({
|
|
48
|
+
required this.binaryPath,
|
|
49
|
+
required this.dataDir,
|
|
50
|
+
this.port = 8090,
|
|
51
|
+
this.host = '127.0.0.1',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
final File binaryPath;
|
|
55
|
+
final Directory dataDir;
|
|
56
|
+
final int port;
|
|
57
|
+
final String host;
|
|
58
|
+
|
|
59
|
+
PocketBaseProcess? _process;
|
|
60
|
+
PocketBaseProcess? get process => _process;
|
|
61
|
+
|
|
62
|
+
/// Start PB in the background. Returns once PB is listening (or after a
|
|
63
|
+
/// short timeout waiting for it to come up).
|
|
64
|
+
Future<PocketBaseProcess> start({
|
|
65
|
+
Map<String, String>? env,
|
|
66
|
+
String? adminEmail,
|
|
67
|
+
String? adminPassword,
|
|
68
|
+
Duration readyTimeout = const Duration(seconds: 15),
|
|
69
|
+
}) async {
|
|
70
|
+
if (_process != null) {
|
|
71
|
+
throw StateError('PocketBase is already running (pid ${_process!.pid})');
|
|
72
|
+
}
|
|
73
|
+
if (!await binaryPath.exists()) {
|
|
74
|
+
throw StateError(
|
|
75
|
+
'PocketBase binary not found at ${binaryPath.path}. '
|
|
76
|
+
'Run `flutter_ota_kit pocketbase install` first.',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
await dataDir.create(recursive: true);
|
|
80
|
+
final hooksDir = Directory(p.join(dataDir.path, 'pb_hooks'));
|
|
81
|
+
await hooksDir.create(recursive: true);
|
|
82
|
+
|
|
83
|
+
final mergedEnv = <String, String>{
|
|
84
|
+
...Platform.environment,
|
|
85
|
+
if (adminEmail != null) 'PB_ADMIN_EMAIL': adminEmail,
|
|
86
|
+
if (adminPassword != null) 'PB_ADMIN_PASSWORD': adminPassword,
|
|
87
|
+
...?env,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
final proc = await Process.start(
|
|
91
|
+
binaryPath.path,
|
|
92
|
+
['serve', '--http=${host}:$port', '--dir=${dataDir.path}'],
|
|
93
|
+
environment: mergedEnv,
|
|
94
|
+
mode: ProcessStartMode.detached,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Wait for the health endpoint to come up (best-effort; fall through
|
|
98
|
+
// after readyTimeout either way).
|
|
99
|
+
final deadline = DateTime.now().add(readyTimeout);
|
|
100
|
+
while (DateTime.now().isBefore(deadline)) {
|
|
101
|
+
try {
|
|
102
|
+
final socket = await Socket.connect(
|
|
103
|
+
host,
|
|
104
|
+
port,
|
|
105
|
+
timeout: const Duration(milliseconds: 250),
|
|
106
|
+
);
|
|
107
|
+
socket.destroy();
|
|
108
|
+
break;
|
|
109
|
+
} catch (_) {
|
|
110
|
+
await Future<void>.delayed(const Duration(milliseconds: 200));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
_process = PocketBaseProcess._(proc, dataDir, binaryPath);
|
|
114
|
+
return _process!;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Future<void> stop() async {
|
|
118
|
+
final proc = _process;
|
|
119
|
+
if (proc == null) return;
|
|
120
|
+
await proc.stop();
|
|
121
|
+
_process = null;
|
|
122
|
+
}
|
|
123
|
+
}
|