Home Reference Source Test Repository

src/api/nvm.js

import { spawn } from 'child_process'
import { statSync } from 'fs'
import semver from 'semver'
import { hookable } from './hooks'

const bin = process.env.NVM_DIR + '/nvm.sh'
const options = {
  cwd: process.cwd(),
  env: process.env,
  stdio: 'inherit',
  shell: process.env.SHELL
}

// 'shell' option introduced in Node v5.7.0
const shellOption = semver.satisfies(process.version, '>=5.7.0')

/**
 * Wether or not nvm exists.
 * @protected
 * @return {boolean} - Wether or not nvm exists
 */
export function exists () {
  try {
    return statSync(bin).isFile()
  } catch (e) {
    return false
  }
}

/* !esdoc bug
 * The Node ChildProcess
 * @external ChildProcess
 * @see {@link https://nodejs.org/api/child_process.html#child_process_class_childprocess}
 */

/* !esdoc bug
 * The Node EventEmitter, some Node versions do NOT expose the ChildProcess class
 * @external EventEmitter
 * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter}
 */

/**
 * Spawn a shell command with nvm shell.
 * @protected
 * @param  {command} [command=''] - A shell command to spawn
 * @return {(ChildProcess|EventEmitter)} - The spawned nvm command child process
 */
export function shell (command = '') {
  // source (period command) nvm before running the command
  const cmd = `. ${bin}` + (command ? ` && ${command}` : '')

  // the hole command has to be quoted starting w/ shell option
  /* istanbul ignore next: depends on process.version */
  const shcmd = shellOption ? `'${cmd}'` : cmd

  // invoke shell command (-c)
  const args = ['-c', shcmd]
  return spawn(process.env.SHELL, args, options)
}

/**
 * Process a nvm shell command as a Promise.
 * - It will resolve on 'close' code not equals 0.
 * - It will reject on 'error' and 'close' code equals 0.
 * @protected
 * @param  {command} [command=''] - A shell command
 * @return {Promise} - The nvm shell Promise
 */
export function nvm (command = '') {
  return new Promise((resolve, reject) => {
    shell(command)

    /*
     * 'error' event is emitted on spawn, kill, or send message Errors
     * no solution found yet to unit test
     */
     // reject on error
    .on('error', /* istanbul ignore next */ (error) => { reject(error.code || 1) })

    // resolve if code is 0, reject otherwise
    .on('close', (code) => { (code === 0 ? resolve : reject)(code) })
  })
}

/**
 * {@link Hookable} nvm
 * @public
 * @function nvm
 * @param  {command}       [command='']  - A shell command
 * @param  {Hooks}         [hooks]       - {@link nvm} Hooks
 * @param  {{pre: Hook}}   [hooks.pre]   - A `pre` hook
 * @param  {{post: Hook}}  [hooks.post]  - A `post` hook
 * @param  {{error: Hook}} [hooks.error] - A `error` hook
 * @return {Promise} - The {@link nvm} Promise
 */
export default hookable(nvm)
// exporting this will fail to document as function w/ esdoc