substituteCLIArguments method

Future<String> substituteCLIArguments(
  1. String? input
)

Substitutes CLI command arguments in a string with their execution results.

This function finds patterns like %{{COMMAND}} or %{COMMAND} and replaces them with the output of executing the command.

Parameters:

  • input - The input string containing command placeholders (can be null)

Returns the string with command placeholders replaced by their standard output. Throws VariableException when a command cannot be run or exits non-zero: substituting the empty string or the command's stderr silently changes what gets built and still reports success.

Implementation

Future<String> substituteCLIArguments(String? input) async {
  if (input == null) return "";
  // Pattern matches %{{COMMAND}} OR %{COMMAND}
  final pattern = RegExp(r'\%\{\{([^\}]+)\}\}|\%\{([^\}]+)\}');

  // Use replaceAllMapped to handle each match individually and asynchronously
  final matches = pattern.allMatches(input).toList();
  if (matches.isEmpty) return input;

  // Since replaceAllMapped cannot be async, we process matches manually
  String result = input;
  for (final match in matches.reversed) {
    final value = match.group(1) ?? match.group(2) ?? "";
    String processResults;

    if (value.trim().isEmpty) {
      processResults = "";
    } else {
      final args = value.contains(" ")
          ? _parseCommandArguments(value)
          : <String>[value];

      final ProcessResult process;
      try {
        process = await Process.run(args.first, args.sublist(1));
      } on ProcessException catch (e) {
        // A missing binary used to substitute an empty string, so
        // `build-name: "%{{git describe}}"` on a machine without git
        // produced `--build-name=` and a green run.
        throw VariableException(
          "`%{{$value}}` could not be run: ${e.message}",
        );
      }

      if (process.exitCode != 0) {
        // The old behaviour substituted stderr into the value. Since stderr
        // contains spaces it split into extra arguments on the flutter
        // command line, producing a build nobody asked for that still
        // reported success.
        final detail = process.stderr.toString().trim();
        throw VariableException(
          "`%{{$value}}` exited with ${process.exitCode}"
          "${detail.isEmpty ? '' : ': $detail'}",
        );
      }

      processResults = process.stdout.toString().trim();
    }

    // Replace only the current match
    result = result.replaceRange(match.start, match.end, processResults);
  }
  return result;
}