startWithStdin method

Future<int> startWithStdin({
  1. String? workingDirectory,
  2. void progressOut(
    1. String line
    )?,
  3. void progressErr(
    1. String line
    )?,
  4. bool showLog = true,
  5. bool singleCharacterMode = false,
})

Starts a process with stdin forwarding for interactive commands.

This method is specifically designed for interactive commands that require user input (like Flutter run with hot reload support).

Parameters:

  • workingDirectory: The working directory for the process
  • progressOut: Callback for stdout lines
  • progressErr: Callback for stderr lines
  • showLog: Whether to show log output

Implementation

Future<int> startWithStdin({
  String? workingDirectory,
  void Function(String line)? progressOut,
  void Function(String line)? progressErr,
  bool showLog = true,
  bool singleCharacterMode = false,
}) async {
  final commandCli = CommandlineConverter().convert(this);

  if (commandCli.isEmpty) {
    throw ArgumentError('Command cannot be empty');
  }

  final executable = commandCli.first;
  final arguments = commandCli.sublist(1);

  try {
    final process = await Process.start(
      executable,
      arguments,
      runInShell: true,
      workingDirectory: workingDirectory,
      environment: Platform.environment,
    );

    // Forward stdout
    process.stdout.transform(utf8.decoder).listen((line) {
      final cleanLine = line.replaceAll(RegExp(r'[\s\n]+$'), '');
      progressOut?.call(cleanLine);
      if (showLog) printMessage(cleanLine);
    });

    // Forward stderr
    process.stderr.transform(utf8.decoder).listen((line) {
      final cleanLine = line.replaceAll(RegExp(r'[\s\n]+$'), '');
      progressErr?.call(cleanLine);
      if (showLog) printerrMessage(cleanLine);
    });

    // Enable single character mode for stdin
    if (singleCharacterMode) {
      stdin.lineMode = false;
      stdin.echoMode = false;
    }

    // Forward stdin - this is the key part for interactive features
    // Use a more robust approach for stdin forwarding
    final subscription = stdin.listen(
      (data) {
        try {
          process.stdin.add(data);
        } catch (e) {
          // Ignore errors when writing to stdin (process might have exited)
        }
      },
      onError: (error) {
        // Handle stdin errors
      },
      onDone: () {
        // Close stdin when done
        process.stdin.close();
      },
    );

    // Handle process exit
    final exitCode = await process.exitCode;

    // Cancel stdin subscription
    await subscription.cancel();

    if (exitCode > 0) {
      throw Exception('Command "$this" exited with code $exitCode');
    }

    return exitCode;
  } catch (e) {
    if (e is ProcessException) {
      throw Exception('Failed to start command "$this": ${e.message}');
    }
    rethrow;
  }
}