run method

  1. @override
Future<void> run()
override

Runs this command.

The return value is wrapped in a Future if necessary and returned by CommandRunner.runCommand.

Implementation

@override
Future<void> run() async {
  // 1. Get the feature name
  String? featureNameArg = argResults?.rest.isNotEmpty == true ? argResults!.rest.first : null;
  if (featureNameArg == null || featureNameArg.trim().isEmpty) {
    throw CliException('Please specify a feature name. Usage: flutter_init_cli generate <feature_name>');
  }

  final featureName = featureNameArg.trim();
  final snakeName = CaseHelper.toSnakeCase(featureName);
  final pascalName = CaseHelper.toPascalCase(featureName);
  final camelName = CaseHelper.toCamelCase(featureName);
  final upperName = CaseHelper.toUpperCase(featureName);

  // 2. Load config file from current directory
  final projectPath = Directory.current.path;
  final configFile = File(p.join(projectPath, '.flutter_init_cli.json'));
  if (!configFile.existsSync()) {
    throw CliException(
      'Could not find .flutter_init_cli.json in the current directory.',
      'Ensure you run this command inside the root folder of a project generated by flutter_init_cli.',
    );
  }

  Map<String, dynamic> config;
  try {
    config = jsonDecode(configFile.readAsStringSync()) as Map<String, dynamic>;
  } catch (e) {
    throw CliException('Failed to parse .flutter_init_cli.json config file.', e.toString());
  }

  final projectName = config['projectName'] as String? ?? 'app';
  final architecture = config['architecture'] as String? ?? 'featureFirst';
  final stateManagement = config['stateManagement'] as String? ?? 'provider';
  final routing = config['routing'] as String? ?? 'navigator';

  Logger.header('GENERATING FEATURE: $pascalName');

  final isFeatureBased = architecture == 'featureFirst' || architecture == 'cleanArchitecture';
  final stateFolder = _getStateManagementFolder(stateManagement, architecture);

  // 3. Resolve target directories
  String targetViewDir;
  String targetStateDir;

  if (isFeatureBased) {
    final featureDir = p.join(projectPath, 'lib', 'features', snakeName);

    // Presentation directories
    targetViewDir = p.join(featureDir, 'presentation', 'views');
    targetStateDir = p.join(featureDir, 'presentation', stateFolder);
    final targetWidgetDir = p.join(featureDir, 'presentation', 'widgets');

    // Data directories
    final targetDataDir = p.join(featureDir, 'data');
    final targetDataSourcesDir = p.join(targetDataDir, 'datasources');
    final targetModelsDir = p.join(targetDataDir, 'models');
    final targetDataRepositoriesDir = p.join(targetDataDir, 'repositories');

    // Domain directories
    final targetDomainDir = p.join(featureDir, 'domain');
    final targetEntitiesDir = p.join(targetDomainDir, 'entities');
    final targetDomainRepositoriesDir = p.join(targetDomainDir, 'repositories');
    final targetUseCasesDir = p.join(targetDomainDir, 'usecases');

    // Create all directories for Feature-First/Clean Architecture
    Services.fileService.createDir(targetViewDir);
    Services.fileService.createDir(targetStateDir);
    Services.fileService.createDir(targetWidgetDir);

    Services.fileService.createDir(targetDataDir);
    Services.fileService.createDir(targetDataSourcesDir);
    Services.fileService.createDir(targetModelsDir);
    Services.fileService.createDir(targetDataRepositoriesDir);

    Services.fileService.createDir(targetDomainDir);
    Services.fileService.createDir(targetEntitiesDir);
    Services.fileService.createDir(targetDomainRepositoriesDir);
    Services.fileService.createDir(targetUseCasesDir);
  } else {
    targetViewDir = p.join(projectPath, 'lib', 'views');
    targetStateDir = p.join(projectPath, 'lib', stateFolder);

    // Ensure directories exist for other architectures
    Services.fileService.createDir(targetViewDir);
    Services.fileService.createDir(targetStateDir);
  }

  // 4. Set up replacements map
  final replacements = {
    'home_provider': '${snakeName}_provider',
    'home_bloc': '${snakeName}_bloc',
    'home_event': '${snakeName}_event',
    'home_state': '${snakeName}_state',
    'home_controller': '${snakeName}_controller',
    'home_store': '${snakeName}_store',
    'Home': pascalName,
    'home': camelName,
    'HOME': upperName,
    '{{PROJECT_NAME}}': projectName,
  };

  // 5. Generate template files
  await _generateFeatureFiles(
    stateManagement: stateManagement,
    targetViewDir: targetViewDir,
    targetStateDir: targetStateDir,
    snakeName: snakeName,
    replacements: replacements,
    stateFolder: stateFolder,
  );

  // 6. Integrate into Routing System
  _integrateRouting(
    projectPath: projectPath,
    snakeName: snakeName,
    pascalName: pascalName,
    camelName: camelName,
    isFeatureBased: isFeatureBased,
    routing: routing,
  );

  Logger.boldInfo('\nSuccessfully generated "$pascalName" feature!');
}