scolladon/sfdx-git-delta

View on GitHub
src/utils/cliHelper.ts

Summary

Maintainability
A
0 mins
Test Coverage
'use strict'
import { join } from 'path'
import { format } from 'util'

import GitAdapter from '../adapter/GitAdapter'
import messages from '../locales/en'
import {
  getLatestSupportedVersion,
  isVersionSupported,
} from '../metadata/metadataManager'
import type { Config } from '../types/config'
import type { Work } from '../types/work'

import asyncFilter from './asyncFilter'
import { readFile, dirExists, fileExists, sanitizePath } from './fsUtils'

const isBlank = (str: string) => !str || /^\s*$/.test(str)

const GIT_SHA_PARAMETERS: (keyof Config)[] = ['to', 'from']
const SOURCE_API_VERSION_ATTRIBUTE = 'sourceApiVersion'
const SFDX_PROJECT_FILE_NAME = 'sfdx-project.json'

export default class CLIHelper {
  protected readonly config: Config
  protected readonly gitAdapter: GitAdapter

  constructor(protected readonly work: Work) {
    this.config = work.config
    this.gitAdapter = GitAdapter.getInstance(work.config)
  }

  protected async _validateGitSha() {
    const errors: string[] = []
    await Promise.all(
      GIT_SHA_PARAMETERS.filter((shaParameter: keyof Config) => {
        const shaValue: string = this.config[shaParameter] as string
        if (isBlank(shaValue)) {
          errors.push(
            format(messages.errorGitSHAisBlank, shaParameter, shaValue)
          )
          return false
        }
        return true
      }).map(async (shaParameter: keyof Config) => {
        const shaValue: string = this.config[shaParameter] as string
        try {
          const ref: string = await this.gitAdapter.parseRev(shaValue)
          ;(this.config[shaParameter] as string) = ref
        } catch (error) {
          errors.push(
            format(messages.errorParameterIsNotGitSHA, shaParameter, shaValue)
          )
        }
      })
    )

    return errors
  }

  public async validateConfig() {
    this._sanitizeConfig()
    await this._handleDefault()
    const errors: string[] = []

    const directoriesPromise = this._filterDirectories()
    const filesPromise = this._filterFiles()

    const directories = await directoriesPromise
    directories.forEach((dir: string) =>
      errors.push(format(messages.errorPathIsNotDir, dir))
    )

    const files = await filesPromise
    files.forEach((file: string) =>
      errors.push(format(messages.errorPathIsNotFile, file))
    )

    try {
      await this.gitAdapter.setGitDir()
    } catch {
      errors.push(format(messages.errorPathIsNotGit, this.config.repo))
    }

    const gitErrors = await this._validateGitSha()
    errors.push(...gitErrors)

    if (errors.length > 0) {
      throw new Error(errors.join(', '))
    }

    await this.gitAdapter.configureRepository()
  }

  protected _filterDirectories() {
    return asyncFilter(
      [this.config.output, join(this.config.repo, this.config.source)].filter(
        Boolean
      ),
      async (dir: string) => {
        const exist = await dirExists(dir)
        return !exist
      }
    )
  }

  protected _filterFiles() {
    return asyncFilter(
      [
        this.config.ignore,
        this.config.ignoreDestructive,
        this.config.include,
        this.config.includeDestructive,
      ].filter(Boolean),
      async (file: string) => {
        const exist = await fileExists(file)
        return !exist
      }
    )
  }

  protected async _handleDefault() {
    await this._getApiVersion()
    await this._apiVersionDefault()
  }

  protected async _getApiVersion() {
    const isInputVersionSupported = await isVersionSupported(
      this.config.apiVersion
    )
    if (!isInputVersionSupported) {
      const sfdxProjectPath = join(this.config.repo, SFDX_PROJECT_FILE_NAME)
      const exists = await fileExists(sfdxProjectPath)
      if (exists) {
        const sfdxProjectRaw = await readFile(sfdxProjectPath)
        const sfdxProject = JSON.parse(sfdxProjectRaw)
        this.config.apiVersion =
          parseInt(sfdxProject[SOURCE_API_VERSION_ATTRIBUTE]) || -1
      }
    }
  }

  protected async _apiVersionDefault() {
    const isInputVersionSupported = await isVersionSupported(
      this.config.apiVersion
    )

    if (!isInputVersionSupported) {
      const latestAPIVersionSupported = await getLatestSupportedVersion()
      if (
        this.config.apiVersion !== undefined &&
        this.config.apiVersion !== null
      ) {
        this.work.warnings.push(
          new Error(
            format(
              messages.warningApiVersionNotSupported,
              latestAPIVersionSupported
            )
          )
        )
      }
      this.config.apiVersion = latestAPIVersionSupported
    }
  }

  protected _sanitizeConfig() {
    this.config.repo = sanitizePath(this.config.repo)
    this.config.source = sanitizePath(this.config.source)
    this.config.output = sanitizePath(this.config.output)
    this.config.ignore = sanitizePath(this.config.ignore)
    this.config.ignoreDestructive = sanitizePath(this.config.ignoreDestructive)
    this.config.include = sanitizePath(this.config.include)
    this.config.includeDestructive = sanitizePath(
      this.config.includeDestructive
    )
  }
}