name: Retry a flaky command
description: >-
  Run a shell command, retrying on non-zero exit. For dependency installs
  (npm ci, uv sync) whose only failures are transient network/toolchain
  flakes — a node-gyp header fetch, a registry blip — so CI self-heals
  instead of needing a manual re-run. Can also capture stdout as a step
  output for commands whose result must be consumed by later steps.

inputs:
  command:
    description: Shell command to run (and retry).
    required: true
  attempts:
    description: Max attempts before giving up.
    default: "3"
  delay:
    description: Seconds to wait between attempts.
    default: "10"
  working-directory:
    description: Directory to run in.
    default: "."

outputs:
  stdout:
    description: Captured stdout from the successful attempt (empty if not needed).
    value: ${{ steps.retry.outputs.stdout }}

runs:
  using: composite
  steps:
    - id: retry
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      # command goes through env, never interpolated into the script body, so
      # a command with quotes/specials can't break or inject into the runner.
      env:
        _CMD: ${{ inputs.command }}
        _ATTEMPTS: ${{ inputs.attempts }}
        _DELAY: ${{ inputs.delay }}
      run: |
        set -uo pipefail
        _OUTFILE="$(mktemp)"
        trap 'rm -f "$_OUTFILE"' EXIT
        n=0
        while :; do
          n=$((n + 1))
          echo "::group::attempt $n/$_ATTEMPTS: $_CMD"
          # Run the command, capturing stdout to a temp file while still
          # streaming to the log. We redirect first, then tee the file to
          # stdout — this avoids pipefail + tee exit-code interactions that
          # can cause the if-branch to be skipped under set -e.
          if bash -c "$_CMD" > "$_OUTFILE"; then
            cat "$_OUTFILE"
            echo "::endgroup::"
            # Preserve newlines in the output via heredoc delimiter.
            {
              echo 'stdout<<__RETRY_STDOUT_EOF__'
              cat "$_OUTFILE"
              echo '__RETRY_STDOUT_EOF__'
            } >> "$GITHUB_OUTPUT"
            exit 0
          fi
          echo "::endgroup::"
          if [ "$n" -ge "$_ATTEMPTS" ]; then
            echo "::error::failed after $n attempts: $_CMD"
            exit 1
          fi
          echo "::warning::attempt $n failed; retrying in ${_DELAY}s: $_CMD"
          sleep "$_DELAY"
        done
