error-mapper.ts•5.54 kB
/**
 * @fileoverview Git CLI error mapping utilities
 * @module services/git/providers/cli/utils/error-mapper
 */
import { JsonRpcErrorCode, McpError } from '@/types-global/errors.js';
/**
 * Git error patterns and their corresponding error codes.
 */
const ERROR_PATTERNS: Array<{
  pattern: RegExp;
  code: JsonRpcErrorCode;
  messageTransform?: (match: RegExpMatchArray) => string;
}> = [
  // Repository errors
  {
    pattern: /not a git repository/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /repository .* not found/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  // Permission errors
  {
    pattern: /permission denied/i,
    code: JsonRpcErrorCode.InternalError,
  },
  {
    pattern: /eacces/i,
    code: JsonRpcErrorCode.InternalError,
  },
  // File errors
  {
    pattern: /pathspec '(.+)' did not match any files/i,
    code: JsonRpcErrorCode.InvalidRequest,
    messageTransform: (match) => `File not found: ${match[1]}`,
  },
  {
    pattern: /no such file or directory/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  // Conflict errors
  {
    pattern: /conflict/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /you have unstaged changes/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /your local changes would be overwritten/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /failed to merge/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  // Branch errors
  {
    pattern: /branch '(.+)' already exists/i,
    code: JsonRpcErrorCode.InvalidRequest,
    messageTransform: (match) => `Branch already exists: ${match[1]}`,
  },
  {
    pattern: /branch '(.+)' not found/i,
    code: JsonRpcErrorCode.InvalidRequest,
    messageTransform: (match) => `Branch not found: ${match[1]}`,
  },
  // Remote errors
  {
    pattern: /could not read from remote/i,
    code: JsonRpcErrorCode.InternalError,
  },
  {
    pattern: /failed to connect/i,
    code: JsonRpcErrorCode.InternalError,
  },
  {
    pattern: /authentication failed/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /remote .* does not exist/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  // Network errors
  {
    pattern: /network/i,
    code: JsonRpcErrorCode.InternalError,
  },
  {
    pattern: /connection/i,
    code: JsonRpcErrorCode.InternalError,
  },
  {
    pattern: /timeout/i,
    code: JsonRpcErrorCode.InternalError,
  },
  // Commit errors
  {
    pattern: /nothing to commit/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /no changes added to commit/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  // Reference errors
  {
    pattern: /reference is not a tree/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /ambiguous argument/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
  {
    pattern: /unknown revision/i,
    code: JsonRpcErrorCode.InvalidRequest,
  },
];
/**
 * Map a git CLI error to an appropriate McpError.
 *
 * @param error - The original error from git command execution
 * @param operation - The git operation that failed
 * @returns Mapped McpError with appropriate code and message
 */
export function mapGitError(error: unknown, operation: string): McpError {
  if (error instanceof McpError) {
    return error;
  }
  const errorMessage = error instanceof Error ? error.message : String(error);
  const lowerMessage = errorMessage.toLowerCase();
  // Try to match against known error patterns
  for (const { pattern, code, messageTransform } of ERROR_PATTERNS) {
    const match = errorMessage.match(pattern);
    if (match) {
      const message = messageTransform
        ? messageTransform(match)
        : `Git ${operation} failed: ${errorMessage}`;
      return new McpError(code, message, {
        cause: error instanceof Error ? error : undefined,
      });
    }
  }
  // Check for git not installed
  if (
    lowerMessage.includes('git') &&
    (lowerMessage.includes('not found') ||
      lowerMessage.includes('enoent') ||
      lowerMessage.includes('command not found'))
  ) {
    return new McpError(
      JsonRpcErrorCode.InternalError,
      'Git command not found. Please ensure Git is installed and in your PATH.',
      { cause: error instanceof Error ? error : undefined },
    );
  }
  // Default error
  return new McpError(
    JsonRpcErrorCode.InternalError,
    `Git ${operation} failed: ${errorMessage}`,
    { cause: error instanceof Error ? error : undefined },
  );
}
/**
 * Check if an error indicates a missing git installation.
 *
 * @param error - Error to check
 * @returns True if git is not installed
 */
export function isGitNotFoundError(error: unknown): boolean {
  const message = (
    error instanceof Error ? error.message : String(error)
  ).toLowerCase();
  return (
    message.includes('git') &&
    (message.includes('not found') ||
      message.includes('enoent') ||
      message.includes('command not found'))
  );
}
/**
 * Extract meaningful error context from git stderr output.
 *
 * @param stderr - Git command stderr output
 * @returns Cleaned error message
 */
export function extractGitErrorMessage(stderr: string): string {
  // Remove common git prefixes
  let message = stderr
    .replace(/^fatal:\s*/gim, '')
    .replace(/^error:\s*/gim, '')
    .replace(/^warning:\s*/gim, '')
    .trim();
  // Take only the first meaningful line
  const lines = message.split('\n').filter((l) => l.trim());
  if (lines.length > 0) {
    message = lines[0]!;
  }
  return message;
}