runCommand function
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);
}
}
}