Skip to content

Commit

Permalink
feat(bloc_tools): automatic update support (felangel#2861)
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel authored Oct 13, 2021
1 parent f640e75 commit dfcee91
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/bloc_tools/example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ $ dart pub global activate bloc_tools
# See list of available commands

```sh
$ bloc help
$ bloc --help
```
51 changes: 47 additions & 4 deletions packages/bloc_tools/lib/src/command_runner.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:bloc_tools/src/version.dart';
import 'package:io/ansi.dart';
import 'package:io/io.dart';
import 'package:mason/mason.dart' show Logger;
import 'package:pub_updater/pub_updater.dart';

/// The package name.
const packageName = 'bloc_tools';

/// {@template bloc_tools_command_runner}
/// A [CommandRunner] for the Bloc Tools CLI.
/// {@endtemplate}
class BlocToolsCommandRunner extends CommandRunner<int> {
/// {@macro bloc_tools_command_runner}
BlocToolsCommandRunner({Logger? logger})
BlocToolsCommandRunner({Logger? logger, PubUpdater? pubUpdater})
: _logger = logger ?? Logger(),
_pubUpdater = pubUpdater ?? PubUpdater(),
super('bloc', 'Command Line Tools for the Bloc Library.') {
argParser.addFlag(
'version',
Expand All @@ -20,6 +26,7 @@ class BlocToolsCommandRunner extends CommandRunner<int> {
}

final Logger _logger;
final PubUpdater _pubUpdater;

@override
Future<int> run(Iterable<String> args) async {
Expand All @@ -44,10 +51,46 @@ class BlocToolsCommandRunner extends CommandRunner<int> {

@override
Future<int?> runCommand(ArgResults topLevelResults) async {
int? exitCode = ExitCode.unavailable.code;
if (topLevelResults['version'] == true) {
_logger.info('bloc_tools version: $packageVersion');
return ExitCode.success.code;
_logger.info(packageVersion);
exitCode = ExitCode.success.code;
} else {
exitCode = await super.runCommand(topLevelResults);
}
return super.runCommand(topLevelResults);
await _checkForUpdates();
return exitCode;
}

Future<void> _checkForUpdates() async {
try {
final latestVersion = await _pubUpdater.getLatestVersion(packageName);
final isUpToDate = packageVersion == latestVersion;
if (!isUpToDate) {
_logger
..info('')
..info('''
+------------------------------------------------------------------------------------+
| |
| ${lightYellow.wrap('Update available!')} ${lightCyan.wrap(packageVersion)} \u2192 ${lightCyan.wrap(latestVersion)} |
| ${lightYellow.wrap('Changelog:')} ${lightCyan.wrap('https://github.com/felangel/bloc/releases/tag/$packageName-v$latestVersion')} |
| |
+------------------------------------------------------------------------------------+
''');
final response = _logger.prompt('Would you like to update? (y/n) ');
if (response.isYes()) {
final updateDone = _logger.progress('Updating to $latestVersion');
await _pubUpdater.update(packageName: packageName);
updateDone('Updated to $latestVersion');
}
}
} catch (_) {}
}
}

extension on String {
bool isYes() {
final normalized = toLowerCase().trim();
return normalized == 'y' || normalized == 'yes';
}
}
1 change: 1 addition & 0 deletions packages/bloc_tools/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
args: ^2.1.0
io: ^1.0.0
mason: ^0.0.1-dev.51
pub_updater: ^0.2.0
universal_io: ^2.0.4

dev_dependencies:
Expand Down
73 changes: 71 additions & 2 deletions packages/bloc_tools/test/src/command_runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ import 'dart:async';
import 'package:args/command_runner.dart';
import 'package:bloc_tools/src/command_runner.dart';
import 'package:bloc_tools/src/version.dart';
import 'package:io/ansi.dart';
import 'package:io/io.dart';
import 'package:mason/mason.dart' show Logger;
import 'package:mocktail/mocktail.dart';
import 'package:pub_updater/pub_updater.dart';
import 'package:test/test.dart';
import 'package:universal_io/io.dart';

class MockLogger extends Mock implements Logger {}

class MockPubUpdater extends Mock implements PubUpdater {}

class FakeProcessResult extends Fake implements ProcessResult {}

const expectedUsage = [
'Command Line Tools for the Bloc Library.\n'
'\n'
Expand All @@ -26,10 +33,22 @@ const expectedUsage = [
'Run "bloc help <command>" for more information about a command.'
];

final updatePrompt = '''
+------------------------------------------------------------------------------------+
| |
| ${lightYellow.wrap('Update available!')} ${lightCyan.wrap(packageVersion)} \u2192 ${lightCyan.wrap(latestVersion)} |
| ${lightYellow.wrap('Changelog:')} ${lightCyan.wrap('https://github.com/felangel/bloc/releases/tag/$packageName-v$latestVersion')} |
| |
+------------------------------------------------------------------------------------+
''';

const latestVersion = '0.0.0';

void main() {
group('BlocToolsCommandRunner', () {
late List<String> printLogs;
late Logger logger;
late PubUpdater pubUpdater;
late BlocToolsCommandRunner commandRunner;

void Function() overridePrint(void Function() fn) {
Expand All @@ -44,7 +63,19 @@ void main() {
setUp(() {
printLogs = [];
logger = MockLogger();
commandRunner = BlocToolsCommandRunner(logger: logger);
pubUpdater = MockPubUpdater();

when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => packageVersion);
when(
() => pubUpdater.update(packageName: packageName),
).thenAnswer((_) => Future.value(FakeProcessResult()));

commandRunner = BlocToolsCommandRunner(
logger: logger,
pubUpdater: pubUpdater,
);
});

test('can be instantiated without an explicit logger instance', () {
Expand All @@ -53,6 +84,44 @@ void main() {
});

group('run', () {
test('prompts for update when newer version exists', () async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);

when(() => logger.prompt(any())).thenReturn('n');

final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.success.code));
verify(() => logger.info(updatePrompt)).called(1);
verify(
() => logger.prompt('Would you like to update? (y/n) '),
).called(1);
});

test('handles pub update errors gracefully', () async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenThrow(Exception('oops'));

final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.success.code));
verifyNever(() => logger.info(updatePrompt));
});

test('updates on "y" response when newer version exists', () async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);

when(() => logger.prompt(any())).thenReturn('y');
when(() => logger.progress(any())).thenReturn(([String? message]) {});

final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.success.code));
verify(() => logger.progress('Updating to $latestVersion')).called(1);
});

test('handles FormatException', () async {
const exception = FormatException('oops!');
var isFirstInvocation = true;
Expand Down Expand Up @@ -107,7 +176,7 @@ void main() {
test('outputs current version', () async {
final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.success.code));
verify(() => logger.info('bloc_tools version: $packageVersion'));
verify(() => logger.info(packageVersion)).called(1);
});
});
});
Expand Down

0 comments on commit dfcee91

Please sign in to comment.