// Copyright 2017, University of Colorado Boulder

/**
 * Command execution wrapper (with common settings)
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

'use strict';

const child_process = require( 'child_process' );
const winston = require( 'winston' );


/**
 * Executes a command, with specific arguments and in a specific directory (cwd).
 * @public
 *
 * Resolves with the stdout: {string}
 * Rejects with { code: {number}, stdout: {string} } -- Happens if the exit code is non-zero.
 *
 * @param {string} cmd - The process to execute. Should be on the current path.
 * @param {Array.<string>} args - Array of arguments. No need to extra-quote things.
 * @param {string} cwd - The working directory where the process should be run from
 * @returns {Promise.<string>} - Stdout
 * @rejects {ExecuteError}
 */
module.exports = function ( cmd, args, cwd ) {
  class ExecuteError extends Error {
    /**
     * @param {string} cmd
     * @param {Array.<string>} args
     * @param {string} cwd
     * @param {string} stdout
     * @param {string} stderr
     * @param {number} code - exit code
     */
    constructor( cmd, args, cwd, stdout, stderr, code ) {
      super( `${cmd} ${args.join( ' ' )} in ${cwd} failed with exit code ${code} and stdout:\n${stdout}` );

      // @public
      this.cmd = cmd;
      this.args = args;
      this.cwd = cwd;
      this.stdout = stdout;
      this.stderr = stderr;
      this.code = code;
    }
  }

  return new Promise( ( resolve, reject ) => {
    const process = child_process.spawn( cmd, args, {
      cwd: cwd
    } );
    winston.debug( `Running ${cmd} ${args.join( ' ' )} from ${cwd}` );

    let stdout = ''; // to be appended to
    let stderr = '';

    process.stderr.on( 'data', data => {
      stderr += data;
    } );
    process.stdout.on( 'data', data => {
      stdout += data;
    } );

    process.on( 'close', code => {
      winston.debug( `Command ${cmd} finished. Output is below.` );

      winston.debug( stderr && `stderr: ${stderr}` || 'stderr is empty.' );
      winston.debug( stdout && `stdout: ${stdout}` || 'stdout is empty.' );

      if ( code !== 0 ) {
        reject( new ExecuteError( cmd, args, cwd, stdout, stderr, code ) );
      }
      else {
        resolve( stdout );
      }
    } );
  } );
};

@@ -267,16 +267,105 @@
 ton' );%0A
+const _ = require( 'lodash' ); // eslint-disable-line%0Aconst assert = require( 'assert' );
 %0A%0A/**%0A *
@@ -842,16 +842,45 @@
 un from%0A
+ * @param %7BObject%7D %5Boptions%5D%0A
  * @retu
@@ -967,17 +967,16 @@
 function
- 
 ( cmd, a
@@ -983,20 +983,549 @@
 rgs, cwd
- ) %7B
+, options ) %7B%0A%0A  options = _.extend( %7B // eslint-disable-line%0A%0A    // %7B'reject'%7C'resolve'%7D - whether errors should be rejected or resolved.  If errors are resolved, then an object%0A    //                      - of the form %7Bcode:number,stdout:string,stderr:string%7D is returned. 'resolve' allows usage%0A    //                      - in Promise.all without exiting on the 1st failure%0A    errors: 'reject'%0A  %7D, options );%0A  assert && assert( options.errors === 'reject' %7C%7C options.errors === 'resolve', 'Errors must reject or resolve' );%0A
 %0A  class
@@ -2762,24 +2762,158 @@
 empty.' );%0A%0A
+      if ( options.errors === 'resolve' ) %7B%0A        resolve( %7B code: code, stdout: stdout, stderr: stderr %7D );%0A      %7D%0A      else %7B%0A  
       if ( c
@@ -2922,24 +2922,26 @@
 e !== 0 ) %7B%0A
+  
         reje
@@ -3002,34 +3002,38 @@
 code ) );%0A      
-%7D%0A
+  %7D%0A  
       else %7B%0A   
@@ -3029,32 +3029,34 @@
  else %7B%0A        
+  
 resolve( stdout 
@@ -3052,17 +3052,67 @@
 ve( 
-stdout );
+%7B code: code, stdout: stdout, stderr: stderr %7D );%0A        %7D
 %0A   

Add an option to resolve errors, see https://github.com/phetsims/perennial/issues/207