@_nazmiforreal/flutter-ota 0.1.17 → 0.1.19
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/lib/src/commands/bundle.dart +29 -44
- package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +4 -5
- package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +61 -19
- package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +11 -7
- package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +15 -17
- package/dart-src/packages/cli-tools/lib/src/commands/storage.dart +14 -16
- package/dart-src/packages/cli-tools/lib/src/operations.dart +9 -0
- package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +105 -1
- package/dart-src/packages/cli-tools/pubspec.yaml +8 -8
- package/dart-src/packages/core/pubspec.yaml +1 -1
- package/dart-src/plugins/aws/pubspec.yaml +3 -3
- package/dart-src/plugins/cloudflare/pubspec.yaml +3 -3
- package/dart-src/plugins/plugin-core/pubspec.yaml +2 -2
- package/dart-src/plugins/pocketbase/pubspec.yaml +3 -3
- package/dart-src/plugins/postgres/pubspec.yaml +3 -3
- package/dart-src/plugins/supabase/pubspec.yaml +3 -3
- package/package.json +3 -3
|
Binary file
|
|
@@ -204,19 +204,10 @@ class BundleDeleteCommand extends FlutterPatcherCommand {
|
|
|
204
204
|
}
|
|
205
205
|
final keepStorage = argResults!['keep-storage'] as bool;
|
|
206
206
|
final steps = Steps('delete');
|
|
207
|
-
await
|
|
208
|
-
() => deleteBundle(backend, id),
|
|
209
|
-
'Deleting bundle $id',
|
|
210
|
-
done: 'Deleted',
|
|
211
|
-
);
|
|
212
|
-
steps.success('Deleted bundle $id');
|
|
207
|
+
await steps.run('Deleting bundle $id', () => deleteBundle(backend, id));
|
|
213
208
|
if (!keepStorage && existing.storageUri.isNotEmpty) {
|
|
214
|
-
await
|
|
215
|
-
|
|
216
|
-
'Removing storage object',
|
|
217
|
-
done: 'Storage removed',
|
|
218
|
-
);
|
|
219
|
-
steps.success('Removed storage object');
|
|
209
|
+
await steps.run('Removing storage object',
|
|
210
|
+
() => backend.storage.delete(existing.storageUri));
|
|
220
211
|
}
|
|
221
212
|
steps.summary();
|
|
222
213
|
});
|
|
@@ -244,14 +235,12 @@ class BundleDisableCommand extends FlutterPatcherCommand {
|
|
|
244
235
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
245
236
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
246
237
|
banner('bundle · disable');
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
done: 'Disabled',
|
|
254
|
-
);
|
|
238
|
+
final steps = Steps('disable');
|
|
239
|
+
await steps.run('Disabling bundle $id', () async {
|
|
240
|
+
await backend.db.updateBundle(id!, {'enabled': false});
|
|
241
|
+
await backend.db.commitBundle();
|
|
242
|
+
});
|
|
243
|
+
steps.summary();
|
|
255
244
|
});
|
|
256
245
|
}
|
|
257
246
|
|
|
@@ -277,14 +266,12 @@ class BundleEnableCommand extends FlutterPatcherCommand {
|
|
|
277
266
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
278
267
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
279
268
|
banner('bundle · enable');
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
done: 'Enabled',
|
|
287
|
-
);
|
|
269
|
+
final steps = Steps('enable');
|
|
270
|
+
await steps.run('Enabling bundle $id', () async {
|
|
271
|
+
await backend.db.updateBundle(id!, {'enabled': true});
|
|
272
|
+
await backend.db.commitBundle();
|
|
273
|
+
});
|
|
274
|
+
steps.summary();
|
|
288
275
|
});
|
|
289
276
|
}
|
|
290
277
|
|
|
@@ -315,7 +302,9 @@ class BundleForceCommand extends FlutterPatcherCommand {
|
|
|
315
302
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
316
303
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
317
304
|
banner('bundle · force');
|
|
318
|
-
|
|
305
|
+
final steps = Steps('force');
|
|
306
|
+
await steps.run(
|
|
307
|
+
off ? 'Clearing force flag on $id' : 'Forcing update for $id',
|
|
319
308
|
() async {
|
|
320
309
|
await backend.db.updateBundle(id!, {
|
|
321
310
|
'shouldForceUpdate': !off,
|
|
@@ -323,9 +312,8 @@ class BundleForceCommand extends FlutterPatcherCommand {
|
|
|
323
312
|
});
|
|
324
313
|
await backend.db.commitBundle();
|
|
325
314
|
},
|
|
326
|
-
off ? 'Clearing force flag on $id' : 'Forcing update for $id',
|
|
327
|
-
done: off ? 'Cleared' : 'Forced',
|
|
328
315
|
);
|
|
316
|
+
steps.summary();
|
|
329
317
|
});
|
|
330
318
|
}
|
|
331
319
|
|
|
@@ -359,11 +347,10 @@ class BundlePromoteCommand extends FlutterPatcherCommand {
|
|
|
359
347
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
360
348
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
361
349
|
banner('bundle · promote');
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
);
|
|
350
|
+
final steps = Steps('promote');
|
|
351
|
+
await steps.run('Promoting $id to $channel',
|
|
352
|
+
() => promoteBundle(backend, id!, channel));
|
|
353
|
+
steps.summary();
|
|
367
354
|
box('promote', [kv('bundle', cyan(id!)), kv('channel', channel)]);
|
|
368
355
|
});
|
|
369
356
|
}
|
|
@@ -411,15 +398,13 @@ class BundleUpdateCommand extends FlutterPatcherCommand {
|
|
|
411
398
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
412
399
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
413
400
|
banner('bundle · update');
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
'Updating $id',
|
|
420
|
-
done: 'Updated',
|
|
421
|
-
);
|
|
401
|
+
final steps = Steps('update');
|
|
402
|
+
await steps.run('Updating $id', () async {
|
|
403
|
+
await backend.db.updateBundle(id!, patch);
|
|
404
|
+
await backend.db.commitBundle();
|
|
405
|
+
});
|
|
422
406
|
final b = await backend.db.getBundleById(id!);
|
|
407
|
+
steps.summary();
|
|
423
408
|
if (b != null) {
|
|
424
409
|
box('bundle', [
|
|
425
410
|
kv('id', cyan(b.id)),
|
|
@@ -130,11 +130,10 @@ class ChannelSetCommand extends FlutterPatcherCommand {
|
|
|
130
130
|
}
|
|
131
131
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
132
132
|
banner('channel · set');
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
);
|
|
133
|
+
final steps = Steps('set');
|
|
134
|
+
await steps.run('Promoting $bundleId to $channel',
|
|
135
|
+
() => promoteBundle(backend, bundleId, channel));
|
|
136
|
+
steps.summary();
|
|
138
137
|
box('channel set', [kv('channel', channel), kv('bundle', cyan(bundleId))]);
|
|
139
138
|
});
|
|
140
139
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import 'dart:io';
|
|
2
2
|
|
|
3
|
+
import 'package:flutter_ota_kit_core/flutter_ota_kit_core.dart';
|
|
4
|
+
|
|
3
5
|
import '../backend.dart';
|
|
4
6
|
import '../cli_base.dart';
|
|
5
7
|
import '../config.dart';
|
|
@@ -75,25 +77,11 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
75
77
|
banner('deploy');
|
|
76
78
|
info('channel ${cyan(channel)} platform ${cyan(platform)} source ${dim(source)}');
|
|
77
79
|
|
|
78
|
-
final
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
channel: channel,
|
|
84
|
-
platform: platform,
|
|
85
|
-
message: message,
|
|
86
|
-
force: force,
|
|
87
|
-
targetAppVersion: targetAppVersion,
|
|
88
|
-
fingerprintHash: fingerprintHash,
|
|
89
|
-
signingKeyBase64: signingKey,
|
|
90
|
-
gitCommitHash: resolvedGitCommitHash,
|
|
91
|
-
bundleId: bundleId,
|
|
92
|
-
),
|
|
93
|
-
),
|
|
94
|
-
'Uploading & registering bundle',
|
|
95
|
-
done: 'Bundle deployed',
|
|
96
|
-
);
|
|
80
|
+
final steps = Steps('deploy');
|
|
81
|
+
final bundle = await _deployWithPhases(steps, backend, source, channel,
|
|
82
|
+
platform, message, force, targetAppVersion, fingerprintHash,
|
|
83
|
+
signingKey, resolvedGitCommitHash, bundleId);
|
|
84
|
+
steps.summary();
|
|
97
85
|
|
|
98
86
|
final lines = <String>[
|
|
99
87
|
kv('bundle id', cyan(bundle.id)),
|
|
@@ -118,3 +106,57 @@ class DeployCommand extends FlutterPatcherCommand {
|
|
|
118
106
|
box('deployed', lines);
|
|
119
107
|
});
|
|
120
108
|
}
|
|
109
|
+
|
|
110
|
+
/// Deploy with per-phase UI. Each phase reported by `DeployOptions.onPhase`
|
|
111
|
+
/// is shown as an active spinner line that gets replaced in-place.
|
|
112
|
+
Future<Bundle> _deployWithPhases(
|
|
113
|
+
Steps steps,
|
|
114
|
+
Backend backend,
|
|
115
|
+
String source,
|
|
116
|
+
String channel,
|
|
117
|
+
String platform,
|
|
118
|
+
String? message,
|
|
119
|
+
bool force,
|
|
120
|
+
String? targetAppVersion,
|
|
121
|
+
String? fingerprintHash,
|
|
122
|
+
String? signingKey,
|
|
123
|
+
String? resolvedGitCommitHash,
|
|
124
|
+
String? bundleId,
|
|
125
|
+
) async {
|
|
126
|
+
String? activeLabel;
|
|
127
|
+
try {
|
|
128
|
+
final bundle = await deployBundle(
|
|
129
|
+
backend,
|
|
130
|
+
DeployOptions(
|
|
131
|
+
source: source,
|
|
132
|
+
channel: channel,
|
|
133
|
+
platform: platform,
|
|
134
|
+
message: message,
|
|
135
|
+
force: force,
|
|
136
|
+
targetAppVersion: targetAppVersion,
|
|
137
|
+
fingerprintHash: fingerprintHash,
|
|
138
|
+
signingKeyBase64: signingKey,
|
|
139
|
+
gitCommitHash: resolvedGitCommitHash,
|
|
140
|
+
bundleId: bundleId,
|
|
141
|
+
onPhase: (phase) {
|
|
142
|
+
if (activeLabel != null) {
|
|
143
|
+
steps.completeActive();
|
|
144
|
+
}
|
|
145
|
+
activeLabel = phase;
|
|
146
|
+
steps.startActive(phase);
|
|
147
|
+
},
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
if (activeLabel != null) {
|
|
151
|
+
steps.completeActive();
|
|
152
|
+
activeLabel = null;
|
|
153
|
+
}
|
|
154
|
+
return bundle;
|
|
155
|
+
} catch (e) {
|
|
156
|
+
if (activeLabel != null) {
|
|
157
|
+
steps.completeActive(success: false);
|
|
158
|
+
activeLabel = null;
|
|
159
|
+
}
|
|
160
|
+
rethrow;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -21,10 +21,11 @@ class KeysCommand extends FlutterPatcherCommand {
|
|
|
21
21
|
@override
|
|
22
22
|
Future<int> run() => runGuarded(() async {
|
|
23
23
|
banner('keys');
|
|
24
|
-
final
|
|
25
|
-
|
|
24
|
+
final steps = Steps('keys');
|
|
25
|
+
final (privateB64, publicB64) = await steps.run(
|
|
26
26
|
'Generating Ed25519 keypair',
|
|
27
|
-
|
|
27
|
+
() => generateEd25519KeyPair(),
|
|
28
|
+
// Use Steps.run without the spinner pattern; keys is fast.
|
|
28
29
|
);
|
|
29
30
|
box('ed25519 keys', [
|
|
30
31
|
'${bold('private')} (keep secret — use with `deploy --key`):',
|
|
@@ -37,12 +38,15 @@ class KeysCommand extends FlutterPatcherCommand {
|
|
|
37
38
|
final file = configCandidates().first;
|
|
38
39
|
final json =
|
|
39
40
|
file.existsSync() && file.readAsStringSync().trim().isNotEmpty
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
? (jsonDecode(file.readAsStringSync()) as Map<String, dynamic>)
|
|
42
|
+
: <String, dynamic>{};
|
|
42
43
|
writePath(json, 'publicKey', publicB64);
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
await steps.run<void>('Saving public key to ${file.path}',
|
|
45
|
+
() async {
|
|
46
|
+
_save(json);
|
|
47
|
+
});
|
|
45
48
|
}
|
|
49
|
+
steps.summary();
|
|
46
50
|
});
|
|
47
51
|
|
|
48
52
|
void _save(Map<String, dynamic> json) {
|
|
@@ -45,23 +45,21 @@ class RollbackCommand extends FlutterPatcherCommand {
|
|
|
45
45
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
46
46
|
|
|
47
47
|
banner('rollback');
|
|
48
|
-
final
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
done: 'Rolled back',
|
|
64
|
-
);
|
|
48
|
+
final steps = Steps('rollback');
|
|
49
|
+
final (disabled, live) = await steps.run('Rolling back channel "$channel"', () async {
|
|
50
|
+
if (bundleId != null && bundleId.isNotEmpty) {
|
|
51
|
+
return rollbackToBundle(
|
|
52
|
+
backend,
|
|
53
|
+
channel,
|
|
54
|
+
bundleId,
|
|
55
|
+
platform: platform,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
final id = await rollbackChannel(backend, channel);
|
|
59
|
+
final nowLive = (await getChannel(backend, channel))?.id ?? '';
|
|
60
|
+
return (<String>[id], nowLive);
|
|
61
|
+
});
|
|
62
|
+
steps.summary();
|
|
65
63
|
box('rollback', [
|
|
66
64
|
kv('channel', channel),
|
|
67
65
|
kv('now live', live.isEmpty ? dim('(none)') : cyan(live)),
|
|
@@ -46,11 +46,13 @@ class StorageListCommand extends FlutterPatcherCommand {
|
|
|
46
46
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
47
47
|
final prefix = argResults!['prefix'] as String?;
|
|
48
48
|
banner('storage · list');
|
|
49
|
-
final
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
final steps = Steps('list');
|
|
50
|
+
final objects = await steps.run('Listing storage objects', () =>
|
|
51
|
+
backend.storage.listObjects(
|
|
52
|
+
prefix == null || prefix.isEmpty ? null : prefix));
|
|
52
53
|
if (objects.isEmpty) {
|
|
53
|
-
|
|
54
|
+
steps.skip('(no objects)');
|
|
55
|
+
steps.summary();
|
|
54
56
|
return;
|
|
55
57
|
}
|
|
56
58
|
final rows = <List<String>>[];
|
|
@@ -58,11 +60,12 @@ class StorageListCommand extends FlutterPatcherCommand {
|
|
|
58
60
|
final size = o.size >= 1024 * 1024
|
|
59
61
|
? '${(o.size / (1024 * 1024)).toStringAsFixed(2)} MB'
|
|
60
62
|
: o.size >= 1024
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
? '${(o.size / 1024).toStringAsFixed(1)} KB'
|
|
64
|
+
: '${o.size} B';
|
|
63
65
|
rows.add([cyan(o.key), size]);
|
|
64
66
|
}
|
|
65
67
|
table('${objects.length} objects', ['KEY', 'SIZE'], rows);
|
|
68
|
+
steps.summary();
|
|
66
69
|
});
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -96,18 +99,13 @@ class StorageDeleteCommand extends FlutterPatcherCommand {
|
|
|
96
99
|
final cfg = effectiveConfig(config ?? loadConfig(), argResults!);
|
|
97
100
|
final backend = requireBackend(cfg, override: backendOverride);
|
|
98
101
|
banner('storage · delete');
|
|
102
|
+
final steps = Steps('delete');
|
|
99
103
|
if (keys.isNotEmpty) {
|
|
100
|
-
await
|
|
101
|
-
|
|
102
|
-
'Deleting ${keys.length} object(s)',
|
|
103
|
-
done: 'Deleted',
|
|
104
|
-
);
|
|
104
|
+
await steps.run('Deleting ${keys.length} object(s)',
|
|
105
|
+
() => backend.storage.deleteObjects(keys));
|
|
105
106
|
} else {
|
|
106
|
-
await
|
|
107
|
-
() => backend.storage.delete(uri!),
|
|
108
|
-
'Deleting $uri',
|
|
109
|
-
done: 'Deleted',
|
|
110
|
-
);
|
|
107
|
+
await steps.run('Deleting $uri', () => backend.storage.delete(uri!));
|
|
111
108
|
}
|
|
109
|
+
steps.summary();
|
|
112
110
|
});
|
|
113
111
|
}
|
|
@@ -23,6 +23,7 @@ class DeployOptions {
|
|
|
23
23
|
this.gitCommitHash,
|
|
24
24
|
this.bundleId,
|
|
25
25
|
this.metadata,
|
|
26
|
+
this.onPhase,
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
final String source;
|
|
@@ -36,6 +37,10 @@ class DeployOptions {
|
|
|
36
37
|
final String? gitCommitHash;
|
|
37
38
|
final String? bundleId;
|
|
38
39
|
final BundleMetadata? metadata;
|
|
40
|
+
|
|
41
|
+
/// Called with a human-readable phase label as deploy progresses.
|
|
42
|
+
/// Used by the CLI to show per-step progress (e.g. Steps.run()).
|
|
43
|
+
final void Function(String phase)? onPhase;
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
/// Options for [listBundles].
|
|
@@ -85,6 +90,7 @@ Future<Bundle> deployBundle(Backend backend, DeployOptions opts) async {
|
|
|
85
90
|
zipPath = packaged.path;
|
|
86
91
|
shouldDelete = false;
|
|
87
92
|
} else {
|
|
93
|
+
opts.onPhase?.call('Zipping source');
|
|
88
94
|
zipPath = await zipDirectory(opts.source);
|
|
89
95
|
shouldDelete = true;
|
|
90
96
|
}
|
|
@@ -94,6 +100,7 @@ Future<Bundle> deployBundle(Backend backend, DeployOptions opts) async {
|
|
|
94
100
|
final zipBytes = await File(zipPath).readAsBytes();
|
|
95
101
|
// Device SDK verifies the artifact against an MD5 hex of the whole file,
|
|
96
102
|
// so `fileHash` is always the MD5 hex (never a `sig:`-prefixed value).
|
|
103
|
+
opts.onPhase?.call('Hashing artifact');
|
|
97
104
|
final md5Hex = md5.convert(zipBytes).toString();
|
|
98
105
|
final signature = opts.signingKeyBase64 != null
|
|
99
106
|
? await ed25519Sign(utf8.encode(md5Hex), opts.signingKeyBase64!)
|
|
@@ -101,6 +108,7 @@ Future<Bundle> deployBundle(Backend backend, DeployOptions opts) async {
|
|
|
101
108
|
|
|
102
109
|
final bundleId = opts.bundleId ?? uuidV7();
|
|
103
110
|
final key = bundleId;
|
|
111
|
+
opts.onPhase?.call('Uploading to storage');
|
|
104
112
|
final uploaded = await backend.storage.upload(key, zipPath);
|
|
105
113
|
final storageUri = uploaded['storageUri'];
|
|
106
114
|
if (storageUri == null || storageUri.isEmpty) {
|
|
@@ -125,6 +133,7 @@ Future<Bundle> deployBundle(Backend backend, DeployOptions opts) async {
|
|
|
125
133
|
rolloutCohortCount: defaultRolloutCohortCount,
|
|
126
134
|
);
|
|
127
135
|
|
|
136
|
+
opts.onPhase?.call('Registering bundle in database');
|
|
128
137
|
await backend.db.appendBundle(bundle);
|
|
129
138
|
await backend.db.commitBundle();
|
|
130
139
|
return bundle;
|
|
@@ -302,11 +302,115 @@ class Steps {
|
|
|
302
302
|
final parts = <String>[];
|
|
303
303
|
if (_done > 0) parts.add('${_done} completed');
|
|
304
304
|
if (_skipped > 0) parts.add('${_skipped} skipped');
|
|
305
|
-
if (_failed > 0)
|
|
305
|
+
if (_failed > 0) {
|
|
306
|
+
parts.add('${red('${_failed} error${_failed > 1 ? 's' : ''}')}');
|
|
307
|
+
}
|
|
306
308
|
if (parts.isEmpty) parts.add('no steps');
|
|
307
309
|
parts.add(time);
|
|
308
310
|
box('summary', [parts.join(' · ')]);
|
|
309
311
|
}
|
|
312
|
+
|
|
313
|
+
/// Run a step that shows a spinner while working, then marks it
|
|
314
|
+
/// success/fail/skip based on the result. This is the recommended way to
|
|
315
|
+
/// show multi-phase work where each phase has a distinct label the user
|
|
316
|
+
/// can understand.
|
|
317
|
+
///
|
|
318
|
+
/// Example:
|
|
319
|
+
/// ```dart
|
|
320
|
+
/// final s = Steps('deploy');
|
|
321
|
+
/// final zipped = await s.run('Zipping source', () async {
|
|
322
|
+
/// return await zipDir(source);
|
|
323
|
+
/// });
|
|
324
|
+
/// final uploaded = await s.run('Uploading to storage', () async {
|
|
325
|
+
/// return await backend.putObject(bucket, zipped);
|
|
326
|
+
/// });
|
|
327
|
+
/// await s.run('Registering bundle', () async {
|
|
328
|
+
/// return await backend.insertBundle(...);
|
|
329
|
+
/// });
|
|
330
|
+
/// s.summary();
|
|
331
|
+
/// ```
|
|
332
|
+
Future<T> run<T>(String label, Future<T> Function() task) async {
|
|
333
|
+
if (!_colorOn) {
|
|
334
|
+
_echo(' ${dim('•')} $label');
|
|
335
|
+
try {
|
|
336
|
+
final r = await task();
|
|
337
|
+
_done++;
|
|
338
|
+
_echo(' ${green('✓')} $label');
|
|
339
|
+
return r;
|
|
340
|
+
} catch (e) {
|
|
341
|
+
_failed++;
|
|
342
|
+
_echo(' ${red('✗')} $label');
|
|
343
|
+
rethrow;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
var i = 0;
|
|
347
|
+
final sw = Stopwatch()..start();
|
|
348
|
+
final timer = Timer.periodic(const Duration(milliseconds: 80), (_) {
|
|
349
|
+
i = (i + 1) % _frames.length;
|
|
350
|
+
stdout.write('\r\x1b[K ${cyan(_frames[i])} $label');
|
|
351
|
+
});
|
|
352
|
+
try {
|
|
353
|
+
final r = await task();
|
|
354
|
+
final elapsed = sw.elapsedMilliseconds;
|
|
355
|
+
if (elapsed < 200) {
|
|
356
|
+
await Future.delayed(Duration(milliseconds: 200 - elapsed));
|
|
357
|
+
}
|
|
358
|
+
timer.cancel();
|
|
359
|
+
sw.stop();
|
|
360
|
+
stdout.write('\r\x1b[K ${green('✓')} $label\n');
|
|
361
|
+
_done++;
|
|
362
|
+
return r;
|
|
363
|
+
} catch (e) {
|
|
364
|
+
timer.cancel();
|
|
365
|
+
stdout.write('\r\x1b[K ${red('✗')} $label\n');
|
|
366
|
+
_failed++;
|
|
367
|
+
rethrow;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
Timer? _activeTimer;
|
|
372
|
+
int _activeIdx = 0;
|
|
373
|
+
String? _activeLabel;
|
|
374
|
+
|
|
375
|
+
/// Print an "active" phase line (⠹ label) that will be replaced in-place
|
|
376
|
+
/// by the next call to [startActive] or finalized by [completeActive].
|
|
377
|
+
/// Use this when a long-running operation reports multiple internal phases
|
|
378
|
+
/// via a callback (e.g. `DeployOptions.onPhase`).
|
|
379
|
+
void startActive(String label) {
|
|
380
|
+
_activeLabel = label;
|
|
381
|
+
if (!_colorOn) {
|
|
382
|
+
_echo(' ${dim('•')} $label');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
_activeIdx = 0;
|
|
386
|
+
_activeTimer?.cancel();
|
|
387
|
+
_activeTimer = Timer.periodic(const Duration(milliseconds: 80), (_) {
|
|
388
|
+
_activeIdx = (_activeIdx + 1) % _frames.length;
|
|
389
|
+
stdout.write('\r\x1b[K ${cyan(_frames[_activeIdx])} $label');
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/// Finalize the current active line. If [success] is true (default) the
|
|
394
|
+
/// line becomes `✓ label` and counts toward `_done`. If false, it becomes
|
|
395
|
+
/// `✗ label` and counts toward `_failed`. Call this when the long-running
|
|
396
|
+
/// operation finishes.
|
|
397
|
+
void completeActive({bool success = true}) {
|
|
398
|
+
final label = _activeLabel;
|
|
399
|
+
if (label == null) return;
|
|
400
|
+
_activeTimer?.cancel();
|
|
401
|
+
_activeTimer = null;
|
|
402
|
+
_activeLabel = null;
|
|
403
|
+
if (_colorOn) {
|
|
404
|
+
stdout.write(
|
|
405
|
+
'\r\x1b[K ${success ? green('✓') : red('✗')} $label\n',
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (success) {
|
|
409
|
+
_done++;
|
|
410
|
+
} else {
|
|
411
|
+
_failed++;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
310
414
|
}
|
|
311
415
|
|
|
312
416
|
// ── Progress Bar (thin, single-line) ────────────────────────────────────────
|
|
@@ -3,7 +3,7 @@ description: >-
|
|
|
3
3
|
Command-line interface for flutter_ota_kit (a hot-updater compatible OTA code
|
|
4
4
|
push tool for Flutter). Deploy bundles, manage channels/rollbacks, run
|
|
5
5
|
migrations, and inspect the backend — mirroring the `hot-updater` CLI UX.
|
|
6
|
-
version: 0.1.
|
|
6
|
+
version: 0.1.1
|
|
7
7
|
publish_to: none
|
|
8
8
|
|
|
9
9
|
environment:
|
|
@@ -17,13 +17,13 @@ dependencies:
|
|
|
17
17
|
collection: ^1.18.0
|
|
18
18
|
cryptography: ^2.7.0
|
|
19
19
|
postgres: ^3.4.0
|
|
20
|
-
flutter_ota_kit_core: ^0.1.
|
|
21
|
-
flutter_ota_kit_plugin_core: ^0.0.
|
|
22
|
-
flutter_ota_kit_supabase: ^0.1.
|
|
23
|
-
flutter_ota_kit_postgres: ^0.1.
|
|
24
|
-
flutter_ota_kit_cloudflare: ^0.1.
|
|
25
|
-
flutter_ota_kit_aws: ^0.1.
|
|
26
|
-
flutter_ota_kit_pocketbase: ^0.1.
|
|
20
|
+
flutter_ota_kit_core: ^0.1.6
|
|
21
|
+
flutter_ota_kit_plugin_core: ^0.0.8
|
|
22
|
+
flutter_ota_kit_supabase: ^0.1.8
|
|
23
|
+
flutter_ota_kit_postgres: ^0.1.7
|
|
24
|
+
flutter_ota_kit_cloudflare: ^0.1.7
|
|
25
|
+
flutter_ota_kit_aws: ^0.1.7
|
|
26
|
+
flutter_ota_kit_pocketbase: ^0.1.4
|
|
27
27
|
http: ^1.2.0
|
|
28
28
|
shelf: ^1.4.0
|
|
29
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: flutter_ota_kit_core
|
|
2
2
|
description: Core data model, rollout cohorts, and shared utilities for flutter_ota_kit — Dart translation of hot-updater's packages/core.
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.6
|
|
4
4
|
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
5
|
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
6
|
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: flutter_ota_kit_aws
|
|
2
2
|
description: AWS S3 storage + S3/DynamoDB database plugins for flutter_ota_kit (faithful port of hot-updater plugins/aws).
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.7
|
|
4
4
|
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
5
|
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
6
|
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
@@ -11,8 +11,8 @@ environment:
|
|
|
11
11
|
sdk: ">=3.13.2 <4.0.0"
|
|
12
12
|
|
|
13
13
|
dependencies:
|
|
14
|
-
flutter_ota_kit_core: ^0.1.
|
|
15
|
-
flutter_ota_kit_plugin_core: ^0.0.
|
|
14
|
+
flutter_ota_kit_core: ^0.1.6
|
|
15
|
+
flutter_ota_kit_plugin_core: ^0.0.8
|
|
16
16
|
crypto: ^3.0.0
|
|
17
17
|
http: ^1.2.0
|
|
18
18
|
path: ^1.9.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: flutter_ota_kit_cloudflare
|
|
2
2
|
description: Cloudflare D1 + R2 storage plugins for flutter_ota_kit (faithful port of hot-updater plugins/cloudflare).
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.7
|
|
4
4
|
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
5
|
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
6
|
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
@@ -11,8 +11,8 @@ environment:
|
|
|
11
11
|
sdk: ">=3.13.2 <4.0.0"
|
|
12
12
|
|
|
13
13
|
dependencies:
|
|
14
|
-
flutter_ota_kit_core: ^0.1.
|
|
15
|
-
flutter_ota_kit_plugin_core: ^0.0.
|
|
14
|
+
flutter_ota_kit_core: ^0.1.6
|
|
15
|
+
flutter_ota_kit_plugin_core: ^0.0.8
|
|
16
16
|
crypto: ^3.0.0
|
|
17
17
|
http: ^1.2.0
|
|
18
18
|
path: ^1.9.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: flutter_ota_kit_plugin_core
|
|
2
2
|
description: Plugin core abstractions for flutter_ota_kit — database, storage, and build interfaces.
|
|
3
|
-
version: 0.0.
|
|
3
|
+
version: 0.0.8
|
|
4
4
|
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
5
|
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
6
|
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
@@ -11,7 +11,7 @@ environment:
|
|
|
11
11
|
sdk: ">=3.13.2 <4.0.0"
|
|
12
12
|
|
|
13
13
|
dependencies:
|
|
14
|
-
flutter_ota_kit_core: ^0.1.
|
|
14
|
+
flutter_ota_kit_core: ^0.1.6
|
|
15
15
|
mime: ^2.0.0
|
|
16
16
|
|
|
17
17
|
dev_dependencies:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: flutter_ota_kit_pocketbase
|
|
2
2
|
description: PocketBase plugin for flutter_ota_kit — local-first self-hostable backend with admin UI, auth, file storage, and realtime.
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.4
|
|
4
4
|
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
5
|
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
6
|
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
@@ -11,8 +11,8 @@ environment:
|
|
|
11
11
|
sdk: ">=3.13.2 <4.0.0"
|
|
12
12
|
|
|
13
13
|
dependencies:
|
|
14
|
-
flutter_ota_kit_core: ^0.1.
|
|
15
|
-
flutter_ota_kit_plugin_core: ^0.0.
|
|
14
|
+
flutter_ota_kit_core: ^0.1.6
|
|
15
|
+
flutter_ota_kit_plugin_core: ^0.0.8
|
|
16
16
|
http: ^1.2.0
|
|
17
17
|
|
|
18
18
|
dev_dependencies:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: flutter_ota_kit_postgres
|
|
2
2
|
description: Postgres plugin for flutter_ota_kit — database backend.
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.7
|
|
4
4
|
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
5
|
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
6
|
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
@@ -11,8 +11,8 @@ environment:
|
|
|
11
11
|
sdk: ">=3.13.2 <4.0.0"
|
|
12
12
|
|
|
13
13
|
dependencies:
|
|
14
|
-
flutter_ota_kit_core: ^0.1.
|
|
15
|
-
flutter_ota_kit_plugin_core: ^0.0.
|
|
14
|
+
flutter_ota_kit_core: ^0.1.6
|
|
15
|
+
flutter_ota_kit_plugin_core: ^0.0.8
|
|
16
16
|
postgres: ^3.4.0
|
|
17
17
|
|
|
18
18
|
dev_dependencies:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: flutter_ota_kit_supabase
|
|
2
2
|
description: Supabase plugin for flutter_ota_kit — database + storage backends.
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.8
|
|
4
4
|
homepage: https://github.com/HYPER12755/flutter-ota-kit
|
|
5
5
|
repository: https://github.com/HYPER12755/flutter-ota-kit
|
|
6
6
|
issue_tracker: https://github.com/HYPER12755/flutter-ota-kit/issues
|
|
@@ -11,8 +11,8 @@ environment:
|
|
|
11
11
|
sdk: ">=3.13.2 <4.0.0"
|
|
12
12
|
|
|
13
13
|
dependencies:
|
|
14
|
-
flutter_ota_kit_core: ^0.1.
|
|
15
|
-
flutter_ota_kit_plugin_core: ^0.0.
|
|
14
|
+
flutter_ota_kit_core: ^0.1.6
|
|
15
|
+
flutter_ota_kit_plugin_core: ^0.0.8
|
|
16
16
|
http: ^1.2.0
|
|
17
17
|
supabase: ^2.14.0
|
|
18
18
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@_nazmiforreal/flutter-ota",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Command-line interface for flutter_ota_kit
|
|
3
|
+
"version": "0.1.19",
|
|
4
|
+
"description": "Command-line interface for flutter_ota_kit \u2014 an OTA code-push tool for Flutter, hot-updater compatible.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"flutter-ota": "./bin/flutter-ota.js"
|
|
7
7
|
},
|
|
@@ -23,4 +23,4 @@
|
|
|
23
23
|
"type": "git",
|
|
24
24
|
"url": "https://github.com/HYPER12755/flutter-ota-kit"
|
|
25
25
|
}
|
|
26
|
-
}
|
|
26
|
+
}
|