@_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,140 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
3
|
+
import 'package:args/args.dart';
|
|
4
|
+
import 'package:args/command_runner.dart';
|
|
5
|
+
|
|
6
|
+
import '../backend.dart';
|
|
7
|
+
import '../cli_base.dart';
|
|
8
|
+
import '../config.dart';
|
|
9
|
+
import '../operations.dart';
|
|
10
|
+
|
|
11
|
+
/// `flutter_patcher channel` — manage channels.
|
|
12
|
+
class ChannelCommand extends Command<int> {
|
|
13
|
+
final FlutterPatcherConfig? config;
|
|
14
|
+
final Backend? backendOverride;
|
|
15
|
+
|
|
16
|
+
ChannelCommand({this.config, this.backendOverride}) {
|
|
17
|
+
addSubcommand(ChannelListCommand(config: config, backendOverride: backendOverride));
|
|
18
|
+
addSubcommand(ChannelGetCommand(config: config, backendOverride: backendOverride));
|
|
19
|
+
addSubcommand(ChannelSetCommand(config: config, backendOverride: backendOverride));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
String get name => 'channel';
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
String get description => 'Manage channels (list / get / set).';
|
|
27
|
+
|
|
28
|
+
@override
|
|
29
|
+
Future<int> run() => runGuarded(() async {
|
|
30
|
+
print(description);
|
|
31
|
+
print('');
|
|
32
|
+
print('Subcommands:');
|
|
33
|
+
print(' list List channels');
|
|
34
|
+
print(' get <channel> Show the live bundle for a channel');
|
|
35
|
+
print(' set <c> <id> Promote a bundle to a channel');
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class ChannelListCommand extends Command<int> {
|
|
40
|
+
ChannelListCommand({this.config, this.backendOverride});
|
|
41
|
+
|
|
42
|
+
final FlutterPatcherConfig? config;
|
|
43
|
+
final Backend? backendOverride;
|
|
44
|
+
|
|
45
|
+
@override
|
|
46
|
+
String get name => 'list';
|
|
47
|
+
|
|
48
|
+
@override
|
|
49
|
+
String get description => 'List channels.';
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
ArgParser get argParser => ArgParser()
|
|
53
|
+
..addOption('backend', abbr: 'b', help: 'Backend provider.');
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
Future<int> run() => runGuarded(() async {
|
|
57
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
58
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
59
|
+
final channels = await listChannels(backend);
|
|
60
|
+
if (channels.isEmpty) {
|
|
61
|
+
stdout.writeln('(no channels)');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (final c in channels) {
|
|
65
|
+
stdout.writeln(' $c');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class ChannelGetCommand extends Command<int> {
|
|
71
|
+
ChannelGetCommand({this.config, this.backendOverride});
|
|
72
|
+
|
|
73
|
+
final FlutterPatcherConfig? config;
|
|
74
|
+
final Backend? backendOverride;
|
|
75
|
+
|
|
76
|
+
@override
|
|
77
|
+
String get name => 'get';
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
String get description => 'Show the live (enabled) bundle for a channel.';
|
|
81
|
+
|
|
82
|
+
@override
|
|
83
|
+
ArgParser get argParser => ArgParser()
|
|
84
|
+
..addOption('backend', abbr: 'b', help: 'Backend provider.')
|
|
85
|
+
..addOption('channel', help: 'Channel.');
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
Future<int> run() => runGuarded(() async {
|
|
89
|
+
final channel = argResults!['channel'] as String?;
|
|
90
|
+
if (channel == null || channel.isEmpty) {
|
|
91
|
+
throw StateError('Usage: flutter_patcher channel get <channel>');
|
|
92
|
+
}
|
|
93
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
94
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
95
|
+
final bundle = await getChannel(backend, channel);
|
|
96
|
+
if (bundle == null) {
|
|
97
|
+
stdout.writeln('(no live bundle on channel "$channel")');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
stdout.writeln('channel "$channel" -> bundle ${bundle.id} '
|
|
101
|
+
'(${bundle.platform.value})');
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class ChannelSetCommand extends Command<int> {
|
|
106
|
+
ChannelSetCommand({this.config, this.backendOverride});
|
|
107
|
+
|
|
108
|
+
final FlutterPatcherConfig? config;
|
|
109
|
+
final Backend? backendOverride;
|
|
110
|
+
|
|
111
|
+
@override
|
|
112
|
+
String get name => 'set';
|
|
113
|
+
|
|
114
|
+
@override
|
|
115
|
+
String get description => 'Promote a bundle to a channel.';
|
|
116
|
+
|
|
117
|
+
@override
|
|
118
|
+
ArgParser get argParser => ArgParser()
|
|
119
|
+
..addOption('backend', abbr: 'b', help: 'Backend provider.')
|
|
120
|
+
..addOption('channel', help: 'Channel.')
|
|
121
|
+
..addOption('bundle-id', help: 'Bundle id.');
|
|
122
|
+
|
|
123
|
+
@override
|
|
124
|
+
Future<int> run() => runGuarded(() async {
|
|
125
|
+
final channel = argResults!['channel'] as String?;
|
|
126
|
+
final bundleId = argResults!['bundle-id'] as String?;
|
|
127
|
+
if (channel == null ||
|
|
128
|
+
channel.isEmpty ||
|
|
129
|
+
bundleId == null ||
|
|
130
|
+
bundleId.isEmpty) {
|
|
131
|
+
throw StateError(
|
|
132
|
+
'Usage: flutter_patcher channel set --channel <c> --bundle-id <id>',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
136
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
137
|
+
await promoteBundle(backend, bundleId, channel);
|
|
138
|
+
stdout.writeln('Channel "$channel" -> bundle $bundleId');
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import 'dart:convert';
|
|
2
|
+
import 'dart:io';
|
|
3
|
+
|
|
4
|
+
import 'package:args/args.dart';
|
|
5
|
+
import 'package:args/command_runner.dart';
|
|
6
|
+
|
|
7
|
+
import '../cli_base.dart';
|
|
8
|
+
import '../config.dart';
|
|
9
|
+
|
|
10
|
+
/// `flutter_patcher config` — get/set/list config values.
|
|
11
|
+
class ConfigCommand extends Command<int> {
|
|
12
|
+
ConfigCommand() {
|
|
13
|
+
addSubcommand(ConfigGetCommand());
|
|
14
|
+
addSubcommand(ConfigSetCommand());
|
|
15
|
+
addSubcommand(ConfigListCommand());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
String get name => 'config';
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
String get description => 'Get, set, or list configuration values.';
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
Future<int> run() => runGuarded(() async {
|
|
26
|
+
// No subcommand selected.
|
|
27
|
+
print(description);
|
|
28
|
+
print(''); print('Subcommands:'); print(' get <key>'); print(' set <key> <value>'); print(' list');
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Map<String, dynamic> _loadProjectJson() {
|
|
33
|
+
final file = configCandidates().first;
|
|
34
|
+
if (!file.existsSync()) return <String, dynamic>{};
|
|
35
|
+
final raw = file.readAsStringSync();
|
|
36
|
+
if (raw.trim().isEmpty) return <String, dynamic>{};
|
|
37
|
+
return jsonDecode(raw) as Map<String, dynamic>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
void _saveProjectJson(Map<String, dynamic> json) {
|
|
41
|
+
final file = configCandidates().first;
|
|
42
|
+
file.parent.createSync(recursive: true);
|
|
43
|
+
file.writeAsStringSync(
|
|
44
|
+
const JsonEncoder.withIndent(' ').convert(json),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class ConfigGetCommand extends Command<int> {
|
|
49
|
+
@override
|
|
50
|
+
String get name => 'get';
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
String get description => 'Print a config value by dot-path (e.g. supabase.url).';
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
ArgParser get argParser => ArgParser()..addOption('key', help: 'Dot-path key.');
|
|
57
|
+
|
|
58
|
+
@override
|
|
59
|
+
Future<int> run() => runGuarded(() async {
|
|
60
|
+
final key = argResults!['key'] as String?;
|
|
61
|
+
if (key == null || key.isEmpty) {
|
|
62
|
+
throw StateError('Usage: flutter_patcher config get <key>');
|
|
63
|
+
}
|
|
64
|
+
final value = readPath(_loadProjectJson(), key);
|
|
65
|
+
if (value == null) {
|
|
66
|
+
stderr.writeln('(not set)');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
stdout.writeln(
|
|
70
|
+
value is String ? value : const JsonEncoder().convert(value),
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class ConfigSetCommand extends Command<int> {
|
|
76
|
+
@override
|
|
77
|
+
String get name => 'set';
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
String get description => 'Set a config value by dot-path.';
|
|
81
|
+
|
|
82
|
+
@override
|
|
83
|
+
ArgParser get argParser => ArgParser()
|
|
84
|
+
..addOption('key', help: 'Dot-path key.')
|
|
85
|
+
..addOption('value', help: 'Value.');
|
|
86
|
+
|
|
87
|
+
@override
|
|
88
|
+
Future<int> run() => runGuarded(() async {
|
|
89
|
+
final key = argResults!['key'] as String?;
|
|
90
|
+
final value = argResults!['value'] as String?;
|
|
91
|
+
if (key == null || key.isEmpty || value == null) {
|
|
92
|
+
throw StateError('Usage: flutter_patcher config set <key> <value>');
|
|
93
|
+
}
|
|
94
|
+
final json = _loadProjectJson();
|
|
95
|
+
writePath(json, key, value);
|
|
96
|
+
_saveProjectJson(json);
|
|
97
|
+
stdout.writeln('Set $key = $value');
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
class ConfigListCommand extends Command<int> {
|
|
102
|
+
@override
|
|
103
|
+
String get name => 'list';
|
|
104
|
+
|
|
105
|
+
@override
|
|
106
|
+
String get description => 'Print the full config.';
|
|
107
|
+
|
|
108
|
+
@override
|
|
109
|
+
Future<int> run() => runGuarded(() async {
|
|
110
|
+
final json = _loadProjectJson();
|
|
111
|
+
stdout.writeln(const JsonEncoder.withIndent(' ').convert(json));
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
3
|
+
import 'package:args/args.dart';
|
|
4
|
+
import 'package:args/command_runner.dart';
|
|
5
|
+
import 'package:path/path.dart' as p;
|
|
6
|
+
|
|
7
|
+
import '../cli_base.dart';
|
|
8
|
+
|
|
9
|
+
/// `flutter_patcher console` — open the web console.
|
|
10
|
+
class ConsoleCommand extends Command<int> {
|
|
11
|
+
@override
|
|
12
|
+
String get name => 'console';
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
String get description => 'Open the flutter_patcher web console.';
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
ArgParser get argParser => ArgParser()
|
|
19
|
+
..addFlag('open',
|
|
20
|
+
help: 'Launch the console with `flutter run -d chrome` (requires Flutter).');
|
|
21
|
+
|
|
22
|
+
Directory? _findConsoleDir() {
|
|
23
|
+
var dir = Directory(p.dirname(Platform.script.path));
|
|
24
|
+
while (dir.path != dir.parent.path) {
|
|
25
|
+
final candidate = Directory(p.join(dir.path, 'packages', 'console'));
|
|
26
|
+
if (candidate.existsSync()) return candidate;
|
|
27
|
+
dir = dir.parent;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
Future<int> run() => runGuarded(() async {
|
|
34
|
+
final consoleDir = _findConsoleDir();
|
|
35
|
+
if (consoleDir == null) {
|
|
36
|
+
stdout.writeln('Console package not found (expected packages/console).');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (argResults!['open'] as bool) {
|
|
40
|
+
stdout.writeln('Launching console...');
|
|
41
|
+
final process = await Process.start(
|
|
42
|
+
'flutter',
|
|
43
|
+
['run', '-d', 'chrome'],
|
|
44
|
+
workingDirectory: consoleDir.path,
|
|
45
|
+
runInShell: true,
|
|
46
|
+
mode: ProcessStartMode.inheritStdio,
|
|
47
|
+
);
|
|
48
|
+
exitCode = await process.exitCode;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
stdout.writeln('flutter_patcher web console:');
|
|
52
|
+
stdout.writeln(' cd ${consoleDir.path}');
|
|
53
|
+
stdout.writeln(' flutter run -d chrome');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
3
|
+
import 'package:args/args.dart';
|
|
4
|
+
import 'package:args/command_runner.dart';
|
|
5
|
+
|
|
6
|
+
import '../backend.dart';
|
|
7
|
+
import '../cli_base.dart';
|
|
8
|
+
import '../config.dart';
|
|
9
|
+
import '../operations.dart';
|
|
10
|
+
import '../util.dart';
|
|
11
|
+
|
|
12
|
+
/// `flutter_patcher deploy` — zip + upload + register a new bundle.
|
|
13
|
+
class DeployCommand extends Command<int> {
|
|
14
|
+
DeployCommand({this.config, this.backendOverride});
|
|
15
|
+
|
|
16
|
+
final FlutterPatcherConfig? config;
|
|
17
|
+
final Backend? backendOverride;
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
String get name => 'deploy';
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
String get description =>
|
|
24
|
+
'Zip a source directory, upload it, and register a new bundle.';
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
ArgParser get argParser => ArgParser()
|
|
28
|
+
..addOption('backend',
|
|
29
|
+
abbr: 'b', help: 'Backend provider (supabase/postgres/cloudflare/aws).')
|
|
30
|
+
..addOption('source', abbr: 's', help: 'Source directory to zip + upload.')
|
|
31
|
+
..addOption('channel', abbr: 'c', help: 'Target channel.')
|
|
32
|
+
..addOption('platform', abbr: 'p', defaultsTo: 'android', help: 'Platform.')
|
|
33
|
+
..addOption('message', abbr: 'm', help: 'Release message.')
|
|
34
|
+
..addFlag('force', help: 'Force the update on clients.')
|
|
35
|
+
..addOption('target-app-version',
|
|
36
|
+
help: 'Semver range target (XOR with fingerprint-hash).')
|
|
37
|
+
..addOption('fingerprint-hash',
|
|
38
|
+
help: 'Fingerprint hash target (XOR with target-app-version).')
|
|
39
|
+
..addOption('key', help: 'Path to Ed25519 private key file (sign bundle).')
|
|
40
|
+
..addOption('git-commit-hash', help: 'Git commit hash (auto-detected).')
|
|
41
|
+
..addOption('bundle-id', help: 'Explicit bundle id (uuidv7 by default).');
|
|
42
|
+
|
|
43
|
+
@override
|
|
44
|
+
Future<int> run() => runGuarded(() async {
|
|
45
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
46
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
47
|
+
final source = argResults!['source'] as String? ?? cfg?.source ?? './dist';
|
|
48
|
+
final channel =
|
|
49
|
+
argResults!['channel'] as String? ?? cfg?.channel ?? 'production';
|
|
50
|
+
final platform =
|
|
51
|
+
argResults!['platform'] as String? ?? cfg?.platform ?? 'android';
|
|
52
|
+
final message = argResults!['message'] as String?;
|
|
53
|
+
final force = argResults!['force'] as bool;
|
|
54
|
+
final targetAppVersion = argResults!['target-app-version'] as String?;
|
|
55
|
+
final fingerprintHash = argResults!['fingerprint-hash'] as String?;
|
|
56
|
+
final keyPath = argResults!['key'] as String?;
|
|
57
|
+
String? signingKey;
|
|
58
|
+
if (keyPath != null) signingKey = File(keyPath).readAsStringSync().trim();
|
|
59
|
+
final resolvedGitCommitHash =
|
|
60
|
+
argResults!['git-commit-hash'] as String? ?? await gitCommitHash(source);
|
|
61
|
+
final bundleId = argResults!['bundle-id'] as String?;
|
|
62
|
+
|
|
63
|
+
final bundle = await deployBundle(
|
|
64
|
+
backend,
|
|
65
|
+
DeployOptions(
|
|
66
|
+
source: source,
|
|
67
|
+
channel: channel,
|
|
68
|
+
platform: platform,
|
|
69
|
+
message: message,
|
|
70
|
+
force: force,
|
|
71
|
+
targetAppVersion: targetAppVersion,
|
|
72
|
+
fingerprintHash: fingerprintHash,
|
|
73
|
+
signingKeyBase64: signingKey,
|
|
74
|
+
gitCommitHash: resolvedGitCommitHash,
|
|
75
|
+
bundleId: bundleId,
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
stdout.writeln('Deployed bundle ${bundle.id}');
|
|
80
|
+
stdout.writeln(' channel: ${bundle.channel}');
|
|
81
|
+
stdout.writeln(' platform: ${bundle.platform.value}');
|
|
82
|
+
stdout.writeln(' enabled: ${bundle.enabled}');
|
|
83
|
+
stdout.writeln(' storageUri: ${bundle.storageUri}');
|
|
84
|
+
if (bundle.targetAppVersion != null) {
|
|
85
|
+
stdout.writeln(' targetAppVersion: ${bundle.targetAppVersion}');
|
|
86
|
+
}
|
|
87
|
+
if (bundle.fingerprintHash != null) {
|
|
88
|
+
stdout.writeln(' fingerprintHash: ${bundle.fingerprintHash}');
|
|
89
|
+
}
|
|
90
|
+
if (bundle.gitCommitHash != null) {
|
|
91
|
+
stdout.writeln(' gitCommitHash: ${bundle.gitCommitHash}');
|
|
92
|
+
}
|
|
93
|
+
if (bundle.message != null) stdout.writeln(' message: ${bundle.message}');
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
3
|
+
import 'package:args/args.dart';
|
|
4
|
+
import 'package:args/command_runner.dart';
|
|
5
|
+
|
|
6
|
+
import '../backend.dart';
|
|
7
|
+
import '../cli_base.dart';
|
|
8
|
+
import '../config.dart';
|
|
9
|
+
|
|
10
|
+
/// `flutter_patcher doctor` — environment + backend connectivity check.
|
|
11
|
+
class DoctorCommand extends Command<int> {
|
|
12
|
+
DoctorCommand({this.config, this.backendOverride});
|
|
13
|
+
|
|
14
|
+
final FlutterPatcherConfig? config;
|
|
15
|
+
final Backend? backendOverride;
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
String get name => 'doctor';
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
String get description => 'Diagnose the local environment and backend connection.';
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
ArgParser get argParser => ArgParser()
|
|
25
|
+
..addOption('backend', abbr: 'b', help: 'Backend provider.');
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
Future<int> run() => runGuarded(() async {
|
|
29
|
+
stdout.writeln('flutter_patcher CLI doctor');
|
|
30
|
+
stdout.writeln(' dart: ${Platform.version.split(' ').first}');
|
|
31
|
+
stdout.writeln(' os: ${Platform.operatingSystem} '
|
|
32
|
+
'${Platform.operatingSystemVersion.split('\n').first}');
|
|
33
|
+
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
34
|
+
if (cfg == null) {
|
|
35
|
+
stdout.writeln(' config: not found (run `flutter_patcher init`)');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
stdout.writeln(' config: provider=${cfg.provider} '
|
|
39
|
+
'channel=${cfg.channel} platform=${cfg.platform}');
|
|
40
|
+
try {
|
|
41
|
+
final backend = requireBackend(cfg, override: backendOverride);
|
|
42
|
+
final channels = await backend.db.getChannels();
|
|
43
|
+
stdout.writeln(' backend: reachable (channels: ${channels.join(', ')})');
|
|
44
|
+
} catch (e) {
|
|
45
|
+
stdout.writeln(' backend: unreachable — $e');
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
3
|
+
import 'package:args/args.dart';
|
|
4
|
+
import 'package:args/command_runner.dart';
|
|
5
|
+
|
|
6
|
+
import '../cli_base.dart';
|
|
7
|
+
import '../util.dart';
|
|
8
|
+
|
|
9
|
+
/// `flutter_patcher fingerprint` — compute a build-time fingerprint hash.
|
|
10
|
+
class FingerprintCommand extends Command<int> {
|
|
11
|
+
@override
|
|
12
|
+
String get name => 'fingerprint';
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
String get description =>
|
|
16
|
+
'Compute a deterministic fingerprint hash for a directory (build-time).';
|
|
17
|
+
|
|
18
|
+
@override
|
|
19
|
+
ArgParser get argParser => ArgParser()
|
|
20
|
+
..addOption('source',
|
|
21
|
+
abbr: 's', defaultsTo: './dist', help: 'Directory to fingerprint.');
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
Future<int> run() => runGuarded(() async {
|
|
25
|
+
final source = argResults!['source'] as String;
|
|
26
|
+
final hash = generateFingerprint(source);
|
|
27
|
+
stdout.writeln(hash);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import 'dart:io';
|
|
2
|
+
|
|
3
|
+
import 'package:args/args.dart';
|
|
4
|
+
import 'package:args/command_runner.dart';
|
|
5
|
+
|
|
6
|
+
import '../backend.dart';
|
|
7
|
+
import '../cli_base.dart';
|
|
8
|
+
import '../config.dart';
|
|
9
|
+
|
|
10
|
+
/// `flutter_patcher init` — scaffold a project config file.
|
|
11
|
+
class InitCommand extends Command<int> {
|
|
12
|
+
InitCommand({this.config, this.backendOverride});
|
|
13
|
+
|
|
14
|
+
final FlutterPatcherConfig? config;
|
|
15
|
+
final Backend? backendOverride;
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
String get name => 'init';
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
String get description =>
|
|
22
|
+
'Scaffold a .flutter_patcher.json config for the current project.';
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
ArgParser get argParser => ArgParser()
|
|
26
|
+
..addOption('provider', defaultsTo: 'supabase', help: 'Backend provider.')
|
|
27
|
+
..addOption('url', help: 'Supabase project URL.')
|
|
28
|
+
..addOption('key', help: 'Supabase service role key.')
|
|
29
|
+
..addOption('anon-key', help: 'Supabase anon key.')
|
|
30
|
+
..addOption('bucket', defaultsTo: 'bundles', help: 'Storage bucket.')
|
|
31
|
+
..addOption('base-path', help: 'Storage base path.')
|
|
32
|
+
..addOption('channel', defaultsTo: 'production', help: 'Default channel.')
|
|
33
|
+
..addOption('platform', defaultsTo: 'android', help: 'Default platform.')
|
|
34
|
+
..addOption('source', defaultsTo: './dist', help: 'Default deploy source.')
|
|
35
|
+
..addFlag('force', abbr: 'f', help: 'Overwrite an existing config.');
|
|
36
|
+
|
|
37
|
+
String? _prompt(String label, String? current) {
|
|
38
|
+
if (!stdin.hasTerminal) return current;
|
|
39
|
+
stdout.write('$label${current != null ? ' [$current]' : ''}: ');
|
|
40
|
+
final line = stdin.readLineSync();
|
|
41
|
+
return (line == null || line.trim().isEmpty) ? current : line.trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
Future<int> run() => runGuarded(() async {
|
|
46
|
+
final file = configCandidates().first;
|
|
47
|
+
if (file.existsSync() && !(argResults!['force'] as bool)) {
|
|
48
|
+
throw StateError(
|
|
49
|
+
'Config already exists at ${file.path}. Use --force to overwrite.',
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
final provider = argResults!['provider'] as String;
|
|
54
|
+
final url = _prompt('Supabase URL', argResults!['url'] as String?);
|
|
55
|
+
final key = _prompt(
|
|
56
|
+
'Supabase service role key',
|
|
57
|
+
argResults!['key'] as String?,
|
|
58
|
+
);
|
|
59
|
+
final anonKey = argResults!['anon-key'] as String?;
|
|
60
|
+
final bucket = argResults!['bucket'] as String? ?? 'bundles';
|
|
61
|
+
final basePath = argResults!['base-path'] as String?;
|
|
62
|
+
final channel = argResults!['channel'] as String? ?? 'production';
|
|
63
|
+
final platform = argResults!['platform'] as String? ?? 'android';
|
|
64
|
+
final source = argResults!['source'] as String? ?? './dist';
|
|
65
|
+
|
|
66
|
+
final cfg = FlutterPatcherConfig(
|
|
67
|
+
provider: provider,
|
|
68
|
+
supabase: SupabaseConfigJson(
|
|
69
|
+
url: url,
|
|
70
|
+
serviceRoleKey: key,
|
|
71
|
+
anonKey: anonKey,
|
|
72
|
+
bucket: bucket,
|
|
73
|
+
basePath: basePath,
|
|
74
|
+
),
|
|
75
|
+
channel: channel,
|
|
76
|
+
platform: platform,
|
|
77
|
+
source: source,
|
|
78
|
+
);
|
|
79
|
+
saveConfig(cfg);
|
|
80
|
+
stdout.writeln('Wrote ${file.path}');
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import 'dart:convert';
|
|
2
|
+
import 'dart:io';
|
|
3
|
+
|
|
4
|
+
import 'package:args/args.dart';
|
|
5
|
+
import 'package:args/command_runner.dart';
|
|
6
|
+
|
|
7
|
+
import '../backend.dart';
|
|
8
|
+
import '../cli_base.dart';
|
|
9
|
+
import '../config.dart';
|
|
10
|
+
import '../sign.dart';
|
|
11
|
+
|
|
12
|
+
/// `flutter_patcher keys` — generate an Ed25519 keypair for bundle signing.
|
|
13
|
+
class KeysCommand extends Command<int> {
|
|
14
|
+
KeysCommand({this.config, this.backendOverride});
|
|
15
|
+
|
|
16
|
+
final FlutterPatcherConfig? config;
|
|
17
|
+
final Backend? backendOverride;
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
String get name => 'keys';
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
String get description =>
|
|
24
|
+
'Generate an Ed25519 keypair used to sign deployed bundles.';
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
ArgParser get argParser => ArgParser()
|
|
28
|
+
..addFlag('save', help: 'Persist the public key into the project config.');
|
|
29
|
+
|
|
30
|
+
@override
|
|
31
|
+
Future<int> run() => runGuarded(() async {
|
|
32
|
+
final (privateB64, publicB64) = await generateEd25519KeyPair();
|
|
33
|
+
stdout.writeln('Private key (keep secret, use with `deploy --key`):');
|
|
34
|
+
stdout.writeln(' $privateB64');
|
|
35
|
+
stdout.writeln('');
|
|
36
|
+
stdout.writeln('Public key (safe to embed / configure on device):');
|
|
37
|
+
stdout.writeln(' $publicB64');
|
|
38
|
+
if (argResults!['save'] as bool) {
|
|
39
|
+
final file = configCandidates().first;
|
|
40
|
+
final json = file.existsSync() && file.readAsStringSync().trim().isNotEmpty
|
|
41
|
+
? (jsonDecode(file.readAsStringSync()) as Map<String, dynamic>)
|
|
42
|
+
: <String, dynamic>{};
|
|
43
|
+
writePath(json, 'publicKey', publicB64);
|
|
44
|
+
_save(json);
|
|
45
|
+
stdout.writeln('');
|
|
46
|
+
stdout.writeln('Saved public key to ${file.path}');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
void _save(Map<String, dynamic> json) {
|
|
51
|
+
final file = configCandidates().first;
|
|
52
|
+
file.parent.createSync(recursive: true);
|
|
53
|
+
file.writeAsStringSync(const JsonEncoder.withIndent(' ').convert(json));
|
|
54
|
+
}
|
|
55
|
+
}
|