@_nazmiforreal/flutter-ota 0.1.20 → 0.1.22

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.
Files changed (62) hide show
  1. package/bin/flutter-ota-linux-x64 +0 -0
  2. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +1 -1
  3. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +340 -340
  4. package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +9 -5
  5. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +3 -2
  6. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +106 -31
  7. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +34 -7
  8. package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +12 -4
  9. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +7 -8
  10. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +41 -9
  11. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +124 -13
  12. package/dart-src/packages/cli-tools/lib/src/commands/fingerprint.dart +2 -1
  13. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +58 -28
  14. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +11 -5
  15. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +283 -32
  16. package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +347 -121
  17. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +18 -9
  18. package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +32 -15
  19. package/dart-src/packages/cli-tools/lib/src/config.dart +16 -22
  20. package/dart-src/packages/cli-tools/lib/src/operations.dart +1 -3
  21. package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +26 -17
  22. package/dart-src/packages/cli-tools/lib/src/pocketbase/schema_installer.dart +0 -1
  23. package/dart-src/packages/cli-tools/lib/src/runner.dart +3 -1
  24. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +10 -16
  25. package/dart-src/packages/cli-tools/pubspec.lock +7 -7
  26. package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +7 -3
  27. package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +13 -16
  28. package/dart-src/packages/core/CHANGELOG.md +15 -0
  29. package/dart-src/packages/core/lib/flutter_ota_kit_core.dart +2 -0
  30. package/dart-src/packages/core/lib/src/app_update_info.dart +71 -1
  31. package/dart-src/packages/core/lib/src/bundle.dart +59 -17
  32. package/dart-src/packages/core/lib/src/bundle_artifacts.dart +7 -3
  33. package/dart-src/packages/core/lib/src/bundle_patch_artifact.dart +16 -0
  34. package/dart-src/packages/core/lib/src/changed_asset.dart +33 -3
  35. package/dart-src/packages/core/lib/src/get_bundles_args.dart +24 -2
  36. package/dart-src/packages/core/lib/src/platform.dart +8 -0
  37. package/dart-src/packages/core/lib/src/semver.dart +0 -3
  38. package/dart-src/packages/core/lib/src/status.dart +20 -1
  39. package/dart-src/packages/core/lib/src/strategy.dart +10 -0
  40. package/dart-src/packages/core/lib/src/update_bundle_params.dart +15 -0
  41. package/dart-src/packages/core/lib/src/uuid.dart +11 -0
  42. package/dart-src/packages/core/pubspec.yaml +1 -1
  43. package/dart-src/plugins/aws/CHANGELOG.md +18 -0
  44. package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +0 -1
  45. package/dart-src/plugins/aws/pubspec.yaml +3 -3
  46. package/dart-src/plugins/cloudflare/CHANGELOG.md +18 -0
  47. package/dart-src/plugins/cloudflare/pubspec.yaml +3 -3
  48. package/dart-src/plugins/plugin-core/CHANGELOG.md +15 -0
  49. package/dart-src/plugins/plugin-core/lib/src/asset_storage_layout.dart +3 -0
  50. package/dart-src/plugins/plugin-core/lib/src/parse_storage_uri.dart +17 -0
  51. package/dart-src/plugins/plugin-core/lib/src/types.dart +94 -3
  52. package/dart-src/plugins/plugin-core/pubspec.yaml +2 -2
  53. package/dart-src/plugins/pocketbase/CHANGELOG.md +18 -0
  54. package/dart-src/plugins/pocketbase/lib/src/pocketbase_client.dart +3 -0
  55. package/dart-src/plugins/pocketbase/pubspec.yaml +3 -3
  56. package/dart-src/plugins/postgres/CHANGELOG.md +18 -0
  57. package/dart-src/plugins/postgres/pubspec.yaml +3 -3
  58. package/dart-src/plugins/supabase/CHANGELOG.md +18 -0
  59. package/dart-src/plugins/supabase/pubspec.yaml +3 -3
  60. package/package.json +2 -2
  61. package/scripts/postinstall.js +5 -1
  62. package/bin/flutter-ota.js +0 -36
@@ -1,5 +1,8 @@
1
+ import 'dart:convert';
1
2
  import 'dart:io';
2
3
 
