iterative/vscode-dvc

View on GitHub
extension/src/cli/dvc/discovery.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
98%
import { CliCompatible, isVersionCompatible } from './version'
import { IExtensionSetup } from '../../interfaces'
import { Toast } from '../../vscode/toast'
import { Response } from '../../vscode/response'
import {
  ConfigKey,
  getConfigValue,
  setUserConfigValue
} from '../../vscode/config'
import { getFirstWorkspaceFolder } from '../../vscode/workspaceFolders'
import { delay } from '../../util/time'
import { SetupSection } from '../../setup/webview/contract'

const warnWithSetupAction = async (
  setup: IExtensionSetup,
  warningText: string
): Promise<void> => {
  const response = await Toast.warnWithOptions(warningText, Response.SHOW_SETUP)

  if (response === Response.SHOW_SETUP) {
    return setup.showSetup(SetupSection.DVC)
  }
}

const warnUnableToVerifyVersion = (setup: IExtensionSetup) => {
  void warnWithSetupAction(
    setup,
    'The extension cannot initialize as we were unable to verify the DVC CLI version.'
  )
}

const warnVersionIncompatible = (setup: IExtensionSetup): void => {
  void warnWithSetupAction(
    setup,
    'The extension cannot initialize because the DVC CLI version is incompatible.'
  )
}

const warnUserCLIInaccessible = async (
  setup: IExtensionSetup
): Promise<void> => {
  if (getConfigValue<boolean>(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE)) {
    return
  }

  const response = await Toast.warnWithOptions(
    'An error was thrown when trying to access the CLI.',
    Response.SHOW_SETUP,
    Response.NEVER
  )

  switch (response) {
    case Response.SHOW_SETUP:
      return setup.showSetup(SetupSection.DVC)
    case Response.NEVER:
      return setUserConfigValue(ConfigKey.DO_NOT_SHOW_CLI_UNAVAILABLE, true)
  }
}

const warnUser = (
  setup: IExtensionSetup,
  cliCompatible: CliCompatible
): void => {
  if (!setup.shouldWarnUserIfCLIUnavailable()) {
    return
  }
  switch (cliCompatible) {
    case CliCompatible.NO_INCOMPATIBLE:
      return warnVersionIncompatible(setup)
    case CliCompatible.NO_CANNOT_VERIFY:
      void warnUnableToVerifyVersion(setup)
      return
    case CliCompatible.NO_NOT_FOUND:
      void warnUserCLIInaccessible(setup)
  }
}

type CanRunCli = {
  isAvailable: boolean
  isCompatible: boolean | undefined
  version: string | undefined
}

const isCliCompatible = (cliCompatible: CliCompatible): boolean | undefined => {
  if (cliCompatible === CliCompatible.NO_NOT_FOUND) {
    return
  }

  return cliCompatible === CliCompatible.YES
}

const getVersionDetails = async (
  setup: IExtensionSetup,
  cwd: string,
  tryGlobalCli?: true
): Promise<
  CanRunCli & {
    cliCompatible: CliCompatible
  }
> => {
  const version = await setup.getCliVersion(cwd, tryGlobalCli)
  const cliCompatible = isVersionCompatible(version)
  const isCompatible = isCliCompatible(cliCompatible)
  return { cliCompatible, isAvailable: !!isCompatible, isCompatible, version }
}

const processVersionDetails = (
  setup: IExtensionSetup,
  cliCompatible: CliCompatible,
  isAvailable: boolean,
  isCompatible: boolean | undefined,
  version: string | undefined
): CanRunCli => {
  warnUser(setup, cliCompatible)

  return {
    isAvailable,
    isCompatible,
    version
  }
}

const tryGlobalFallbackVersion = async (
  setup: IExtensionSetup,
  cwd: string
): Promise<CanRunCli> => {
  const tryGlobal = await getVersionDetails(setup, cwd, true)
  const { cliCompatible, isAvailable, isCompatible, version } = tryGlobal

  if (!isCompatible) {
    return {
      isAvailable,
      isCompatible,
      version
    }
  }

  setup.unsetPythonBinPath()

  return processVersionDetails(
    setup,
    cliCompatible,
    isAvailable,
    isCompatible,
    version
  )
}

const extensionCanAutoRunCli = async (
  setup: IExtensionSetup,
  cwd: string
): Promise<CanRunCli> => {
  const {
    cliCompatible: pythonCliCompatible,
    isAvailable: pythonVersionIsAvailable,
    isCompatible: pythonVersionIsCompatible,
    version: pythonVersion
  } = await getVersionDetails(setup, cwd)

  if (pythonCliCompatible === CliCompatible.NO_NOT_FOUND) {
    const globalResults = await tryGlobalFallbackVersion(setup, cwd)

    if (globalResults.isCompatible) {
      return globalResults
    }
  }

  return processVersionDetails(
    setup,
    pythonCliCompatible,
    pythonVersionIsAvailable,
    pythonVersionIsCompatible,
    pythonVersion
  )
}

export const extensionCanRunCli = async (
  setup: IExtensionSetup,
  cwd: string
): Promise<CanRunCli> => {
  if (await setup.isPythonExtensionUsed()) {
    return extensionCanAutoRunCli(setup, cwd)
  }

  const { cliCompatible, isAvailable, isCompatible, version } =
    await getVersionDetails(setup, cwd)

  return processVersionDetails(
    setup,
    cliCompatible,
    isAvailable,
    isCompatible,
    version
  )
}

const checkVersion = async (
  setup: IExtensionSetup,
  cwd: string,
  checkGlobal?: true
) => {
  const version = await setup.getCliVersion(cwd, checkGlobal)
  const cliCompatible = isVersionCompatible(version)
  return isCliCompatible(cliCompatible)
}

export const recheck = async (
  setup: IExtensionSetup,
  run: () => Promise<void[] | undefined>,
  recheckInterval: number
): Promise<void> => {
  await delay(recheckInterval)
  const roots = setup.getRoots()
  const cwd = roots.length > 0 ? roots[0] : getFirstWorkspaceFolder()

  if (!cwd || setup.getAvailable()) {
    return
  }

  let isCompatible = await checkVersion(setup, cwd)
  if (!isCompatible) {
    isCompatible = await checkVersion(setup, cwd, true)
  }

  if (!isCompatible) {
    return recheck(setup, run, recheckInterval)
  }

  void run()
}