Repository: jonbhanson/flutter_native_splash

Issue:
Make flavor generation easier by not having to specify flavors by name
**Is your feature request related to a problem? Please describe.**
It's frustrating that I need to specify the names of the flavors when generating the splash screens for different flavors. 

**Describe the solution you'd like**
Instead of having to do:
`dart run flutter_native_splash:create --flavors development,staging,production`
I would like to be able to do:
`dart run flutter_native_splash:create -f flutter_native_spash*`

**Describe alternatives you've considered**
Currently need to do some manual work and thus need to write some thorough README files and do checks that things are not missed. Could also do some scripts that generate the names and command and then run it. 

**Additional context**
Proposed solution is inspired by the args that `flutter_launcher_icons` takes for flavors setup, which would make things easier. 


Discussion: Feel free to add a PR, or @vlazdra may be interested in adding this.
This should be pretty straightforward, might be a good start as a first issue if you'd like to open a PR @petcupaula ? 😃 

If not, let me know so I can extend this feature. 😃 
@vlazdra Feel free to go ahead with it 😃
Ok. 😃 

Just to be clear you'd like to have the ability to not specify the exact config file but instead provide the asterisks and for the tool to scan all the files and use all that were found as flavors and generate the splash screen for all of them? @petcupaula 

That way, if you maybe have many flavors, you wouldn't need to specify the exact file names/flavor names and miss some, and in the future if you add new ones, you won't need to adapt the run script? Am I understanding you correctly on this?
Yes, exactly! A match on all files with the right prefix (flutter_native_splash*) and then splash screens are generated for everything defined in those files. 

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb85e0c..488cc93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## [2.4.4] - ()
+
+- Added the ability to generate all flavors with a single flag. Closes [#751](https://github.com/jonbhanson/flutter_native_splash/issues/751)
+
 ## [2.4.3] - (2024-Nov-17)
 
 - Add Swift Package Manager support.  Closes [#749](https://github.com/jonbhanson/flutter_native_splash/issues/749).
diff --git a/README.md b/README.md
index bfdc8f5..f02577f 100644
--- a/README.md
+++ b/README.md
@@ -194,10 +194,20 @@ When the package finishes running, your splash screen is ready.
 
 (Optionally), If you added your config to a separate YAML file instead of `pubspec.yaml`, just add --path with the command in the terminal:
 
-```
+```bash
 dart run flutter_native_splash:create --path=path/to/my/file.yaml
 ```
 
+| Command                | Description                                                                                                                                    |
+| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
+| -h, --[no-]help        | Show help                                                                                                                                      |
+| -p, --path             | Path to the flutter project, if the project is not in it's default location.                                                                   |
+| -f, --flavor           | Flavor to create the splash for. The flavor must match the pattern flutter_native_splash-*.yaml (where * is the flavor name).                  |
+| -F, --flavors          | Comma separated list of flavors to create the splash screens for. Match the pattern flutter_native_splash-*.yaml (where * is the flavor name). |
+| -A, --[no-]all-flavors | Create the splash screens for all flavors that match the pattern flutter_native_splash-*.yaml (where * is the flavor name).                    |
+
+> Note: Only one flavor option is allowed.
+
 ## 3. Set up app initialization (optional)
 
 By default, the splash screen will be removed when Flutter has drawn the first frame. If you would like the splash screen to remain while your app initializes, you can use the `preserve()` and `remove()` methods together. Pass the `preserve()` method the value returned from `WidgetsFlutterBinding.ensureInitialized()` to keep the splash on screen. Later, when your app has initialized, make a call to `remove()` to remove the splash screen.
@@ -255,15 +265,15 @@ If you have a project setup that contains multiple flavors or environments, and
 
 Instead of maintaining multiple files and copy/pasting images, you can now, using this tool, create different splash screens for different environments.
 
-### Pre-requirements
+## Pre-requirements
 
-In order to use the new feature, and generate the desired splash images for you app, a couple of changes are required.
+In order to use this feature, and generate the desired splash images for your app, a couple of changes are required.
 
 If you want to generate just one flavor and one file you would use either options as described in Step 1. But in order to setup the flavors, you will then be required to move all your setup values to the `flutter_native_splash.yaml` file, but with a prefix.
 
 Let's assume for the rest of the setup that you have 3 different flavors, `Production`, `Acceptance`, `Development`.
 
-First this you will need to do is to create a different setup file for all 3 flavors with a suffix like so:
+First thing you will need to do is to create a different setup file for all 3 flavors with a suffix like so:
 
 ```bash
 flutter_native_splash-production.yaml
@@ -271,7 +281,7 @@ flutter_native_splash-acceptance.yaml
 flutter_native_splash-development.yaml
 ```
 
-You would setup those 3 files the same way as you would the one, but with different assets depending on which environment you would be generating. For example (Note: these are just examples, you can use whatever setup you need for your project that is already supported by the package):
+You would setup those 3 files the same way as you would the one, but with different assets depending on which environment you would be generating. For example:
 
 ```yaml
 # flutter_native_splash-development.yaml
@@ -328,6 +338,10 @@ flutter_native_splash:
   web: false
 ```
 
+> Note: these are just example values. You should substitute them with real values.
+
+## One by one
+
 If you'd like to generate only a single flavor (maybe you are
 testing something out), you can use only the single command like this:
 
@@ -342,6 +356,8 @@ dart run flutter_native_splash:create --flavor acceptance
 dart run flutter_native_splash:create --flavor development
 ```
 
+## More than one
+
 You also have the ability to specify all the flavors in one command
 as shown bellow:
 
@@ -349,7 +365,19 @@ as shown bellow:
 dart run flutter_native_splash:create --flavors development,staging,production
 ```
 
-Note: the available flavors need to be comma separated for this option to work.
+> Note: the available flavors need to be comma separated for this option to work.
+
+## All flavors
+
+And if you have many different flavors available in your project, and wish to generate the splash screen for all of them, you can use this command (starting from 2.4.4):
+
+```bash
+dart run flutter_native_splash:create --all-flavors
+# OR you can use the shorthand option
+dart run flutter_native_splash:create -A
+```
+
+This will take all files from the root of the project, scan through them and match for the pattern `flutter_native_splash-*.yaml` where the value at the place of the star will be used as the flavor name and will be consumed to generate the files.
 
 ### Android setup
 
diff --git a/bin/create.dart b/bin/create.dart
index 45f620b..f509f19 100644
--- a/bin/create.dart
+++ b/bin/create.dart
@@ -1,35 +1,98 @@
+import 'dart:io';
+
 import 'package:args/args.dart';
 import 'package:flutter_native_splash/cli_commands.dart';
+import 'package:flutter_native_splash/enums.dart';
+import 'package:flutter_native_splash/helper_utils.dart';
 
 void main(List<String> args) {
   final parser = ArgParser();
 
-  parser.addOption('path');
-  parser.addOption('flavor');
-  parser.addOption('flavors');
+  parser
+    ..addFlag(
+      ArgEnums.help.name,
+      abbr: ArgEnums.help.abbr,
+      help: 'Show help',
+    )
+    ..addOption(
+      ArgEnums.path.name,
+      abbr: ArgEnums.path.abbr,
+      help:
+          'Path to the flutter project, if the project is not in it\'s default location.',
+    )
+    ..addOption(
+      ArgEnums.flavor.name,
+      abbr: ArgEnums.flavor.abbr,
+      help:
+          'Flavor to create the splash for. The flavor must match the pattern flutter_native_splash-*.yaml (where * is the flavor name).',
+    )
+    ..addOption(
+      ArgEnums.flavors.name,
+      abbr: ArgEnums.flavors.abbr,
+      help:
+          'Comma separated list of flavors to create the splash screens for. Match the pattern flutter_native_splash-*.yaml (where * is the flavor name).',
+    )
+    ..addFlag(
+      ArgEnums.allFlavors.name,
+      abbr: ArgEnums.allFlavors.abbr,
+      help:
+          'Create the splash screens for all flavors that match the pattern flutter_native_splash-*.yaml (where * is the flavor name).',
+    );
 
   final parsedArgs = parser.parse(args);
 
-  if (parsedArgs['flavor'] != null && parsedArgs['flavors'] != null) {
-    throw Exception('Cannot use both flavor and flavors arguments');
+  final helpArg = parsedArgs[ArgEnums.help.name] as bool?;
+
+  if (helpArg == true) {
+    print(parser.usage);
+    return;
   }
 
-  if (parsedArgs['flavor'] != null) {
+  final pathArg = parsedArgs[ArgEnums.path.name]?.toString();
+  final flavorArg = parsedArgs[ArgEnums.flavor.name]?.toString();
+  final flavorsArg = parsedArgs[ArgEnums.flavors.name]?.toString();
+  final allFlavorsArg = parsedArgs[ArgEnums.allFlavors.name] as bool?;
+
+  // Validate the flavor arguments
+  HelperUtils.validateFlavorArgs(
+    flavorArg: flavorArg,
+    flavorsArg: flavorsArg,
+    allFlavorsArg: allFlavorsArg,
+  );
+
+  if (flavorArg != null) {
     createSplash(
-      path: parsedArgs['path']?.toString(),
-      flavor: parsedArgs['flavor']?.toString(),
+      path: pathArg,
+      flavor: flavorArg,
     );
-  } else if (parsedArgs['flavors'] != null) {
-    final flavors = parsedArgs['flavors']?.toString().split(',');
-    for (final flavor in flavors!) {
+  } else if (flavorsArg != null) {
+    for (final flavor in flavorsArg.split(',')) {
+      createSplash(
+        path: pathArg,
+        flavor: flavor,
+      );
+    }
+  } else if (allFlavorsArg == true) {
+    // Find all flavor configurations in current project directory
+    final flavors = Directory.current
+        .listSync()
+        .whereType<File>()
+        .map((entity) => entity.path.split(Platform.pathSeparator).last)
+        .where(HelperUtils.isValidFlavorConfigFileName)
+        .map(HelperUtils.getFlavorNameFromFileName)
+        .toList();
+
+    print('Found ${flavors.length} flavor configurations: $flavors');
+
+    for (final flavor in flavors) {
       createSplash(
-        path: parsedArgs['path']?.toString(),
+        path: pathArg,
         flavor: flavor,
       );
     }
   } else {
     createSplash(
-      path: parsedArgs['path']?.toString(),
+      path: pathArg,
       flavor: null,
     );
   }
diff --git a/bin/remove.dart b/bin/remove.dart
index b904ca8..d989b1c 100644
--- a/bin/remove.dart
+++ b/bin/remove.dart
@@ -1,16 +1,39 @@
 import 'package:args/args.dart';
 import 'package:flutter_native_splash/cli_commands.dart';
+import 'package:flutter_native_splash/enums.dart';
 
 void main(List<String> args) {
   final parser = ArgParser();
 
-  parser.addOption('path');
-  parser.addOption('flavor');
+  parser
+    ..addFlag(
+      ArgEnums.help.name,
+      abbr: ArgEnums.help.abbr,
+      help: 'Show help',
+    )
+    ..addOption(
+      ArgEnums.path.name,
+      abbr: ArgEnums.path.abbr,
+      help:
+          'Path to the flutter project, if the project is not in it\'s default location.',
+    )
+    ..addOption(
+      ArgEnums.flavor.name,
+      abbr: ArgEnums.flavor.abbr,
+      help: 'Flavor to remove the splash for.',
+    );
 
   final parsedArgs = parser.parse(args);
 
+  final helpArg = parsedArgs[ArgEnums.help.name];
+
+  if (helpArg != null) {
+    print(parser.usage);
+    return;
+  }
+
   removeSplash(
-    path: parsedArgs['path']?.toString(),
-    flavor: parsedArgs['flavor']?.toString(),
+    path: parsedArgs[ArgEnums.path.name]?.toString(),
+    flavor: parsedArgs[ArgEnums.flavor.name]?.toString(),
   );
 }
diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig
index 592ceee..ec97fc6 100644
--- a/example/ios/Flutter/Debug.xcconfig
+++ b/example/ios/Flutter/Debug.xcconfig
@@ -1,1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
 #include "Generated.xcconfig"
diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig
index 592ceee..c4855bf 100644
--- a/example/ios/Flutter/Release.xcconfig
+++ b/example/ios/Flutter/Release.xcconfig
@@ -1,1 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
 #include "Generated.xcconfig"
diff --git a/example/ios/Podfile b/example/ios/Podfile
new file mode 100644
index 0000000..d97f17e
--- /dev/null
+++ b/example/ios/Podfile
@@ -0,0 +1,44 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '12.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+  use_frameworks!
+  use_modular_headers!
+
+  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+  target 'RunnerTests' do
+    inherit! :search_paths
+  end
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_ios_build_settings(target)
+  end
+end
diff --git a/lib/enums.dart b/lib/enums.dart
new file mode 100644
index 0000000..6f79bb4
--- /dev/null
+++ b/lib/enums.dart
@@ -0,0 +1,12 @@
+enum ArgEnums {
+  help(name: 'help', abbr: 'h'),
+  path(name: 'path', abbr: 'p'),
+  flavor(name: 'flavor', abbr: 'f'),
+  flavors(name: 'flavors', abbr: 'F'),
+  allFlavors(name: 'all-flavors', abbr: 'A');
+
+  final String name;
+  final String abbr;
+
+  const ArgEnums({required this.name, required this.abbr});
+}
diff --git a/lib/helper_utils.dart b/lib/helper_utils.dart
new file mode 100644
index 0000000..962acf4
--- /dev/null
+++ b/lib/helper_utils.dart
@@ -0,0 +1,46 @@
+import 'package:flutter_native_splash/enums.dart';
+
+class HelperUtils {
+  const HelperUtils._();
+
+  /// Checks if a given filename matches the flutter native splash flavor config pattern
+  /// The pattern is: flutter_native_splash-*.yaml where * is the flavor name
+  ///
+  /// Returns true if the filename matches the pattern, false otherwise
+  static bool isValidFlavorConfigFileName(String fileName) {
+    return RegExp(r'^flutter_native_splash-[^-]+\.yaml$').hasMatch(fileName);
+  }
+
+  /// Extracts the flavor name from a valid flavor config filename
+  ///
+  /// Throws an exception if the filename is not a valid flavor config filename
+  static String getFlavorNameFromFileName(String fileName) {
+    final flavorMatch =
+        RegExp(r'^flutter_native_splash-(.+)\.yaml$').firstMatch(fileName);
+
+    final flavorName = flavorMatch?.group(1);
+
+    if (flavorName == null) {
+      throw Exception('Invalid flavor config filename: $fileName');
+    }
+
+    return flavorName;
+  }
+
+  /// Validate the flavor arguments
+  ///
+  /// Throws an exception if the arguments are invalid.
+  static void validateFlavorArgs({
+    required String? flavorArg,
+    required String? flavorsArg,
+    required bool? allFlavorsArg,
+  }) {
+    if ((flavorArg != null && flavorsArg != null) ||
+        (flavorArg != null && allFlavorsArg == true) ||
+        (flavorsArg != null && allFlavorsArg == true)) {
+      throw Exception(
+        'Cannot use multiple flavor options together. Please use only one of: --${ArgEnums.flavor.name}, --${ArgEnums.flavors.name}, or --${ArgEnums.allFlavors.name}.',
+      );
+    }
+  }
+}


Test patch:
diff --git a/test/helper_utils_test.dart b/test/helper_utils_test.dart
new file mode 100644
index 0000000..c3c6916
--- /dev/null
+++ b/test/helper_utils_test.dart
@@ -0,0 +1,151 @@
+import 'package:flutter_native_splash/helper_utils.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('HelperUtils.isValidFlavorConfigFileName', () {
+    test('should return true for valid flavor config filenames', () {
+      expect(
+        HelperUtils.isValidFlavorConfigFileName(
+            'flutter_native_splash-dev.yaml'),
+        isTrue,
+      );
+      expect(
+        HelperUtils.isValidFlavorConfigFileName(
+            'flutter_native_splash-prod.yaml'),
+        isTrue,
+      );
+      expect(
+        HelperUtils.isValidFlavorConfigFileName(
+            'flutter_native_splash-staging.yaml'),
+        isTrue,
+      );
+    });
+
+    test('should return false for invalid flavor config filenames', () {
+      expect(
+        HelperUtils.isValidFlavorConfigFileName('flutter_native_splash.yaml'),
+        isFalse,
+      );
+      expect(
+        HelperUtils.isValidFlavorConfigFileName('flutter_native_splash-.yaml'),
+        isFalse,
+      );
+      expect(
+        HelperUtils.isValidFlavorConfigFileName(
+            'flutter_native_splash-dev.yml'),
+        isFalse,
+      );
+      expect(
+        HelperUtils.isValidFlavorConfigFileName('other-config.yaml'),
+        isFalse,
+      );
+      expect(
+        HelperUtils.isValidFlavorConfigFileName('random.txt'),
+        isFalse,
+      );
+    });
+  });
+
+  group('HelperUtils.getFlavorNameFromFileName', () {
+    test('should return the flavor name from a valid flavor config filename',
+        () {
+      expect(
+        HelperUtils.getFlavorNameFromFileName('flutter_native_splash-dev.yaml'),
+        'dev',
+      );
+      expect(
+        HelperUtils.getFlavorNameFromFileName(
+          'flutter_native_splash-prod.yaml',
+        ),
+        'prod',
+      );
+      expect(
+        HelperUtils.getFlavorNameFromFileName(
+          'flutter_native_splash-staging-flavor.yaml',
+        ),
+        'staging-flavor',
+      );
+    });
+
+    test(
+        'should throw an exception if the filename is not a valid flavor config filename',
+        () {
+      expect(
+        () => HelperUtils.getFlavorNameFromFileName(
+          'flutter_native_splash.yaml',
+        ),
+        throwsA(
+          isA<Exception>().having(
+            (e) => e.toString(),
+            'message',
+            contains('Invalid flavor config filename'),
+          ),
+        ),
+      );
+    });
+  });
+
+  group('Flavor arguments validation', () {
+    test('throws when flavor and flavors are used together', () {
+      expect(
+        () => HelperUtils.validateFlavorArgs(
+          flavorArg: 'dev',
+          flavorsArg: 'dev,prod',
+          allFlavorsArg: false,
+        ),
+        throwsA(
+          isA<Exception>().having(
+            (e) => e.toString(),
+            'message',
+            contains('Cannot use multiple flavor options together'),
+          ),
+        ),
+      );
+    });
+
+    test('throws when flavor and allFlavors are used together', () {
+      expect(
+        () => HelperUtils.validateFlavorArgs(
+          flavorArg: 'dev',
+          flavorsArg: null,
+          allFlavorsArg: true,
+        ),
+        throwsA(
+          isA<Exception>().having(
+            (e) => e.toString(),
+            'message',
+            contains('Cannot use multiple flavor options together'),
+          ),
+        ),
+      );
+    });
+
+    test('throws when flavors and allFlavors are used together', () {
+      expect(
+        () => HelperUtils.validateFlavorArgs(
+          flavorArg: null,
+          flavorsArg: 'dev,prod',
+          allFlavorsArg: true,
+        ),
+        throwsA(
+          isA<Exception>().having(
+            (e) => e.toString(),
+            'message',
+            contains('Cannot use multiple flavor options together'),
+          ),
+        ),
+      );
+    });
+
+    test('does not throw with single flavor option', () {
+      expect(
+        () => HelperUtils.validateFlavorArgs(
+          flavorArg: 'dev',
+          flavorsArg: null,
+          allFlavorsArg: false,
+        ),
+        returnsNormally,
+      );
+    });
+  });
+}