run method

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

Runs this command.

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

Implementation

@override
Future<int> run() async {
  if (argResults!.rest.isEmpty) {
    stderr.writeln('\x1B[31mError:\x1B[0m Please specify a component name.');
    stderr.writeln('');
    stderr.writeln('Usage: flai add <component>');
    stderr.writeln(
      'Run \x1B[36mflai list\x1B[0m to see available components.',
    );
    return 1;
  }

  final componentName = argResults!.rest.first;
  final dryRun = argResults!['dry-run'] as bool;
  final cwd = Directory.current.path;

  // 1. Check the component exists in the registry.
  final brick = BrickRegistry.lookup(componentName);
  if (brick == null) {
    stderr.writeln(
      '\x1B[31mError:\x1B[0m Unknown component "$componentName".',
    );
    stderr.writeln(
      'Run \x1B[36mflai list\x1B[0m to see available components.',
    );
    return 1;
  }

  // 2. Check that the project is initialised.
  final configManager = FlaiConfigManager(projectRoot: cwd);
  if (!configManager.exists) {
    stderr.writeln(
      '\x1B[31mError:\x1B[0m No flai.yaml found. '
      'Run \x1B[36mflai init\x1B[0m first.',
    );
    return 1;
  }

  final config = configManager.read();
  final alreadyInstalled = config.installed.toSet();

  // 3. Resolve the full dependency graph.
  const resolver = DependencyResolver();
  final installOrder = resolver.resolve(
    componentName,
    alreadyInstalled: alreadyInstalled,
  );

  if (installOrder.isEmpty) {
    stdout.writeln(
      '\x1B[32m\u2713\x1B[0m $componentName is already installed.',
    );
    return 0;
  }

  // 4. Collect pub dependencies.
  final pubDeps = resolver.collectPubDependencies(installOrder);

  // 5. Display the installation plan.
  stdout.writeln('');
  stdout.writeln('\x1B[1mInstallation plan:\x1B[0m');
  for (final name in installOrder) {
    final info = BrickRegistry.lookup(name)!;
    stdout.writeln('  \x1B[36m+\x1B[0m $name — ${info.description}');
  }
  if (pubDeps.isNotEmpty) {
    stdout.writeln('');
    stdout.writeln('\x1B[1mPub dependencies to add:\x1B[0m');
    for (final dep in pubDeps) {
      stdout.writeln('  \x1B[36m+\x1B[0m $dep');
    }
  }
  stdout.writeln('');

  if (dryRun) {
    stdout.writeln('\x1B[33m(dry run — no changes made)\x1B[0m');
    return 0;
  }

  // Derive the output_dir variable from config.
  // config.outputDir is like "lib/flai" — the brick var is just "flai".
  final outputDirVar =
      config.outputDir.startsWith('lib/')
          ? config.outputDir.substring(4)
          : config.outputDir;

  // 6. Install each component using Mason.
  for (final name in installOrder) {
    final brickPath = _resolveBrickPath(name);
    if (brickPath == null) {
      stderr.writeln(
        '\x1B[33m!\x1B[0m Brick not found for $name — skipping.',
      );
      continue;
    }

    stdout.writeln('\x1B[36m>\x1B[0m Installing $name...');

    try {
      final generator = await MasonGenerator.fromBrick(Brick.path(brickPath));
      final target = DirectoryGeneratorTarget(Directory(cwd));
      final files = await generator.generate(
        target,
        vars: {'output_dir': outputDirVar},
        fileConflictResolution: FileConflictResolution.overwrite,
      );

      for (final file in files) {
        stdout.writeln('  \x1B[32m\u2713\x1B[0m ${file.path}');
      }
    } on Exception catch (e) {
      stderr.writeln('\x1B[31mError:\x1B[0m Failed to install $name: $e');
      continue;
    }
  }

  // 7. Add pub.dev dependencies to the project pubspec.yaml.
  if (pubDeps.isNotEmpty) {
    _addPubDependencies(cwd, pubDeps);
  }

  // 8. Update flai.yaml.
  configManager.markInstalled(installOrder);

  stdout.writeln('');
  stdout.writeln(
    '\x1B[32m\u2713 Successfully installed $componentName!\x1B[0m',
  );

  if (pubDeps.isNotEmpty) {
    stdout.writeln('');
    stdout.writeln(
      'Run \x1B[36mflutter pub get\x1B[0m to fetch new dependencies.',
    );
  }
  stdout.writeln('');

  return 0;
}