/ utils / plugins / gitAvailability.ts
gitAvailability.ts
 1  /**
 2   * Utility for checking git availability.
 3   *
 4   * Git is required for installing GitHub-based marketplaces. This module
 5   * provides a memoized check to determine if git is available on the system.
 6   */
 7  
 8  import memoize from 'lodash-es/memoize.js'
 9  import { which } from '../which.js'
10  
11  /**
12   * Check if a command is available in PATH.
13   *
14   * Uses which to find the actual executable without executing it.
15   * This is a security best practice to avoid executing arbitrary code
16   * in untrusted directories.
17   *
18   * @param command - The command to check for
19   * @returns True if the command exists and is executable
20   */
21  async function isCommandAvailable(command: string): Promise<boolean> {
22    try {
23      return !!(await which(command))
24    } catch {
25      return false
26    }
27  }
28  
29  /**
30   * Check if git is available on the system.
31   *
32   * This is memoized so repeated calls within a session return the cached result.
33   * Git availability is unlikely to change during a single CLI session.
34   *
35   * Only checks PATH — does not exec git. On macOS this means the /usr/bin/git
36   * xcrun shim passes even without Xcode CLT installed; callers that hit
37   * `xcrun: error:` at exec time should call markGitUnavailable() so the rest
38   * of the session behaves as though git is absent.
39   *
40   * @returns True if git is installed and executable
41   */
42  export const checkGitAvailable = memoize(async (): Promise<boolean> => {
43    return isCommandAvailable('git')
44  })
45  
46  /**
47   * Force the memoized git-availability check to return false for the rest of
48   * the session.
49   *
50   * Call this when a git invocation fails in a way that indicates the binary
51   * exists on PATH but cannot actually run — the macOS xcrun shim being the
52   * main case (`xcrun: error: invalid active developer path`). Subsequent
53   * checkGitAvailable() calls then short-circuit to false, so downstream code
54   * that guards on git availability skips cleanly instead of failing repeatedly
55   * with the same exec error.
56   *
57   * lodash memoize uses a no-arg cache key of undefined.
58   */
59  export function markGitUnavailable(): void {
60    checkGitAvailable.cache?.set?.(undefined, Promise.resolve(false))
61  }
62  
63  /**
64   * Clear the git availability cache.
65   * Used for testing purposes.
66   */
67  export function clearGitAvailabilityCache(): void {
68    checkGitAvailable.cache?.clear?.()
69  }