run method

  1. @override
Future<ZapStepRun> run(
  1. MissionStep step, {
  2. required String workingDirectory,
  3. required Duration timeout,
})
override

Implementation

@override
Future<ZapStepRun> run(
  MissionStep step, {
  required String workingDirectory,
  required Duration timeout,
}) async {
  final tokens = step.command.trim().split(RegExp(r'\s+'));
  final executable = tokens.first;
  final args = tokens.skip(1).toList();

  final started = DateTime.now();
  final process = await Process.start(
    executable,
    args,
    workingDirectory: workingDirectory,
  );

  final stdoutBuffer = StringBuffer();
  final stderrBuffer = StringBuffer();
  // Drain both streams to completion — the certified digest covers
  // the FULL output, so the last chunks must be decoded before the
  // digest is computed, even when the process exits first.
  final stdoutDrained = process.stdout
      .transform(const Utf8Decoder(allowMalformed: true))
      .listen(stdoutBuffer.write)
      .asFuture<void>();
  final stderrDrained = process.stderr
      .transform(const Utf8Decoder(allowMalformed: true))
      .listen(stderrBuffer.write)
      .asFuture<void>();

  var timedOut = false;
  int exit;
  final done = process.exitCode;
  try {
    exit = await done.timeout(timeout);
  } on TimeoutException {
    timedOut = true;
    // SIGTERM can be trapped — a step that ignores it would hang the
    // sequential serve loop forever. Escalate straight to SIGKILL.
    process.kill(ProcessSignal.sigkill);
    // Wait out the kill, then normalize to the timeout convention —
    // the signal's raw negative code is not part of the contract.
    await done;
    exit = zapTimeoutExit;
  }
  await stdoutDrained;
  await stderrDrained;

  final combined = '${stdoutBuffer.toString()}${stderrBuffer.toString()}';
  final output = timedOut
      ? 'ZAP: step ${step.id} timed out after ${timeout.inSeconds}s '
            'and was killed\n$combined'
      : combined;
  final at = DateTime.now().toUtc();

  return ZapStepRun(
    stepId: step.id,
    phase: step.phase,
    command: step.command,
    exit: exit,
    digest: ZapStepRun.digestOf(utf8.encode(combined)),
    at: at.toIso8601String(),
    durationMs: at.difference(started).inMilliseconds,
    output: output,
  );
}