#!/usr/bin/env bash
#
# vercel-deploy.sh  -  safe wrapper around `vercel` CLI.
#
# Why this exists (v8.0.0): on 2026-04-27 a `vercel deploy --token=...`
# invocation failed during the v7.9.1 release flow. The CLI's error retry
# hint printed the failed argv verbatim, including `--token=vcp_...`. That
# leaked the deploy token into the conversation transcript and forced a
# token rotation. See `feedback_vercel_cli_token_leak.md`.
#
# This wrapper guarantees three properties for every Vercel call:
#   1. Tokens are passed via VERCEL_TOKEN env var, NEVER via --token=... argv.
#      Even if a downstream caller still uses --token=, the redact filter
#      below scrubs it from stdout/stderr.
#   2. Every stdout/stderr line is piped through `redact_token` before being
#      written to the user's terminal or any log file.
#   3. Failed deploys exit with a clear message instead of dumping the CLI's
#      retry hint that contains the original argv.
#
# Usage:
#   pipeline/lib/vercel-deploy.sh deploy [--prod] [--cwd=<dir>] [-- <extra-vercel-args>...]
#   pipeline/lib/vercel-deploy.sh redact < input > output     # filter mode
#   pipeline/lib/vercel-deploy.sh doctor                       # env + CLI check
#
# Environment:
#   VERCEL_TOKEN           -  required for deploy (resolved from prefs keychainMapping if unset)
#   VERCEL_ORG_ID          -  optional, scopes deploys to a team
#   VERCEL_PROJECT_ID      -  optional, links to a specific project
#   VERCEL_DEPLOY_QUIET    -  when set, suppresses progress lines (raw CLI output stays redacted)
#
# Exit codes:
#   0  deploy succeeded
#   1  argument or environment error (no token, missing CLI)
#   2  vercel CLI exited non-zero (post-redaction tail printed)

set -euo pipefail

CMD="${1:-help}"
shift || true

# ---------------------------------------------------------------------------
# Token redaction filter  -  used everywhere user-visible output flows.
#
# Patterns scrubbed:
#   --token=<rest>          → --token=***REDACTED***
#   --token <rest>          → --token ***REDACTED***
#   vcp_<hex/alnum>         → vcp_***REDACTED***
#   Bearer <jwt-ish>        → Bearer ***REDACTED***
#   "token":"<val>"         → "token":"***REDACTED***"
#
# Sed rather than perl/awk so the wrapper has no extra runtime deps.
# ---------------------------------------------------------------------------
redact_filter() {
  sed -E \
    -e 's/(--token[ =])[^ "'"'"']+/\1***REDACTED***/g' \
    -e 's/vcp_[A-Za-z0-9_-]+/vcp_***REDACTED***/g' \
    -e 's/(Bearer[ =])[A-Za-z0-9._-]+/\1***REDACTED***/g' \
    -e 's/("token"[[:space:]]*:[[:space:]]*")[^"]+/\1***REDACTED***/g' \
    -e 's/("VERCEL_TOKEN"[[:space:]]*:[[:space:]]*")[^"]+/\1***REDACTED***/g'
}

cmd_redact() {
  redact_filter
}

cmd_doctor() {
  local rc=0
  if command -v vercel >/dev/null 2>&1; then
    echo "vercel: $(vercel --version 2>&1 | redact_filter | head -1)"
  else
    echo "vercel: NOT INSTALLED  -  run 'npm i -g vercel'" >&2
    rc=1
  fi

  if [ -n "${VERCEL_TOKEN:-}" ]; then
    local masked
    masked=$(printf '%s' "$VERCEL_TOKEN" | head -c 4)
    echo "VERCEL_TOKEN: set (prefix=${masked}..., length=${#VERCEL_TOKEN})"
  else
    echo "VERCEL_TOKEN: unset  -  set via env or pipeline keychainMapping.vercel"
  fi

  if [ -n "${VERCEL_ORG_ID:-}" ]; then
    echo "VERCEL_ORG_ID: set"
  fi
  if [ -n "${VERCEL_PROJECT_ID:-}" ]; then
    echo "VERCEL_PROJECT_ID: set"
  fi

  return $rc
}

