LCOV - code coverage report
Current view: top level - src - command_runner.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 91 91 100.0 %
Date: 2023-11-15 10:29:52 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:args/args.dart';
       2             : import 'package:args/command_runner.dart';
       3             : import 'package:cli_completion/cli_completion.dart';
       4             : import 'package:mason/mason.dart' hide packageVersion;
       5             : import 'package:meta/meta.dart';
       6             : import 'package:path/path.dart' as path;
       7             : import 'package:pub_updater/pub_updater.dart';
       8             : import 'package:universal_io/io.dart';
       9             : import 'package:very_good_cli/src/commands/commands.dart';
      10             : import 'package:very_good_cli/src/logger_extension.dart';
      11             : import 'package:very_good_cli/src/version.dart';
      12             : 
      13             : /// The package name.
      14             : const packageName = 'very_good_cli';
      15             : 
      16             : /// {@template very_good_command_runner}
      17             : /// A [CommandRunner] for the Very Good CLI.
      18             : /// {@endtemplate}
      19             : class VeryGoodCommandRunner extends CompletionCommandRunner<int> {
      20             :   /// {@macro very_good_command_runner}
      21          15 :   VeryGoodCommandRunner({
      22             :     Logger? logger,
      23             :     PubUpdater? pubUpdater,
      24             :     Map<String, String>? environment,
      25           1 :   })  : _logger = logger ?? Logger(),
      26           1 :         _pubUpdater = pubUpdater ?? PubUpdater(),
      27           1 :         _environment = environment ?? Platform.environment,
      28          15 :         super('very_good', '🦄 A Very Good Command-Line Interface') {
      29          15 :     argParser
      30          15 :       ..addFlag(
      31             :         'version',
      32             :         negatable: false,
      33             :         help: 'Print the current version.',
      34             :       )
      35          15 :       ..addFlag(
      36             :         'verbose',
      37             :         help: 'Noisy logging, including all shell commands executed.',
      38             :       );
      39          45 :     addCommand(CreateCommand(logger: _logger));
      40          45 :     addCommand(PackagesCommand(logger: _logger));
      41          45 :     addCommand(TestCommand(logger: _logger));
      42          45 :     addCommand(UpdateCommand(logger: _logger, pubUpdater: pubUpdater));
      43             :   }
      44             : 
      45             :   /// Standard timeout duration for the CLI.
      46             :   static const timeout = Duration(milliseconds: 500);
      47             : 
      48             :   final Logger _logger;
      49             :   final PubUpdater _pubUpdater;
      50             : 
      51             :   /// Map of environments information.
      52          45 :   Map<String, String> get environment => environmentOverride ?? _environment;
      53             :   final Map<String, String> _environment;
      54             : 
      55             :   /// Boolean for checking if windows, which can be overridden for
      56             :   /// testing purposes.
      57             :   @visibleForTesting
      58             :   bool? isWindowsOverride;
      59           3 :   bool get _isWindows => isWindowsOverride ?? Platform.isWindows;
      60             : 
      61           1 :   @override
      62           3 :   void printUsage() => _logger.info(usage);
      63             : 
      64          15 :   @override
      65             :   Future<int> run(Iterable<String> args) async {
      66             :     try {
      67          15 :       final argResults = parse(args);
      68             : 
      69          30 :       if (argResults['verbose'] == true) {
      70           2 :         _logger.level = Level.verbose;
      71             :       }
      72          29 :       return await runCommand(argResults) ?? ExitCode.success.code;
      73           3 :     } on FormatException catch (e, stackTrace) {
      74           1 :       _logger
      75           2 :         ..err(e.message)
      76           2 :         ..err('$stackTrace')
      77           1 :         ..info('')
      78           2 :         ..info(usage);
      79           1 :       return ExitCode.usage.code;
      80           3 :     } on UsageException catch (e) {
      81           3 :       _logger
      82           6 :         ..err(e.message)
      83           3 :         ..info('')
      84           6 :         ..info(e.usage);
      85           3 :       return ExitCode.usage.code;
      86             :     }
      87             :   }
      88             : 
      89          15 :   @override
      90             :   Future<int?> runCommand(ArgResults topLevelResults) async {
      91          45 :     if (topLevelResults.command?.name == 'completion') {
      92           1 :       await super.runCommand(topLevelResults);
      93           1 :       return ExitCode.success.code;
      94             :     }
      95             : 
      96          15 :     _logger
      97          15 :       ..detail('Argument information:')
      98          15 :       ..detail('  Top level options:');
      99          30 :     for (final option in topLevelResults.options) {
     100          15 :       if (topLevelResults.wasParsed(option)) {
     101           4 :         _logger.detail('  - $option: ${topLevelResults[option]}');
     102             :       }
     103             :     }
     104          15 :     if (topLevelResults.command != null) {
     105          15 :       final commandResult = topLevelResults.command!;
     106          15 :       _logger
     107          45 :         ..detail('  Command: ${commandResult.name}')
     108          15 :         ..detail('    Command options:');
     109          30 :       for (final option in commandResult.options) {
     110          15 :         if (commandResult.wasParsed(option)) {
     111          16 :           _logger.detail('    - $option: ${commandResult[option]}');
     112             :         }
     113             :       }
     114             : 
     115          15 :       if (commandResult.command != null) {
     116          10 :         final subCommandResult = commandResult.command!;
     117          40 :         _logger.detail('    Command sub command: ${subCommandResult.name}');
     118             :       }
     119             :     }
     120             : 
     121          15 :     int? exitCode = ExitCode.unavailable.code;
     122          30 :     if (topLevelResults['version'] == true) {
     123           2 :       _logger.info(packageVersion);
     124           1 :       exitCode = ExitCode.success.code;
     125             :     } else {
     126          15 :       exitCode = await super.runCommand(topLevelResults);
     127             :     }
     128          45 :     if (topLevelResults.command?.name != UpdateCommand.commandName) {
     129          14 :       await _checkForUpdates();
     130             :     }
     131          15 :     _showThankYou();
     132             :     return exitCode;
     133             :   }
     134             : 
     135          14 :   Future<void> _checkForUpdates() async {
     136             :     try {
     137          28 :       final latestVersion = await _pubUpdater.getLatestVersion(packageName);
     138           1 :       final isUpToDate = packageVersion == latestVersion;
     139             :       if (!isUpToDate) {
     140           1 :         _logger
     141           1 :           ..info('')
     142           1 :           ..info(
     143             :             '''
     144           3 : ${lightYellow.wrap('Update available!')} ${lightCyan.wrap(packageVersion)} \u2192 ${lightCyan.wrap(latestVersion)}
     145           3 : ${lightYellow.wrap('Changelog:')} ${lightCyan.wrap('https://github.com/verygoodopensource/very_good_cli/releases/tag/v$latestVersion')}
     146           2 : Run ${lightCyan.wrap('very_good update')} to update''',
     147             :           );
     148             :       }
     149             :     } catch (_) {}
     150             :   }
     151             : 
     152          15 :   void _showThankYou() {
     153          30 :     if (environment.containsKey('CI')) return;
     154             : 
     155           1 :     final versionFile = File(
     156           3 :       path.join(_configDir.path, 'version'),
     157           1 :     )..createSync(recursive: true);
     158             : 
     159           2 :     if (versionFile.readAsStringSync() == packageVersion) return;
     160           1 :     versionFile.writeAsStringSync(packageVersion);
     161             : 
     162           2 :     _logger.wrap(
     163           1 :       lightMagenta.wrap('''
     164             : 
     165             : Thank you for using Very Good Ventures open source tools!
     166           4 : Don't forget to fill out this form to get information on future updates and releases here: ${lightBlue.wrap(link(uri: Uri.parse('https://verygood.ventures/open-source/cli/subscribe-latest-tool-updates')))}'''),
     167           2 :       print: _logger.info,
     168             :     );
     169             :   }
     170             : 
     171           1 :   Directory get _configDir {
     172           1 :     if (_isWindows) {
     173             :       // Use localappdata on windows
     174           2 :       final localAppData = environment['LOCALAPPDATA']!;
     175           2 :       return Directory(path.join(localAppData, 'VeryGoodCLI'));
     176             :     } else {
     177             :       // Try using XDG config folder
     178           2 :       var dirPath = environment['XDG_CONFIG_HOME'];
     179             :       // Fallback to $HOME if not following XDG specification
     180           1 :       if (dirPath == null || dirPath.isEmpty) {
     181           2 :         dirPath = environment['HOME'];
     182             :       }
     183           2 :       return Directory(path.join(dirPath!, '.very_good_cli'));
     184             :     }
     185             :   }
     186             : }

Generated by: LCOV version 1.16