runCommand function

Future<void> runCommand(
  1. String command,
  2. List<String> args, {
  3. String? successMessage,
  4. bool showLoading = true,
  5. String? loadingMessage,
  6. String? workingDirectory,
})

Executes a shell command with optional loading indicators and error handling.

This function sanitizes the command and args to prevent command injection. It can display a progress indicator while the command is running and logs success or error messages upon completion.

command The executable command to run (e.g., 'flutter', 'dart'). args A list of string arguments to pass to the command. successMessage An optional message to display on successful command execution. showLoading If true, a loading indicator with elapsed time will be shown. Defaults to true. loadingMessage An optional custom message to display during loading. workingDirectory The directory in which to run the command. If null, the current working directory of the process is used.

Throws an Exception if the command fails (returns a non-zero exit code).

Implementation

Future<void> runCommand(
  String command,
  List<String> args, {
  String? successMessage,
  bool showLoading = true,
  String? loadingMessage,
  String? workingDirectory,
}) async {
  // Sanitize command and arguments
  final sanitizedCommand = sanitizeArg(command);
  final sanitizedArgs = args.map(sanitizeArg).toList();
  final sanitizedWorkingDirectory = workingDirectory != null
      ? sanitizeArg(workingDirectory)
      : null;

  if (showLoading) {
    final stopwatch = Stopwatch()..start();
    String fullCommand = '$sanitizedCommand ${sanitizedArgs.join(" ")}';
    if (fullCommand.length > 50) {
      fullCommand =
          '$sanitizedCommand ${sanitizedArgs.join(" ").substring(0, 50)}...';
    }
    final progress = Stream.periodic(const Duration(milliseconds: 100), (
      count,
    ) {
      return loadingMessage != null
          ? "🛠 $loadingMessage [${(stopwatch.elapsedMilliseconds / 1000).toStringAsFixed(1)}s]"
          : "🛠 Running $fullCommand [${(stopwatch.elapsedMilliseconds / 1000).toStringAsFixed(1)}s]";
    });

    // Print progress indicator in a loop
    final progressSubscription = progress.listen((message) {
      stdout.write('\r$message'); // Overwrite the same line in the terminal
    });

    try {
      final result = await Process.run(
        sanitizedCommand,
        sanitizedArgs,
        runInShell: true,
        workingDirectory: sanitizedWorkingDirectory,
      );
      progressSubscription.cancel(); // Stop the progress indicator
      stdout.write('\r'); // Clear the line

      if (result.exitCode == 0) {
        stopwatch.stop();
        logger.i(
          successMessage != null
              ? "$successMessage ${(stopwatch.elapsedMilliseconds / 1000).toStringAsFixed(2)}s"
              : '✅ Command completed in ${(stopwatch.elapsedMilliseconds / 1000).toStringAsFixed(2)}s.',
        );
      } else {
        throw Exception(
          '❌ Command failed: $sanitizedCommand ${sanitizedArgs.join(" ")}\nError: ${result.stderr}',
        );
      }
    } catch (e) {
      progressSubscription.cancel();
      stdout.write('\r'); // Clear the line
      logger.e(e);
    }
  } else {
    try {
      final result = await Process.run(
        sanitizedCommand,
        sanitizedArgs,
        runInShell: true,
        workingDirectory: sanitizedWorkingDirectory,
      );

      if (result.exitCode == 0) {
        logger.i('\r${successMessage ?? '✅ Command completed successfully.'}');
      } else {
        throw Exception(
          '❌ Command failed: $sanitizedCommand ${sanitizedArgs.join(" ")}\nError: ${result.stderr}',
        );
      }
    } catch (e) {
      logger.e(e);
    }
  }
}