Source: utils/check.js

import * as TYPES from '#types/types.js' // eslint-disable-line
import semver from 'semver'
import { $ } from 'execa'
import isOnline from 'is-online'
import chalk from 'chalk'
import * as git from '#git.js'
import ora from 'ora'
import { isTagInCurrentCommit } from '#git.js'

/**
 * @ignore
 * @typedef {TYPES.CLI_PACKAGE_JSON} CLI_PACKAGE_JSON {@link CLI_PACKAGE_JSON}
 * @typedef {TYPES.DATA} DATA {@link DATA}
 */

/**
 * #### Check node version
 * Will throw an error if node version is wrong
 * @async
 * @memberof CHECK
 * @param {CLI_PACKAGE_JSON} packageJson - CLI package.json
 * @returns undefined
 * @throws console.error & process.exit(1) if node version is wrong
 */
async function checkNode (packageJson) {
  const engines = packageJson.engines
  if (!semver.satisfies(process.version, engines.node)) {
    console.error(`${chalk.bold.red('Error:')} Required node version ${chalk.yellow(engines.node)} not satisfied with current version ${process.version}\n`)
    process.exit(1)
  }
}

/**
 * #### Check if shell bin exists
 * @async
 * @memberof CHECK
 * @param {string} bin - bin name
 * @returns undefined
 * @throws process.exit(1) if bin does not exist
 */
async function checkIfBinExists (bin) {
  try {
    await $`which ${bin}`
  } catch (error) {
    console.error(`${chalk.bold.red('Error:')} This script requires ${chalk.yellow(bin)} to be instaled\n`)
    process.exit(1)
  }
}

/**
 * #### Check if git repo and git root
 * @async
 * @memberof CHECK
 * @returns undefined
 * @throws process.exit(1) if not git repo or not git root
 */
async function checkIfGitRepoAndRoot () {
  const isRepo = await git.isRepo()
  const isRoot = await git.isRoot()
  if (!isRepo) {
    console.error(`${chalk.bold.red('Error:')} This script requires to be run in a git repository\n`)
    process.exit(1)
  }
  if (!isRoot) {
    console.error(`${chalk.bold.red('Error:')} This script requires to be run in the root of a git repository\n`)
    process.exit(1)
  }
}

/**
 * #### Check if commit message exists
 * @async
 * @memberof CHECK
 * @returns undefined
 * @throws process.exit(1) if no commit message exists
 */
async function checkIfCommitMessageExists () {
  const isCommits = await git.isCommits()
  if (!isCommits) {
    console.error(`${chalk.bold.red('Error:')} This script requires to be run in a git repository with at least one commit\n`)
    process.exit(1)
  }
}

/**
 * #### Check if current commit has a tag
 * @async
 * @memberof CHECK
 * @param {DATA} data - data
 * @returns undefined
 * @throws process.exit(1)
 */
async function checkIfCommitHasTag (data) {
  data.git.isTagInCurrentCommit = await isTagInCurrentCommit()
  if (data.git.isTagInCurrentCommit) {
    console.error(`${chalk.bold.red('Error:')} Your current commit is already marked as a tagged release, you need at least one commit between releases\n`)
    process.exit(1)
  }
}

/**
 * #### check local repo status
 * @async
 * @memberof CHECK
 * @param {DATA} data - data
 * @returns undefined
 * @throws process.exit(1) or warning if repo status is not ok
 */
async function checkLocalRepo (data) {
  if (data.git.branch !== 'master' && data.git.branch !== 'main') {
    console.warn(`${chalk.red('Warning:')} you are not on ${chalk.blue('master')} or ${chalk.blue('main')} branch\n`)
  }
  if (data.git.stagedFiles.length > 0) {
    console.warn(`${chalk.red('Warning:')} You alredy have files staged for commit. It is recommended to commit them separately before proceeding with the release.\n`)
  }
}

/**
 * #### check remote repo status
 * @async
 * @memberof CHECK
 * @param {DATA} data - data
 * @returns undefined
 * @throws process.exit(1) or warning if remote repo status is not ok
 */
function checkRemoteRepo (data) {
  if (data.git.isRemote) {
    if (data.git.isBranchBehind) {
      console.error(`${chalk.bold.red('Error:')} Your branch is behind ${chalk.cyan(data.git.branchUpstream)}\n`)
      process.exit(1)
    }
    if (data.git.isBranchAhead) {
      console.error(`${chalk.bold.red('Error:')} Your branch is ahead ${chalk.cyan(data.git.branchUpstream)}\n`)
      process.exit(1)
    }
    if (data.git.isBranchDetached) {
      console.error(`${chalk.bold.red('Error:')} Your branch is detached\n`)
      process.exit(1)
    }
    // check if remote protocol not ssh and disable remote release type
    if (data.git.remote.protocol && data.git.remote.protocol !== 'ssh') {
      data.git.isRemote = false
      console.error(`${chalk.bold.red('Error:')} You are using non-SSH remote ${chalk.cyan(data.git.remote.href)}`)
      console.error('Remote release type only works with SSH remote URLs.\n')
      process.exit(1)
    }
  }
}

/**
 * #### check ssh connection
 * @async
 * @memberof CHECK
 * @param {string} serverUrl - server url
 * @returns undefined
 * @throws process.exit(1) if  ssh connection fails
 */
async function checkSshConnection (serverUrl) {
  const spinner = ora('Check ssh connection').start()
  try {
    await $`ssh -T -o ConnectTimeout=5 -o BatchMode=yes ${serverUrl}`
    spinner.succeed()
  } catch (error) {
    if (!error.stderr.includes('successfully authenticated')) {
      spinner.fail()
      console.error(`${chalk.bold.red('Error:')} Host key verification failed.`)
      console.error(`SSH connection to ${chalk.cyan(serverUrl)} failed.`)
      console.error('Make sure you\'ve set up your SSH key and added it to your account.\n')
      process.exit(1)
    }
    spinner.succeed()
  }
}

/**
 * #### Check ssh permissions to current repo
 * @async
 * @memberof CHECK
 * @returns undefined
 * @throws process.exit(1) if ssh permissions to current repo fails
 */
async function checkSshPermissions () {
  const spinner = ora('Check ssh permissions').start()
  try {
    await $`git push --dry-run`
    spinner.succeed()
  } catch (error) {
    spinner.fail()
    if (error.stderr.includes('denied')) {
      console.error(`${chalk.bold.red('Error:')} Permission to current repo denied to your user.`)
      console.error('To fix this, the owner of the repository needs to add your account as a collaborator on the repository or to a team that has write access to the repository.\n')
      process.exit(1)
    }
    console.error(error.stderr)
    process.exit(1)
  }
}

/**
 * #### check is online
 * @async
 * @memberof CHECK
 * @returns undefined
 * @throws process.exit(1) if not online
 */
async function checkIsOnline () {
  console.log('')
  const spinner = ora('Check internet connection').start()
  const isOnlineResult = await isOnline()
  if (!isOnlineResult) {
    spinner.fail()
    console.error(`${chalk.bold.red('Error:')} No Internet connection\n`)
    process.exit(1)
  }
  spinner.succeed()
}

export {
  checkNode,
  checkIfBinExists,
  checkIfGitRepoAndRoot,
  checkIfCommitMessageExists,
  checkIfCommitHasTag,
  checkLocalRepo,
  checkRemoteRepo,
  checkSshConnection,
  checkSshPermissions,
  checkIsOnline
}

Table of contents