tunnckoCore/async-exec-cmd

View on GitHub
index.js

Summary

Maintainability
A
3 hrs
Test Coverage
/*!
 * async-exec-cmd <https://github.com/tunnckoCore/async-exec-cmd>
 *
 * Copyright (c) 2015 Charlike Mike Reagent, contributors.
 * Released under the MIT license.
 */

/* jshint asi:true */

'use strict'

var isEmptyFunction = require('is-empty-function')
var handleArguments = require('handle-arguments')
var handleErrors = require('handle-errors')('async-exec-cmd')
var union = require('arr-union')
var spawn = require('cross-spawn')
var typeOf = require('kind-of')

var error = handleErrors.error
var type = handleErrors.type

/**
 * > Async execute command via spawn. All arguments are rebuilt, merged, structured, normalized
 * and after all passed to [cross-spawn][cross-spawn], which actually is Node's `spawn`.
 *
 * **Example:**
 *
 * ```js
 * var asyncExecCmd = require('async-exec-cmd')
 *
 * var child = asyncExecCmd('npm install', [
 *   '--save-dev', 'bluebird'
 * ], function __cb (err, res, code, buffer) {
 *   if (err) {
 *     console.error(err, code)
 *     return
 *   }
 *
 *   console.log(res, code, buffer)
 * })
 * ```
 *
 * @name asyncExecCmd
 * @param  {String} `<cmd>`
 * @param  {Array}  `[args]`
 * @param  {Object} `[opts]`
 * @param  {Function} `<callback>`
 * @return {Stream} actually what `child_process.spawn` returns
 * @api public
 */
module.exports = function asyncExecCmd () {
  var argz = handleArguments(arguments)
  argz = checkArguments(argz)
  argz = buildArguments(argz)

  return buildSpawn(argz.cmd, argz.args, argz.opts, argz.callback)
}

/**
 * > Create flexible arguments - check types.
 *
 * @param  {Object} `argz`
 * @return {Object}
 * @api private
 */
function checkArguments (argz) {
  if (!argz.args.length) {
    return error('first argument cant be function')
  }

  if (isEmptyFunction(argz.cb.toString())) {
    return error('should have `callback` (non empty callback)')
  }

  if (typeOf(argz.args[0]) !== 'string') {
    return type('expect `cmd` be string', argz.cb)
  }

  if (typeOf(argz.args[1]) === 'object') {
    argz.args[2] = argz.args[1]
    argz.args[1] = []
  }

  if (typeOf(argz.args[2]) !== 'object') {
    argz.args[2] = {}
  }

  return {
    cmd: argz.args[0],
    args: argz.args[1],
    opts: argz.args[2],
    callback: argz.cb
  }
}

/**
 * > Build/structure already checked arguments.
 *
 * @param  {Object} `argz`
 * @return {Object}
 * @api private
 */
function buildArguments (argz) {
  var args = argz.cmd.split(' ')
  argz.cmd = args.shift()
  argz.args = union([], args || [], argz.args || [])
  return argz
}

/**
 * > Handle cross-spawn.
 *
 * @param  {String} `cmd`
 * @param  {Array} `args`
 * @param  {Object} `opts`
 * @param  {Function} `callback`
 * @return {Stream} actually what `child_process.spawn` returns
 * @api private
 */
function buildSpawn (cmd, args, opts, callback) {
  var proc = spawn(cmd, args, opts)
  var buffer = new Buffer('')
  var cmdError = {}

  cmd = cmd + ' ' + args.join(' ')

  if (proc.stdout) {
    proc.stdout.on('data', function indexOnData (data) {
      buffer = Buffer.concat([buffer, data])
    })
  }

  proc
    .on('error', function spawnOnError (err) {
      cmdError = new CommandError({
        command: cmd,
        message: err.message ? err.message : undefined,
        stack: err.stack ? err.stack : undefined,
        buffer: buffer ? buffer : undefined,
        status: err.status ? err.status : 1
      })
    })
    .on('close', function spawnOnClose (code) {
      if (code === 0) {
        callback(null, buffer.toString().trim(), code, buffer)
        return
      }

      cmdError = new CommandError({
        command: cmd,
        message: cmdError.message ? cmdError.message : undefined,
        stack: cmdError.stack ? cmdError.stack : undefined,
        buffer: cmdError.buffer ? cmdError.buffer : buffer,
        status: cmdError.status ? cmdError.status : code
      })
      callback(cmdError, undefined, code, undefined)
    })

  return proc
}

/**
 * > Construct `CommandError`.
 *
 * @param {Object} `err`
 * @api private
 */
function CommandError (err) {
  this.name = 'CommandError'
  this.command = err.command
  this.message = err.message
  this.stack = err.stack
  this.buffer = err.buffer
  this.status = err.status
  Error.captureStackTrace(this, CommandError)
}

CommandError.prototype = Object.create(Error.prototype)
CommandError.prototype.constructor = CommandError