4
+ import 'package:http/http.dart' as http;
5
+
3
6
  import 'package:flutter_ota_kit_cli/flutter_ota_kit_cli.dart';
4
7
 
5
8
  import '../ui/ui.dart';
@@ -7,7 +10,14 @@ import '../ui/ui.dart';
7
10
  /// `flutter-ota doctor` — environment + backend connectivity check.
8
11
  class DoctorCommand extends FlutterPatcherCommand {
9
12
  DoctorCommand({this.config, this.backendOverride}) {
10
- argParser.addOption('backend', abbr: 'b', help: 'Backend provider.');
13
+ final detected = config?.provider ?? loadConfig()?.provider;
14
+ argParser.addOption(
15
+ 'backend',
16
+ abbr: 'b',
17
+ help: detected != null
18
+ ? 'Backend provider [detected: $detected].'
19
+ : 'Backend provider.',
20
+ );
11
21
  }
12
22
 
13
23
  final FlutterPatcherConfig? config;
@@ -41,10 +51,8 @@ class DoctorCommand extends FlutterPatcherCommand {
41
51
  steps.success('Channel ${cfg.channel}');
42
52
  steps.success('Platform ${cfg.platform}');
43
53
 
44
- // PocketBase-specific: show binary + health endpoint status.
45
- if (cfg.provider == 'pocketbase') {
46
- await _checkPocketBase(cfg, steps);
47
- }
54
+ // Backend-specific pre-flight checks.
55
+ await _checkProvider(cfg, steps);
48
56
 
49
57
  try {
50
58
  final backend = requireBackend(cfg, override: backendOverride);
@@ -55,19 +63,120 @@ class DoctorCommand extends FlutterPatcherCommand {
55
63
  final time = ms >= 1000
56
64
  ? '${(ms / 1000).toStringAsFixed(1)}s'
57
65
  : '${ms}ms';
58
- steps.success(
59
- 'Backend reachable — ${channels.join(', ')} in $time',
60
- );
66
+ steps.success('Backend reachable — ${channels.join(', ')} in $time');
61
67
  } catch (e) {
62
68
  steps.fail('Backend unreachable — $e');
63
69
  }
64
70
  steps.summary();
65
71
  });
66
72
 
67
- Future<void> _checkPocketBase(
68
- FlutterPatcherConfig cfg,
69
- Steps steps,
70
- ) async {
73
+ Future<void> _checkProvider(FlutterPatcherConfig cfg, Steps steps) async {
74
+ switch (cfg.provider) {
75
+ case 'supabase':
76
+ await _checkSupabase(cfg, steps);
77
+ case 'postgres':
78
+ await _checkPostgres(cfg, steps);
79
+ case 'cloudflare':
80
+ await _checkCloudflare(cfg, steps);
81
+ case 'aws':
82
+ await _checkAws(cfg, steps);
83
+ case 'pocketbase':
84
+ await _checkPocketBase(cfg, steps);
85
+ default:
86
+ steps.fail('Unknown provider: ${cfg.provider}');
87
+ }
88
+ }
89
+
90
+ Future<void> _checkSupabase(FlutterPatcherConfig cfg, Steps steps) async {
91
+ final url = cfg.supabase.url;
92
+ if (url == null || url.isEmpty) {
93
+ steps.fail('SUPABASE_URL not set');
94
+ return;
95
+ }
96
+ try {
97
+ final res = await http
98
+ .get(Uri.parse('$url/rest/v1/'))
99
+ .timeout(const Duration(seconds: 5));
100
+ if (res.statusCode < 400) {
101
+ steps.success('Supabase reachable ($url)');
102
+ } else {
103
+ steps.fail('Supabase returned ${res.statusCode}');
104
+ }
105
+ } catch (e) {
106
+ steps.fail('Supabase unreachable — $e');
107
+ }
108
+ }
109
+
110
+ Future<void> _checkPostgres(FlutterPatcherConfig cfg, Steps steps) async {
111
+ final host = cfg.postgres.host;
112
+ if (host == null || host.isEmpty) {
113
+ steps.fail('POSTGRES_HOST not set');
114
+ return;
115
+ }
116
+ final port = int.tryParse(cfg.postgres.port ?? '5432') ?? 5432;
117
+ try {
118
+ final socket = await Socket.connect(
119
+ host,
120
+ port,
121
+ ).timeout(const Duration(seconds: 5));
122
+ await socket.close();
123
+ steps.success('Postgres reachable ($host:$port)');
124
+ } catch (e) {
125
+ steps.fail('Postgres unreachable — $e');
126
+ }
127
+ }
128
+
129
+ Future<void> _checkCloudflare(FlutterPatcherConfig cfg, Steps steps) async {
130
+ final accountId = cfg.cloudflare.accountId;
131
+ final apiToken = cfg.cloudflare.apiToken;
132
+ if (accountId == null || accountId.isEmpty) {
133
+ steps.fail('CLOUDFLARE_ACCOUNT_ID not set');
134
+ return;
135
+ }
136
+ if (apiToken == null || apiToken.isEmpty) {
137
+ steps.fail('CLOUDFLARE_API_TOKEN not set');
138
+ return;
139
+ }
140
+ try {
141
+ final res = await http
142
+ .get(
143
+ Uri.parse(
144
+ 'https://api.cloudflare.com/client/v4/accounts/$accountId',
145
+ ),
146
+ headers: {
147
+ 'Authorization': 'Bearer $apiToken',
148
+ 'Content-Type': 'application/json',
149
+ },
150
+ )
151
+ .timeout(const Duration(seconds: 5));
152
+ final body = jsonDecode(res.body) as Map<String, dynamic>;
153
+ if (body['success'] == true) {
154
+ steps.success('Cloudflare account reachable');
155
+ } else {
156
+ final errors = body['errors'] as List? ?? [];
157
+ final msg = errors.isNotEmpty ? errors.first['message'] : res.body;
158
+ steps.fail('Cloudflare API error: $msg');
159
+ }
160
+ } catch (e) {
161
+ steps.fail('Cloudflare unreachable — $e');
162
+ }
163
+ }
164
+
165
+ Future<void> _checkAws(FlutterPatcherConfig cfg, Steps steps) async {
166
+ final bucket = cfg.aws.bucket;
167
+ if (bucket == null || bucket.isEmpty) {
168
+ steps.fail('AWS_BUCKET not set');
169
+ return;
170
+ }
171
+ final region = cfg.aws.region;
172
+ if (region == null || region.isEmpty) {
173
+ steps.fail('AWS_REGION not set');
174
+ return;
175
+ }
176
+ steps.success('AWS S3 bucket "$bucket" in $region');
177
+ }
178
+
179
+ Future<void> _checkPocketBase(FlutterPatcherConfig cfg, Steps steps) async {
71
180
  final paths = PocketBaseInstallPaths.resolve();
72
181
  final installed = await paths.binaryPath.exists();
73
182
  if (installed) {
@@ -79,8 +188,8 @@ class DoctorCommand extends FlutterPatcherCommand {
79
188
  }
80
189
  final url = cfg.pocketbase.url;
81
190
  if (url != null && url.isNotEmpty) {
191
+ final client = PocketBaseClient(url);
82
192
  try {
83
- final client = PocketBaseClient(url);
84
193
  final ok = await client.health();
85
194
  if (ok) {
86
195
  steps.success('PocketBase reachable');
@@ -89,6 +198,8 @@ class DoctorCommand extends FlutterPatcherCommand {
89
198
  }
90
199
  } catch (e) {
91
200
  steps.fail('PocketBase unreachable — $e');
201
+ } finally {
202
+ client.close();
92
203
  }
93
204
  }
94
205
  }
@@ -25,6 +25,7 @@ class FingerprintCommand extends FlutterPatcherCommand {
25
25
  banner(name);
26
26
  final source = argResults!['source'] as String;
27
27
  final hash = generateFingerprint(source);
28
- box('fingerprint', [kv('source', source), kv('hash', hash)]);
28
+ step('source ${dim(source)}');
29
+ step('hash ${cyan(hash)}');
29
30
  });
30
31
  }
@@ -31,7 +31,11 @@ class InitCommand extends FlutterPatcherCommand {
31
31
  defaultsTo: 'production',
32
32
  help: 'Default channel.',
33
33
  );
34
- argParser.addOption('platform', defaultsTo: 'android', help: 'Default platform.');
34
+ argParser.addOption(
35
+ 'platform',
36
+ defaultsTo: 'android',
37
+ help: 'Default platform.',
38
+ );
35
39
  argParser.addOption(
36
40
  'source',
37
41
  abbr: 's',
@@ -42,7 +46,11 @@ class InitCommand extends FlutterPatcherCommand {
42
46
  'global',
43
47
  help: 'Write to the global ~/.flutter_ota_kit config (no scaffolding).',
44
48
  );
45
- argParser.addFlag('force', abbr: 'f', help: 'Overwrite an existing config.');
49
+ argParser.addFlag(
50
+ 'force',
51
+ abbr: 'f',
52
+ help: 'Overwrite an existing config.',
53
+ );
46
54
  }
47
55
 
48
56
  final FlutterPatcherConfig? config;
@@ -226,10 +234,7 @@ class InitCommand extends FlutterPatcherCommand {
226
234
  'Supabase service role key',
227
235
  env['SUPABASE_SERVICE_ROLE_KEY'],
228
236
  ),
229
- anonKey: _p(
230
- 'Supabase publishable key',
231
- env['SUPABASE_PUBLISHABLE_KEY'],
232
- ),
237
+ anonKey: _p('Supabase anon key', env['SUPABASE_ANON_KEY']),
233
238
  bucket: _p('Storage bucket', env['SUPABASE_BUCKET'] ?? 'bundles'),
234
239
  basePath: _p('Storage base path', env['SUPABASE_BASE_PATH']),
235
240
  managementKey: _p(
@@ -248,8 +253,10 @@ class InitCommand extends FlutterPatcherCommand {
248
253
  step('Wrote ${file.path}');
249
254
 
250
255
  if (global) {
251
- warn('Secrets are stored in plaintext under ~/.flutter_ota_kit. '
252
- 'Restrict permissions and use a secrets manager in production.');
256
+ warn(
257
+ 'Secrets are stored in plaintext under ~/.flutter_ota_kit. '
258
+ 'Restrict permissions and use a secrets manager in production.',
259
+ );
253
260
  return;
254
261
  }
255
262
 
@@ -259,21 +266,33 @@ class InitCommand extends FlutterPatcherCommand {
259
266
  stdout.writeln();
260
267
  stdout.writeln(' ${cyan('Next steps:')}');
261
268
  step('1. ${dim('flutter pub get')}');
262
- step('2. In lib/main.dart call ${green('await setupFlutterOta();')} '
263
- 'right after WidgetsFlutterBinding.ensureInitialized() (before runApp). '
264
- 'A `.env` scaffold was written — put secrets there, then build with '
265
- '`--dart-define-from-file=.env` (environment overrides config).');
269
+ step(
270
+ '2. In lib/main.dart call ${green('await setupFlutterOta();')} '
271
+ 'right after WidgetsFlutterBinding.ensureInitialized() (before runApp). '
272
+ 'A `.env` scaffold was written — put secrets there, then build with '
273
+ '`--dart-define-from-file=.env` (environment overrides config).',
274
+ );
266
275
  if (provider == 'supabase') {
267
- step('3. Provision the backend once: ${dim('flutter-ota migrate supabase')}');
276
+ step(
277
+ '3. Provision the backend once: ${dim('flutter-ota migrate supabase')}',
278
+ );
268
279
  } else if (provider == 'postgres') {
269
280
  step('3. Provision the backend: ${dim('flutter-ota migrate postgres')}');
281
+ } else if (provider == 'cloudflare') {
282
+ step(
283
+ '3. Provision the backend: ${dim('flutter-ota migrate cloudflare')}',
284
+ );
285
+ } else if (provider == 'aws') {
286
+ step('3. Provision the backend: ${dim('flutter-ota migrate aws')}');
270
287
  } else if (provider == 'pocketbase') {
271
- step('3. Install PocketBase + provision the schema: '
272
- '${dim('flutter-ota pocketbase install')} then '
273
- '${dim('flutter-ota pocketbase serve')}.');
288
+ step(
289
+ '3. Provision the backend: ${dim('flutter-ota migrate pocketbase')}',
290
+ );
274
291
  }
275
- step('4. Build a patch: ${dim('flutter-ota build --name 1.0.1')} '
276
- 'then ${dim('flutter-ota deploy')}');
292
+ step(
293
+ '4. Build a patch: ${dim('flutter-ota build --version 1.0.1')} '
294
+ 'then ${dim('flutter-ota deploy')}',
295
+ );
277
296
  });
278
297
 
279
298
  /// Generate the integration files (pubspec dep, manifest permission, setup dart
@@ -304,7 +323,9 @@ class InitCommand extends FlutterPatcherCommand {
304
323
  final marker = '\ndependencies:';
305
324
  final idx = content.indexOf(marker);
306
325
  if (idx == -1) {
307
- warn('Could not find a `dependencies:` block; add `flutter_ota_kit` manually.');
326
+ warn(
327
+ 'Could not find a `dependencies:` block; add `flutter_ota_kit` manually.',
328
+ );
308
329
  return;
309
330
  }
310
331
  final insertAt = idx + marker.length;
@@ -326,7 +347,9 @@ class InitCommand extends FlutterPatcherCommand {
326
347
  }
327
348
  final match = RegExp(r'<manifest[^>]*>').firstMatch(content);
328
349
  if (match == null) {
329
- warn('Could not locate <manifest> tag; add INTERNET permission manually.');
350
+ warn(
351
+ 'Could not locate <manifest> tag; add INTERNET permission manually.',
352
+ );
330
353
  return;
331
354
  }
332
355
  final insertAt = match.end;
@@ -420,7 +443,7 @@ class InitCommand extends FlutterPatcherCommand {
420
443
  default:
421
444
  final c = cfg.supabase;
422
445
  entries['SUPABASE_URL'] = c.url ?? '';
423
- entries['SUPABASE_PUBLISHABLE_KEY'] = c.anonKey ?? '';
446
+ entries['SUPABASE_ANON_KEY'] = c.anonKey ?? '';
424
447
  entries['SUPABASE_BUCKET'] = c.bucket ?? 'bundles';
425
448
  entries['CHANNEL'] = cfg.channel;
426
449
  entries['APP_VERSION'] = '';
@@ -462,7 +485,14 @@ class InitCommand extends FlutterPatcherCommand {
462
485
  step('Added .env to .gitignore');
463
486
  }
464
487
 
465
- String _q(String? s) => s == null ? "''" : "'${s.replaceAll("'", "\\'")}'";
488
+ String _q(String? s) {
489
+ if (s == null) return "''";
490
+ final escaped = s
491
+ .replaceAll('\\', '\\\\')
492
+ .replaceAll('\$', '\\\$')
493
+ .replaceAll("'", "\\'");
494
+ return "'$escaped'";
495
+ }
466
496
 
467
497
  /// Build the body of `lib/flutter_ota_kit_setup.dart` for the selected backend.
468
498
  ///
@@ -489,7 +519,7 @@ class InitCommand extends FlutterPatcherCommand {
489
519
  password: const String.fromEnvironment('POSTGRES_PASSWORD', defaultValue: ''),
490
520
  servingBaseUrl: const String.fromEnvironment('POSTGRES_SERVING_BASE_URL', defaultValue: ${_q(c.servingBaseUrl ?? '')}),
491
521
  channel: const String.fromEnvironment('CHANNEL', defaultValue: $channel),
492
- platform: Platform.android,
522
+ platform: Platform.${cfg.platform},
493
523
  updateStrategy: UpdateStrategy.appVersion,
494
524
  appVersion: $appVersionExpr,
495
525
  ));''';
@@ -506,7 +536,7 @@ class InitCommand extends FlutterPatcherCommand {
506
536
  secretAccessKey: const String.fromEnvironment('R2_SECRET_ACCESS_KEY', defaultValue: ''),
507
537
  basePath: const String.fromEnvironment('R2_BASE_PATH', defaultValue: ${_q(c.r2BasePath ?? '')}),
508
538
  channel: const String.fromEnvironment('CHANNEL', defaultValue: $channel),
509
- platform: Platform.android,
539
+ platform: Platform.${cfg.platform},
510
540
  updateStrategy: UpdateStrategy.appVersion,
511
541
  appVersion: $appVersionExpr,
512
542
  ));''';
@@ -522,7 +552,7 @@ class InitCommand extends FlutterPatcherCommand {
522
552
  basePath: const String.fromEnvironment('AWS_BASE_PATH', defaultValue: ${_q(c.basePath ?? '')}),
523
553
  endpoint: const String.fromEnvironment('AWS_ENDPOINT', defaultValue: ${_q(c.endpoint ?? '')}),
524
554
  channel: const String.fromEnvironment('CHANNEL', defaultValue: $channel),
525
- platform: Platform.android,
555
+ platform: Platform.${cfg.platform},
526
556
  updateStrategy: UpdateStrategy.appVersion,
527
557
  appVersion: $appVersionExpr,
528
558
  ));''';
@@ -537,7 +567,7 @@ class InitCommand extends FlutterPatcherCommand {
537
567
  bundlesCollection: const String.fromEnvironment('POCKETBASE_BUNDLES_COLLECTION', defaultValue: ${_q(c.bundlesCollection)}),
538
568
  bundlesBucket: const String.fromEnvironment('POCKETBASE_BUCKET', defaultValue: ${_q(c.bundlesBucket)}),
539
569
  channel: const String.fromEnvironment('CHANNEL', defaultValue: $channel),
540
- platform: Platform.android,
570
+ platform: Platform.${cfg.platform},
541
571
  updateStrategy: UpdateStrategy.appVersion,
542
572
  appVersion: $appVersionExpr,
543
573
  ));''';
@@ -548,10 +578,10 @@ class InitCommand extends FlutterPatcherCommand {
548
578
  '''
549
579
  FlutterPatcher.configureSupabase(SupabaseUpdateConfig(
550
580
  supabaseUrl: const String.fromEnvironment('SUPABASE_URL', defaultValue: ${_q(c.url ?? '')}),
551
- anonKey: const String.fromEnvironment('SUPABASE_PUBLISHABLE_KEY', defaultValue: ''),
581
+ anonKey: const String.fromEnvironment('SUPABASE_ANON_KEY', defaultValue: ''),
552
582
  bucket: const String.fromEnvironment('SUPABASE_BUCKET', defaultValue: ${_q(c.bucket ?? 'bundles')}),
553
583
  channel: const String.fromEnvironment('CHANNEL', defaultValue: $channel),
554
- platform: Platform.android,
584
+ platform: Platform.${cfg.platform},
555
585
  updateStrategy: UpdateStrategy.appVersion,
556
586
  appVersion: $appVersionExpr,
557
587
  sdkVersion: const String.fromEnvironment('SDK_VERSION', defaultValue: '1.0.0'),
@@ -1,4 +1,5 @@
1
1
  import 'dart:convert';
2
+ import 'dart:io';
2
3
 
3
4
  import '../cli_base.dart';
4
5
  import '../config.dart';
@@ -8,7 +9,10 @@ import '../ui/ui.dart';
8
9
  /// `flutter_ota_kit keys` — generate an Ed25519 keypair for bundle signing.
9
10
  class KeysCommand extends FlutterPatcherCommand {
10
11
  KeysCommand() {
11
- argParser.addFlag('save', help: 'Persist the public key into the project config.');
12
+ argParser.addFlag(
13
+ 'save',
14
+ help: 'Persist the public key into the project config.',
15
+ );
12
16
  }
13
17
 
14
18
  @override
@@ -38,11 +42,10 @@ class KeysCommand extends FlutterPatcherCommand {
38
42
  final file = configCandidates().first;
39
43
  final json =
40
44
  file.existsSync() && file.readAsStringSync().trim().isNotEmpty
41
- ? (jsonDecode(file.readAsStringSync()) as Map<String, dynamic>)
42
- : <String, dynamic>{};
45
+ ? (jsonDecode(file.readAsStringSync()) as Map<String, dynamic>)
46
+ : <String, dynamic>{};
43
47
  writePath(json, 'publicKey', publicB64);
44
- await steps.run<void>('Saving public key to ${file.path}',
45
- () async {
48
+ await steps.run<void>('Saving public key to ${file.path}', () async {
46
49
  _save(json);
47
50
  });
48
51
  }
@@ -53,5 +56,8 @@ class KeysCommand extends FlutterPatcherCommand {
53
56
  final file = configCandidates().first;
54
57
  file.parent.createSync(recursive: true);
55
58
  file.writeAsStringSync(const JsonEncoder.withIndent(' ').convert(json));
59
+ if (!Platform.isWindows) {
60
+ Process.runSync('chmod', ['600', file.path]);
61
+ }
56
62
  }
57
63
  }