cmd_deploy() {
  # Token-on-argv refusal runs FIRST - the whole point of this wrapper is
  # never letting --token reach a transcript, so the guard must not depend
  # on the vercel CLI being installed.
  local arg
  for arg in "$@"; do
    case "$arg" in
      --token|--token=*)
        echo "ERROR: refused to deploy with --token argv. Pass the token via VERCEL_TOKEN env var instead." >&2
        exit 1
        ;;
    esac
  done

  if ! command -v vercel >/dev/null 2>&1; then
    echo "ERROR: vercel CLI not on PATH  -  run 'npm i -g vercel'" >&2
    exit 1
  fi

  if [ -z "${VERCEL_TOKEN:-}" ]; then
    # Route the credential-helper hint through the resolver instead of a
    # hardcoded ~/.claude path so Copilot-only installs get a usable command.
    # The resolver ships alongside this wrapper in lib/; fall back to the
    # per-CLI install locations when running from a different cwd layout.
    # shellcheck source=/dev/null
    # Existence check before sourcing: `. <missing>` aborts the shell under `set -e`,
    # `||` included, so a `.`-chain reaches neither its later candidates nor its error
    # branch. The loop also covers all three hosts - the chain it replaced knew only
    # .claude and .copilot, so a Codex-only install could not resolve at all.
    for _cred_resolver in \
      "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/credential-store-resolver.sh" \
      "$HOME/.claude/lib/credential-store-resolver.sh" \
      "$HOME/.copilot/lib/credential-store-resolver.sh" \
      "$HOME/.codex/lib/credential-store-resolver.sh"; do
      [ -f "$_cred_resolver" ] || continue
      # shellcheck source=/dev/null
      . "$_cred_resolver" 2>/dev/null || true
      if [ -n "${CRED_STORE:-}" ]; then break; fi
    done
    unset _cred_resolver
    echo "ERROR: VERCEL_TOKEN not set. Resolve via env or keychain ('${CRED_STORE:-credential-store.sh} get <vercel-key>')." >&2
    echo "Hint: vercel CLI accepts the token via env var; do NOT pass --token= on argv (leaks on retry)." >&2
    exit 1
  fi

  local cwd="."
  local prod=false
  local extra_args=()

  while [ "$#" -gt 0 ]; do
    case "$1" in
      --prod|--production) prod=true ;;
      --cwd=*) cwd="${1#--cwd=}" ;;
      --) shift; extra_args=("$@"); break ;;
      *)   extra_args+=("$1") ;;
    esac
    shift || true
  done

  local cmd_args=(deploy)
  if [ "$prod" = true ]; then
    cmd_args+=(--prod)
  fi
  cmd_args+=("${extra_args[@]}")

  # Refuse to run if the user is trying to pass --token=... via extra args.
  for a in "${extra_args[@]}"; do
    case "$a" in
      --token=*|--token)
        echo "ERROR: refused to deploy with --token argv. Pass the token via VERCEL_TOKEN env var instead." >&2
        echo "       Reason: vercel CLI prints failed argv on retry, leaking the token to the terminal." >&2
        exit 1
        ;;
    esac
  done

  # Run the CLI with token via env. Pipe stdout AND stderr through the redact
  # filter so even if the CLI dumps argv on failure (it does  -  that was the
  # original v7.9.1 leak), tokens never reach the user's screen or transcript.
  set +e
  ( cd "$cwd" && VERCEL_TOKEN="$VERCEL_TOKEN" vercel "${cmd_args[@]}" 2>&1 ) \
    | redact_filter
  local rc="${PIPESTATUS[0]}"
  set -e

  if [ "$rc" -ne 0 ]; then
    echo "" >&2
    echo "vercel-deploy.sh: deploy failed (exit $rc). Token-leaking output was filtered above." >&2
    exit 2
  fi
}

case "$CMD" in
  deploy)  cmd_deploy "$@" ;;
  redact)  cmd_redact ;;
  doctor)  cmd_doctor ;;
  -h|--help|help)
    cat <<'EOF'
Usage:
  vercel-deploy.sh deploy [--prod] [--cwd=<dir>] [-- <extra-vercel-args>...]
  vercel-deploy.sh redact < input > output
  vercel-deploy.sh doctor

See: pipeline/lib/vercel-deploy.sh source comments for the full leak rationale.
EOF
    ;;
  *)
    echo "unknown subcommand: $CMD" >&2
    exit 1
    ;;
esac
