@_nazmiforreal/flutter-ota 0.1.21 → 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.
- package/bin/flutter-ota-linux-x64 +0 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_config.json +460 -0
- package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +707 -0
- package/dart-src/packages/cli-tools/lib/flutter_ota_kit_cli.dart +7 -4
- package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +95 -32
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +28 -12
- package/dart-src/packages/cli-tools/lib/src/commands/config_command.dart +9 -4
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +29 -6
- package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +20 -32
- package/dart-src/packages/cli-tools/lib/src/commands/init.dart +40 -19
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +7 -5
- package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +33 -22
- package/dart-src/packages/cli-tools/lib/src/commands/pocketbase.dart +256 -68
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +17 -11
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +28 -12
- package/dart-src/packages/cli-tools/lib/src/config.dart +5 -6
- package/dart-src/packages/cli-tools/lib/src/operations.dart +1 -3
- package/dart-src/packages/cli-tools/lib/src/pocketbase/process_manager.dart +17 -16
- package/dart-src/packages/cli-tools/lib/src/runner.dart +3 -1
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +8 -14
- package/dart-src/packages/cli-tools/pubspec.lock +590 -0
- package/dart-src/packages/cli-tools/test/mocks/mock_pocketbase_client.dart +7 -3
- package/dart-src/packages/cli-tools/test/pocketbase_schema_test.dart +13 -16
- package/dart-src/packages/core/CHANGELOG.md +15 -0
- package/dart-src/packages/core/lib/src/bundle.dart +3 -6
- package/dart-src/packages/core/pubspec.yaml +1 -1
- package/dart-src/plugins/aws/CHANGELOG.md +18 -0
- package/dart-src/plugins/aws/lib/src/aws_cloudfront_client.dart +1 -2
- package/dart-src/plugins/aws/pubspec.yaml +3 -3
- package/dart-src/plugins/cloudflare/CHANGELOG.md +18 -0
- package/dart-src/plugins/cloudflare/pubspec.yaml +3 -3
- package/dart-src/plugins/plugin-core/CHANGELOG.md +15 -0
- package/dart-src/plugins/plugin-core/pubspec.yaml +2 -2
- package/dart-src/plugins/pocketbase/CHANGELOG.md +18 -0
- package/dart-src/plugins/pocketbase/pubspec.yaml +3 -3
- package/dart-src/plugins/postgres/CHANGELOG.md +18 -0
- package/dart-src/plugins/postgres/pubspec.yaml +3 -3
- package/dart-src/plugins/supabase/CHANGELOG.md +18 -0
- package/dart-src/plugins/supabase/pubspec.yaml +3 -3
- package/package.json +2 -2
- package/scripts/postinstall.js +5 -1
|
@@ -7,16 +7,24 @@ import '../ui/ui.dart';
|
|
|
7
7
|
class RollbackCommand extends FlutterPatcherCommand {
|
|
8
8
|
RollbackCommand({this.config, this.backendOverride}) {
|
|
9
9
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
10
|
-
argParser.addOption(
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
argParser.addOption(
|
|
11
|
+
'backend',
|
|
12
|
+
abbr: 'b',
|
|
13
|
+
help: detected != null
|
|
14
|
+
? 'Backend provider [detected: $detected].'
|
|
15
|
+
: 'Backend provider.',
|
|
16
|
+
);
|
|
13
17
|
argParser.addOption('channel', abbr: 'c', help: 'Channel to roll back.');
|
|
14
18
|
argParser.addOption(
|
|
15
19
|
'bundle-id',
|
|
16
20
|
abbr: 'i',
|
|
17
21
|
help: 'Roll back to this specific bundle id.',
|
|
18
22
|
);
|
|
19
|
-
argParser.addOption(
|
|
23
|
+
argParser.addOption(
|
|
24
|
+
'platform',
|
|
25
|
+
abbr: 'p',
|
|
26
|
+
help: 'Platform filter (e.g. android).',
|
|
27
|
+
);
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
final FlutterPatcherConfig? config;
|
|
@@ -49,14 +57,12 @@ class RollbackCommand extends FlutterPatcherCommand {
|
|
|
49
57
|
|
|
50
58
|
banner('rollback');
|
|
51
59
|
final steps = Steps('rollback');
|
|
52
|
-
final (
|
|
60
|
+
final (
|
|
61
|
+
disabled,
|
|
62
|
+
live,
|
|
63
|
+
) = await steps.run('Rolling back channel "$channel"', () async {
|
|
53
64
|
if (bundleId != null && bundleId.isNotEmpty) {
|
|
54
|
-
return rollbackToBundle(
|
|
55
|
-
backend,
|
|
56
|
-
channel,
|
|
57
|
-
bundleId,
|
|
58
|
-
platform: platform,
|
|
59
|
-
);
|
|
65
|
+
return rollbackToBundle(backend, channel, bundleId, platform: platform);
|
|
60
66
|
}
|
|
61
67
|
final id = await rollbackChannel(backend, channel);
|
|
62
68
|
final nowLive = (await getChannel(backend, channel))?.id ?? '';
|
|
@@ -28,9 +28,13 @@ class StorageCommand extends FlutterPatcherCommand {
|
|
|
28
28
|
class StorageListCommand extends FlutterPatcherCommand {
|
|
29
29
|
StorageListCommand({this.config, this.backendOverride}) {
|
|
30
30
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
31
|
-
argParser.addOption(
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
argParser.addOption(
|
|
32
|
+
'backend',
|
|
33
|
+
abbr: 'b',
|
|
34
|
+
help: detected != null
|
|
35
|
+
? 'Backend provider [detected: $detected].'
|
|
36
|
+
: 'Backend provider.',
|
|
37
|
+
);
|
|
34
38
|
argParser.addOption('prefix', help: 'Key prefix filter (e.g. bundles).');
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -50,9 +54,12 @@ class StorageListCommand extends FlutterPatcherCommand {
|
|
|
50
54
|
final prefix = argResults!['prefix'] as String?;
|
|
51
55
|
banner('storage · list');
|
|
52
56
|
final steps = Steps('list');
|
|
53
|
-
final objects = await steps.run(
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
final objects = await steps.run(
|
|
58
|
+
'Listing storage objects',
|
|
59
|
+
() => backend.storage.listObjects(
|
|
60
|
+
prefix == null || prefix.isEmpty ? null : prefix,
|
|
61
|
+
),
|
|
62
|
+
);
|
|
56
63
|
if (objects.isEmpty) {
|
|
57
64
|
steps.skip('(no objects)');
|
|
58
65
|
steps.summary();
|
|
@@ -70,10 +77,17 @@ class StorageListCommand extends FlutterPatcherCommand {
|
|
|
70
77
|
class StorageDeleteCommand extends FlutterPatcherCommand {
|
|
71
78
|
StorageDeleteCommand({this.config, this.backendOverride}) {
|
|
72
79
|
final detected = config?.provider ?? loadConfig()?.provider;
|
|
73
|
-
argParser.addOption(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
argParser.addOption(
|
|
81
|
+
'backend',
|
|
82
|
+
abbr: 'b',
|
|
83
|
+
help: detected != null
|
|
84
|
+
? 'Backend provider [detected: $detected].'
|
|
85
|
+
: 'Backend provider.',
|
|
86
|
+
);
|
|
87
|
+
argParser.addMultiOption(
|
|
88
|
+
'key',
|
|
89
|
+
help: 'Storage key to delete (repeatable).',
|
|
90
|
+
);
|
|
77
91
|
argParser.addOption('uri', help: 'Full storage URI to delete.');
|
|
78
92
|
}
|
|
79
93
|
|
|
@@ -102,8 +116,10 @@ class StorageDeleteCommand extends FlutterPatcherCommand {
|
|
|
102
116
|
banner('storage · delete');
|
|
103
117
|
final steps = Steps('delete');
|
|
104
118
|
if (keys.isNotEmpty) {
|
|
105
|
-
await steps.run(
|
|
106
|
-
|
|
119
|
+
await steps.run(
|
|
120
|
+
'Deleting ${keys.length} object(s)',
|
|
121
|
+
() => backend.storage.deleteObjects(keys),
|
|
122
|
+
);
|
|
107
123
|
} else {
|
|
108
124
|
await steps.run('Deleting $uri', () => backend.storage.delete(uri!));
|
|
109
125
|
}
|
|
@@ -170,9 +170,7 @@ FlutterPatcherConfig? loadConfig() {
|
|
|
170
170
|
jsonDecode(raw) as Map<String, dynamic>,
|
|
171
171
|
);
|
|
172
172
|
} on FormatException {
|
|
173
|
-
stderr.writeln(
|
|
174
|
-
'Warning: invalid config at ${file.path} — ignoring.',
|
|
175
|
-
);
|
|
173
|
+
stderr.writeln('Warning: invalid config at ${file.path} — ignoring.');
|
|
176
174
|
return null;
|
|
177
175
|
}
|
|
178
176
|
}
|
|
@@ -246,7 +244,7 @@ SupabaseServiceRoleConfig resolveSupabaseConfig(
|
|
|
246
244
|
if (resolvedUrl == null || resolvedUrl.isEmpty) {
|
|
247
245
|
throw StateError(
|
|
248
246
|
'Supabase URL is required. Set it via SUPABASE_URL env var, '
|
|
249
|
-
|
|
247
|
+
'or `flutter-ota config set supabase.url <url>`.',
|
|
250
248
|
);
|
|
251
249
|
}
|
|
252
250
|
|
|
@@ -280,7 +278,8 @@ SupabaseStorageConfig resolveSupabaseStorageConfig(
|
|
|
280
278
|
supabaseServiceRoleKey: db.supabaseServiceRoleKey,
|
|
281
279
|
supabaseAnonKey: db.supabaseAnonKey,
|
|
282
280
|
clientFactory: db.clientFactory,
|
|
283
|
-
bucketName:
|
|
281
|
+
bucketName:
|
|
282
|
+
bucket ??
|
|
284
283
|
Platform.environment['SUPABASE_BUCKET'] ??
|
|
285
284
|
config.supabase.bucket ??
|
|
286
285
|
'bundles',
|
|
@@ -382,7 +381,7 @@ PostgresConfig resolvePostgresDatabaseConfig(
|
|
|
382
381
|
if (resolvedHost == null || resolvedHost.isEmpty) {
|
|
383
382
|
throw StateError(
|
|
384
383
|
'Postgres host is required. Set POSTGRES_HOST env var, '
|
|
385
|
-
|
|
384
|
+
'or `flutter-ota config set postgres.host <host>`.',
|
|
386
385
|
);
|
|
387
386
|
}
|
|
388
387
|
|
|
@@ -61,9 +61,7 @@ class ListOptions {
|
|
|
61
61
|
/// Build the zip, upload it, and register a [Bundle] via the backend.
|
|
62
62
|
Future<Bundle> deployBundle(Backend backend, DeployOptions opts) async {
|
|
63
63
|
if (opts.targetAppVersion != null && opts.fingerprintHash != null) {
|
|
64
|
-
throw StateError(
|
|
65
|
-
'Use only one of target-app-version / fingerprint-hash.',
|
|
66
|
-
);
|
|
64
|
+
throw StateError('Use only one of target-app-version / fingerprint-hash.');
|
|
67
65
|
}
|
|
68
66
|
if (opts.targetAppVersion == null && opts.fingerprintHash == null) {
|
|
69
67
|
throw StateError(
|
|
@@ -17,7 +17,7 @@ import 'package:path/path.dart' as p;
|
|
|
17
17
|
|
|
18
18
|
class PocketBaseProcess {
|
|
19
19
|
PocketBaseProcess._(this.process, this.dataDir, this.binaryPath)
|
|
20
|
-
|
|
20
|
+
: _exitCodeCompleted = false {
|
|
21
21
|
process.exitCode.then((_) => _exitCodeCompleted = true);
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -92,12 +92,14 @@ class PocketBaseProcessManager {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
void _writeConfigFile(int pid) {
|
|
95
|
-
configFile.writeAsStringSync(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
configFile.writeAsStringSync(
|
|
96
|
+
jsonEncode({
|
|
97
|
+
'pid': pid,
|
|
98
|
+
'host': host,
|
|
99
|
+
'port': port,
|
|
100
|
+
'url': 'http://$host:$port',
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
Map<String, dynamic>? readConfig() {
|
|
@@ -135,19 +137,18 @@ class PocketBaseProcessManager {
|
|
|
135
137
|
final hooksDir = Directory(p.join(dataDir.path, 'pb_hooks'));
|
|
136
138
|
await hooksDir.create(recursive: true);
|
|
137
139
|
|
|
138
|
-
final mergedEnv = <String, String>{
|
|
139
|
-
...Platform.environment,
|
|
140
|
-
...?env,
|
|
141
|
-
};
|
|
140
|
+
final mergedEnv = <String, String>{...Platform.environment, ...?env};
|
|
142
141
|
|
|
143
142
|
// Create superuser if credentials provided (PB 0.40+ doesn't support
|
|
144
143
|
// PB_ADMIN_EMAIL env var, so we use the CLI directly).
|
|
145
144
|
if (adminEmail != null && adminPassword != null) {
|
|
146
|
-
final result = await Process.run(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
final result = await Process.run(binaryPath.path, [
|
|
146
|
+
'superuser',
|
|
147
|
+
'upsert',
|
|
148
|
+
'--dir=${dataDir.path}',
|
|
149
|
+
adminEmail,
|
|
150
|
+
adminPassword,
|
|
151
|
+
], environment: mergedEnv);
|
|
151
152
|
if (result.exitCode != 0) {
|
|
152
153
|
// Non-fatal; serve will continue but schema install may fail.
|
|
153
154
|
}
|
|
@@ -24,7 +24,9 @@ class FlutterPatcherRunner extends CommandRunner<int> {
|
|
|
24
24
|
// Intercept missing subcommand for parent commands so we show our
|
|
25
25
|
// colored usage text instead of the args package's plain text.
|
|
26
26
|
if (results.command == null && results.rest.isEmpty) {
|
|
27
|
-
final cmdName = results.arguments.isNotEmpty
|
|
27
|
+
final cmdName = results.arguments.isNotEmpty
|
|
28
|
+
? results.arguments.first
|
|
29
|
+
: null;
|
|
28
30
|
if (cmdName != null) {
|
|
29
31
|
final cmd = commands[cmdName];
|
|
30
32
|
if (cmd != null && cmd.subcommands.isNotEmpty) {
|
|
@@ -127,7 +127,8 @@ Future<T> spinner<T>(
|
|
|
127
127
|
final r = await task();
|
|
128
128
|
// Keep spinner visible for at least 400ms so fast ops are readable.
|
|
129
129
|
final elapsed = sw.elapsedMilliseconds;
|
|
130
|
-
if (elapsed < 400)
|
|
130
|
+
if (elapsed < 400)
|
|
131
|
+
await Future.delayed(Duration(milliseconds: 400 - elapsed));
|
|
131
132
|
timer.cancel();
|
|
132
133
|
sw.stop();
|
|
133
134
|
stdout.write('\r\x1b[K ${green('✓')} ${done ?? label}\n');
|
|
@@ -296,9 +297,7 @@ class Steps {
|
|
|
296
297
|
void summary() {
|
|
297
298
|
_sw.stop();
|
|
298
299
|
final ms = _sw.elapsedMilliseconds;
|
|
299
|
-
final time = ms >= 1000
|
|
300
|
-
? '${(ms / 1000).toStringAsFixed(1)}s'
|
|
301
|
-
: '${ms}ms';
|
|
300
|
+
final time = ms >= 1000 ? '${(ms / 1000).toStringAsFixed(1)}s' : '${ms}ms';
|
|
302
301
|
final parts = <String>[];
|
|
303
302
|
if (_done > 0) parts.add('${_done} completed');
|
|
304
303
|
if (_skipped > 0) parts.add('${_skipped} skipped');
|
|
@@ -401,9 +400,7 @@ class Steps {
|
|
|
401
400
|
_activeTimer = null;
|
|
402
401
|
_activeLabel = null;
|
|
403
402
|
if (_colorOn) {
|
|
404
|
-
stdout.write(
|
|
405
|
-
'\r\x1b[K ${success ? green('✓') : red('✗')} $label\n',
|
|
406
|
-
);
|
|
403
|
+
stdout.write('\r\x1b[K ${success ? green('✓') : red('✗')} $label\n');
|
|
407
404
|
}
|
|
408
405
|
if (success) {
|
|
409
406
|
_done++;
|
|
@@ -455,9 +452,7 @@ class ProgressBar {
|
|
|
455
452
|
_spin = (_spin + 1) % _frames.length;
|
|
456
453
|
final pctStr = '${(pct * 100).round()}%'.padLeft(4);
|
|
457
454
|
|
|
458
|
-
stdout.write(
|
|
459
|
-
'\r\x1b[K ${cyan(_frames[_spin])} $_label $bar $pctStr',
|
|
460
|
-
);
|
|
455
|
+
stdout.write('\r\x1b[K ${cyan(_frames[_spin])} $_label $bar $pctStr');
|
|
461
456
|
}
|
|
462
457
|
|
|
463
458
|
/// Finish: print the final status line. This is intentionally async and
|
|
@@ -470,11 +465,10 @@ class ProgressBar {
|
|
|
470
465
|
_sw.stop();
|
|
471
466
|
// Keep bar visible for at least 500ms so fast ops are readable.
|
|
472
467
|
final elapsed = _sw.elapsedMilliseconds;
|
|
473
|
-
if (elapsed < 500)
|
|
468
|
+
if (elapsed < 500)
|
|
469
|
+
await Future.delayed(Duration(milliseconds: 500 - elapsed));
|
|
474
470
|
final ms = _sw.elapsedMilliseconds;
|
|
475
|
-
final time = ms >= 1000
|
|
476
|
-
? '${(ms / 1000).toStringAsFixed(1)}s'
|
|
477
|
-
: '${ms}ms';
|
|
471
|
+
final time = ms >= 1000 ? '${(ms / 1000).toStringAsFixed(1)}s' : '${ms}ms';
|
|
478
472
|
if (_colorOn) {
|
|
479
473
|
stdout.write('\r\x1b[K ${green('✓')} $_label in $time\n');
|
|
480
474
|
} else {
|