runCommand function

Future<ProcessOutput> runCommand(
  1. String command,
  2. List<String> args, {
  3. String? workingDirectory,
  4. Map<String, String>? environment,
  5. Duration? timeout,
  6. int? maxOutputBytes,
})

Run a command and capture output.

Implementation

Future<ProcessOutput> runCommand(
  String command,
  List<String> args, {
  String? workingDirectory,
  Map<String, String>? environment,
  Duration? timeout,
  int? maxOutputBytes,
}) async {
  final sw = Stopwatch()..start();

  try {
    final process = await Process.start(
      command,
      args,
      workingDirectory: workingDirectory,
      environment: environment,
    );

    final stdoutBuf = StringBuffer();
    final stderrBuf = StringBuffer();
    var stdoutBytes = 0;
    var stderrBytes = 0;

    final stdoutSub = process.stdout.transform(utf8.decoder).listen((chunk) {
      stdoutBytes += chunk.length;
      if (maxOutputBytes == null || stdoutBytes <= maxOutputBytes) {
        stdoutBuf.write(chunk);
      }
    });

    final stderrSub = process.stderr.transform(utf8.decoder).listen((chunk) {
      stderrBytes += chunk.length;
      if (maxOutputBytes == null || stderrBytes <= maxOutputBytes) {
        stderrBuf.write(chunk);
      }
    });

    int exitCode;
    bool timedOut = false;

    if (timeout != null) {
      final exitFuture = process.exitCode;
      final result = await Future.any([
        exitFuture,
        Future.delayed(timeout, () => -1),
      ]);

      if (result == -1) {
        timedOut = true;
        process.kill(ProcessSignal.sigterm);
        await Future.delayed(const Duration(seconds: 2));
        process.kill(ProcessSignal.sigkill);
        exitCode = await exitFuture.timeout(
          const Duration(seconds: 3),
          onTimeout: () => -1,
        );
      } else {
        exitCode = result;
      }
    } else {
      exitCode = await process.exitCode;
    }

    await stdoutSub.cancel();
    await stderrSub.cancel();
    sw.stop();

    var stdout = stdoutBuf.toString();
    var stderr = stderrBuf.toString();

    if (maxOutputBytes != null && stdoutBytes > maxOutputBytes) {
      stdout += '\n[... output truncated at $maxOutputBytes bytes]';
    }
    if (maxOutputBytes != null && stderrBytes > maxOutputBytes) {
      stderr += '\n[... output truncated at $maxOutputBytes bytes]';
    }

    return ProcessOutput(
      exitCode: exitCode,
      stdout: stdout,
      stderr: stderr,
      elapsed: sw.elapsed,
      timedOut: timedOut,
    );
  } catch (e) {
    sw.stop();
    return ProcessOutput(
      exitCode: -1,
      stdout: '',
      stderr: e.toString(),
      elapsed: sw.elapsed,
    );
  }
}