diff --git a/packages/flutterfire_cli/bin/flutterfire.dart b/packages/flutterfire_cli/bin/flutterfire.dart index 40248161..3a580fac 100755 --- a/packages/flutterfire_cli/bin/flutterfire.dart +++ b/packages/flutterfire_cli/bin/flutterfire.dart @@ -26,7 +26,7 @@ import 'package:flutterfire_cli/version.g.dart'; import 'package:pub_updater/pub_updater.dart'; Future main(List arguments) async { - if (arguments.contains('--version') || arguments.contains('-v')) { + if (isCliVersionRequest(arguments)) { print(cliVersion); // No version checks on CIs. if (utils.isCI) return; @@ -74,3 +74,11 @@ Future main(List arguments) async { rethrow; } } + +/// Whether the arguments request the FlutterFire CLI version. +/// +/// Only a standalone version flag belongs to the CLI. Subcommands may define +/// their own version option, such as the BoM version accepted by `install`. +bool isCliVersionRequest(List arguments) => + arguments.length == 1 && + (arguments.single == '--version' || arguments.single == '-v'); diff --git a/packages/flutterfire_cli/lib/src/commands/install.dart b/packages/flutterfire_cli/lib/src/commands/install.dart index 1273e94a..8a46c9eb 100644 --- a/packages/flutterfire_cli/lib/src/commands/install.dart +++ b/packages/flutterfire_cli/lib/src/commands/install.dart @@ -19,6 +19,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:ansi_styles/ansi_styles.dart'; +import 'package:args/args.dart'; import 'package:collection/collection.dart'; import '../common/utils.dart'; @@ -85,6 +86,28 @@ enum FlutterFirePlugins { FlutterFirePlugins.values.map((plugin) => plugin.name).toList(); } +/// Maps prompt selection indexes to the exact plugin list shown to the user. +/// +/// The prompt excludes plugins that are unavailable in the selected BoM, so +/// its indexes must not be applied to the unfiltered [FlutterFirePlugins] +/// values. +List pluginsFromSelectionIndexes( + List availablePlugins, + List selectedIndexes, +) => selectedIndexes.map((index) => availablePlugins[index]).toList(); + +/// Returns the BoM version supplied as a positional argument or option. +/// +/// [ArgResults.rest] contains only positional arguments, unlike +/// [ArgResults.arguments], which also contains command options. +String? bomVersionFromArguments(ArgResults arguments) { + if (arguments.rest.length == 1) { + return arguments.rest.single; + } + + return arguments['version'] as String?; +} + class InstallCommand extends FlutterFireCommand { InstallCommand(FlutterApp? flutterApp) : super(flutterApp) { setupDefaultFirebaseCliOptions(); @@ -138,12 +161,15 @@ class InstallCommand extends FlutterFireCommand { return selectedPlugins; } - final selectedPlugins = []; final listAvailablePluginsInVersion = availablePlugins.keys.toList(); - final choices = FlutterFirePlugins.values + // Keep the filtered enum values as well as their labels so prompt indexes + // can be resolved against the same list the user sees. + final availablePluginChoices = FlutterFirePlugins.values .where( (element) => listAvailablePluginsInVersion.contains(element.name), ) + .toList(); + final choices = availablePluginChoices .map((plugin) => plugin.displayName) .toList(); final defaultSelection = List.filled(choices.length, false); @@ -164,10 +190,7 @@ class InstallCommand extends FlutterFireCommand { choices, defaultSelection: defaultSelection, ); - for (final index in selectedChoices) { - selectedPlugins.add(FlutterFirePlugins.values[index]); - } - return selectedPlugins; + return pluginsFromSelectionIndexes(availablePluginChoices, selectedChoices); } Future> _getPluginVersionsFromJSON( @@ -204,13 +227,8 @@ class InstallCommand extends FlutterFireCommand { @override Future run() async { // Get the BoM version number from the arguments - late String bomVersion; - - if (argResults?.arguments.length == 1) { - bomVersion = argResults!.arguments[0]; - } else if (argResults?['version'] != null) { - bomVersion = argResults!['version'] as String; - } else { + final bomVersion = bomVersionFromArguments(argResults!); + if (bomVersion == null) { stderr.writeln( 'Usage for install command: flutterfire install ', ); diff --git a/packages/flutterfire_cli/test/install_unit_test.dart b/packages/flutterfire_cli/test/install_unit_test.dart new file mode 100644 index 00000000..62b24a3a --- /dev/null +++ b/packages/flutterfire_cli/test/install_unit_test.dart @@ -0,0 +1,52 @@ +import 'package:args/args.dart'; +import 'package:flutterfire_cli/src/commands/install.dart'; +import 'package:test/test.dart'; + +import '../bin/flutterfire.dart'; + +void main() { + group('install plugin selection', () { + test('maps prompt indexes against the available plugins', () { + final availablePlugins = [ + for (final plugin in FlutterFirePlugins.values) + if (plugin != FlutterFirePlugins.dynamicLinks) plugin, + ]; + + expect(pluginsFromSelectionIndexes(availablePlugins, [9, 10, 13]), [ + FlutterFirePlugins.inAppMessaging, + FlutterFirePlugins.messaging, + FlutterFirePlugins.remoteConfig, + ]); + }); + }); + + group('install version arguments', () { + final parser = ArgParser() + ..addOption('version') + ..addFlag('only-pubspec-plugins', negatable: false); + + test('accepts a positional version with options', () { + final results = parser.parse(['4.17.1', '--only-pubspec-plugins']); + + expect(bomVersionFromArguments(results), '4.17.1'); + }); + + test('accepts the command version option', () { + final results = parser.parse(['--version', '4.17.1']); + + expect(bomVersionFromArguments(results), '4.17.1'); + }); + + test( + 'only treats a standalone global version flag as a CLI version request', + () { + expect(isCliVersionRequest(['--version']), isTrue); + expect(isCliVersionRequest(['-v']), isTrue); + expect( + isCliVersionRequest(['install', '--version', '4.17.1']), + isFalse, + ); + }, + ); + }); +}