Repository: serverless/serverless (JavaScript) — Bug Fix

Issue:
attempting to run a non existing command is not handled
```
Eslams-MacBook-Pro:test-projects eslam$ sldev whate
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.0.0
`-------'

  Type Error ---------------------------------------------

     Cannot read property 'options' of undefined

  Stack Trace --------------------------------------------

TypeError: Cannot read property 'options' of undefined
    at PluginManager.validateOptions (/Users/eslam/serverless-stuff/serverless/lib/classes/PluginManager.js:31:48)
    at PluginManager.run (/Users/eslam/serverless-stuff/serverless/lib/classes/PluginManager.js:50:10)
    at Serverless.run (/Users/eslam/serverless-stuff/serverless/lib/Serverless.js:64:33)
    at /Users/eslam/serverless-stuff/serverless/bin/serverless:19:50
    at tryCatcher (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/promise.js:502:31)
    at Promise._settlePromise (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/promise.js:559:18)
    at Promise._settlePromise0 (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/promise.js:604:10)
    at Promise._settlePromises (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/promise.js:683:18)
    at Async._drainQueue (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/async.js:138:16)
    at Async._drainQueues (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/async.js:148:10)
    at Immediate.Async.drainQueues [as _onImmediate] (/Users/eslam/serverless-stuff/serverless/node_modules/bluebird/js/release/async.js:17:14)
    at processImmediate [as _immediateCallback] (timers.js:383:17)

  Get Support --------------------------------------------
     Docs:          https://git.io/voP4S
     Bugs:          https://git.io/voP45

     Please report this error. We think it might be a bug.

Eslams-MacBook-Pro:test-projects eslam$ 
```



Discussion:

Resolving patch:
diff --git a/.travis.yml b/.travis.yml
index 9bcbcf59aff..a95ccea4ce2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,7 @@ script:
   - npm test
   # Only Run Integration Tests and ESLINT for the first job in the whole build to make the build faster
   - if [[ "$TRAVIS_JOB_NUMBER" =~ [0-9]+\.1 ]]; then npm run integration-test; fi
-  - if [[ "$TRAVIS_JOB_NUMBER" =~ [0-9]+\.1 ]]; then ./scripts/eslint.sh; fi
+  - if [[ "$TRAVIS_JOB_NUMBER" =~ [0-9]+\.1 ]]; then npm run lint; fi
 
 after_success:
   - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
diff --git a/bin/serverless b/bin/serverless
index 3dfe4e72ff6..c915a955067 100755
--- a/bin/serverless
+++ b/bin/serverless
@@ -2,10 +2,18 @@
 
 'use strict';
 
-const Serverless = require('../lib/Serverless');
+const BbPromise = require('bluebird');
+const logError = require('../lib/classes/Error').logError;
 
-const serverless = new Serverless({
-  interactive: typeof process.env.CI === 'undefined',
-});
+process.on('unhandledRejection', (e) => logError(e));
 
-serverless.init().then(() => serverless.run());
+(() => BbPromise.resolve().then(() => {
+  // requiring here so that if anything went wrong,
+  // during require, it will be caught.
+  const Serverless = require('../lib/Serverless'); // eslint-disable-line global-require
+  const serverless = new Serverless({
+    interactive: typeof process.env.CI === 'undefined',
+  });
+
+  return serverless.init().then(() => serverless.run());
+}).catch(e => logError(e)))();
diff --git a/lib/Serverless.js b/lib/Serverless.js
index 5a59244c11f..a0cdef2453b 100644
--- a/lib/Serverless.js
+++ b/lib/Serverless.js
@@ -2,19 +2,16 @@
 
 require('shelljs/global');
 
+const BbPromise = require('bluebird');
 const CLI = require('./classes/CLI');
 const Config = require('./classes/Config');
 const YamlParser = require('./classes/YamlParser');
 const PluginManager = require('./classes/PluginManager');
 const Utils = require('./classes/Utils');
 const Service = require('./classes/Service');
-const SError = require('./classes/Error');
+const SError = require('./classes/Error').SError;
 const Version = require('./../package.json').version;
 
-process.on('unhandledRejection', (e) => {
-  throw new SError(e);
-});
-
 class Serverless {
   constructor(config) {
     let configObject = config;
@@ -43,7 +40,7 @@ class Serverless {
 
   init() {
     // create a new CLI instance
-    this.cli = new CLI(this, this.config.interactive);
+    this.cli = new CLI(this);
 
     // get an array of commands and options that should be processed
     this.processedInput = this.cli.processInput();
@@ -51,6 +48,8 @@ class Serverless {
     // set the options
     this.pluginManager.setOptions(this.processedInput.options);
 
+    if (!this.processedInput.options.noGreeting) this.cli.asciiGreeting();
+
     return this.service.load(this.processedInput.options)
       .then(() => {
         // load all plugins
@@ -65,8 +64,10 @@ class Serverless {
   run() {
     if (!this.cli.displayHelp(this.processedInput) && this.processedInput.commands.length) {
       // trigger the plugin lifecycle when there's something which should be processed
-      this.pluginManager.run(this.processedInput.commands);
+      return this.pluginManager.run(this.processedInput.commands);
     }
+
+    return BbPromise.resolve();
   }
 
   getVersion() {
diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js
index a57a12056c3..4eb43c8336d 100644
--- a/lib/classes/CLI.js
+++ b/lib/classes/CLI.js
@@ -1,16 +1,14 @@
 'use strict';
 
 const version = require('../../package.json').version;
-const Spinner = require('cli-spinner').Spinner;
 const minimist = require('minimist');
 const _ = require('lodash');
 const os = require('os');
 const chalk = require('chalk');
 
 class CLI {
-  constructor(serverless, interactive, inputArray) {
+  constructor(serverless, inputArray) {
     this.serverless = serverless;
-    this.interactive = interactive;
     this.inputArray = (typeof inputArray !== 'undefined' ? inputArray : []);
     this.loadedPlugins = [];
   }
@@ -19,30 +17,6 @@ class CLI {
     this.loadedPlugins = plugins;
   }
 
-  displayHelp(processedInput) {
-    const commands = processedInput.commands;
-    const options = processedInput.options;
-
-    // if only "help" or "h" was entered
-    if ((commands.length === 0 && (options.help || options.h)) ||
-      (commands.length === 1 && (commands.indexOf('help') > -1))) {
-      this.generateMainHelp();
-      return true;
-    }
-    // if only "version" or "v" was entered
-    if ((commands.length === 0 && (options.version || options.v)) ||
-      (commands.length === 1 && (commands.indexOf('version') > -1))) {
-      this.getVersionNumber();
-      return true;
-    }
-    // if "help" was entered in combination with commands (or one command)
-    if (commands.length >= 1 && (options.help || options.h)) {
-      this.generateCommandsHelp(commands);
-      return true;
-    }
-    return false;
-  }
-
   processInput() {
     let inputArray;
 
@@ -82,17 +56,39 @@ class CLI {
     return commandsAndOptions;
   }
 
-  generateMainHelp() {
-    this.asciiGreeting();
+  displayHelp(processedInput) {
+    const commands = processedInput.commands;
+    const options = processedInput.options;
 
-    console.log('');
+    // if only "help" or "h" was entered
+    if ((commands.length === 0 && (options.help || options.h)) ||
+      (commands.length === 1 && (commands.indexOf('help') > -1))) {
+      this.generateMainHelp();
+      return true;
+    }
+    // if only "version" or "v" was entered
+    if ((commands.length === 0 && (options.version || options.v)) ||
+      (commands.length === 1 && (commands.indexOf('version') > -1))) {
+      this.getVersionNumber();
+      return true;
+    }
+    // if "help" was entered in combination with commands (or one command)
+    if (commands.length >= 1 && (options.help || options.h)) {
+      this.generateCommandsHelp(commands);
+      return true;
+    }
+    return false;
+  }
+
+  generateMainHelp() {
+    this.consoleLog('');
 
-    console.log(chalk.yellow.underline('Commands'));
-    console.log(chalk.dim('* Serverless documentation: http://docs.serverless.com'));
-    console.log(chalk.dim('* You can run commands with "serverless" or the shortcut "sls"'));
-    console.log(chalk.dim('* Pass "--help" after any <command> for contextual help'));
+    this.consoleLog(chalk.yellow.underline('Commands'));
+    this.consoleLog(chalk.dim('* Serverless documentation: http://docs.serverless.com'));
+    this.consoleLog(chalk.dim('* You can run commands with "serverless" or the shortcut "sls"'));
+    this.consoleLog(chalk.dim('* Pass "--help" after any <command> for contextual help'));
 
-    console.log('');
+    this.consoleLog('');
 
     const sortedPlugins = this.loadedPlugins.sort();
 
@@ -106,7 +102,7 @@ class CLI {
             const command = firstLevelCommand;
             const usage = firstLevelCommandObject.usage;
             const dots = _.repeat('.', dotsLength - command.length);
-            console.log('%s %s %s', chalk.yellow(command), chalk.dim(dots), usage);
+            this.consoleLog('%s %s %s', chalk.yellow(command), chalk.dim(dots), usage);
           }
           _.forEach(firstLevelCommandObject.commands,
             (secondLevelCommandObject, secondLevelCommand) => {
@@ -115,20 +111,20 @@ class CLI {
                 const command = `${firstLevelCommand} ${secondLevelCommand}`;
                 const usage = secondLevelCommandObject.usage;
                 const dots = _.repeat('.', dotsLength - command.length);
-                console.log('%s %s %s', chalk.yellow(command), chalk.dim(dots), usage);
+                this.consoleLog('%s %s %s', chalk.yellow(command), chalk.dim(dots), usage);
               }
             });
         });
     });
 
-    console.log('');
+    this.consoleLog('');
 
     // print all the installed plugins
-    console.log(chalk.yellow.underline('Plugins'));
+    this.consoleLog(chalk.yellow.underline('Plugins'));
     if (sortedPlugins.length) {
-      console.log(sortedPlugins.map((plugin) => plugin.constructor.name).join(', '));
+      this.consoleLog(sortedPlugins.map((plugin) => plugin.constructor.name).join(', '));
     } else {
-      console.log('No plugins added yet');
+      this.consoleLog('No plugins added yet');
     }
   }
 
@@ -143,11 +139,11 @@ class CLI {
           if (command === commands[0]) {
             if (commandObject.lifecycleEvents) {
               // print the name of the plugin
-              console.log(chalk.yellow.underline(`Plugin: ${plugin.constructor.name}`));
+              this.consoleLog(chalk.yellow.underline(`Plugin: ${plugin.constructor.name}`));
               // print the command with the corresponding usage
               const commandsDots = _.repeat('.', dotsLength - command.length);
               const commandsUsage = commandObject.usage;
-              console.log('%s %s %s',
+              this.consoleLog('%s %s %s',
                 chalk.yellow(command),
                 chalk.dim(commandsDots),
                 commandsUsage);
@@ -155,7 +151,7 @@ class CLI {
               _.forEach(commandObject.options, (optionsObject, option) => {
                 const optionsDots = _.repeat('.', dotsLength - option.length);
                 const optionsUsage = optionsObject.usage;
-                console.log('    %s %s %s',
+                this.consoleLog('    %s %s %s',
                   chalk.yellow(`--${option}`),
                   chalk.dim(optionsDots.slice(0, optionsDots.length - 6)),
                   optionsUsage);
@@ -174,11 +170,11 @@ class CLI {
                   if (secondLevelCommand === commands[1]) {
                     if (secondLevelCommandObject.lifecycleEvents) {
                       // print the name of the plugin
-                      console.log(chalk.yellow.underline(`Plugin: ${plugin.constructor.name}`));
+                      this.consoleLog(chalk.yellow.underline(`Plugin: ${plugin.constructor.name}`));
                       // print the command with the corresponding usage
                       const commandsDots = _.repeat('.', dotsLength - secondLevelCommand.length);
                       const commandsUsage = secondLevelCommandObject.usage;
-                      console.log('%s %s %s',
+                      this.consoleLog('%s %s %s',
                         chalk.yellow(secondLevelCommand),
                         chalk.dim(commandsDots),
                         commandsUsage);
@@ -186,7 +182,7 @@ class CLI {
                       _.forEach(secondLevelCommandObject.options, (optionsObject, option) => {
                         const optionsDots = _.repeat('.', dotsLength - option.length);
                         const optionsUsage = optionsObject.usage;
-                        console.log('    %s %s %s',
+                        this.consoleLog('    %s %s %s',
                           chalk.yellow(`--${option}`),
                           chalk.dim(optionsDots.slice(0, optionsDots.length - 6)),
                           optionsUsage);
@@ -199,61 +195,33 @@ class CLI {
       });
     }
 
-    console.log('');
+    this.consoleLog('');
   }
 
   getVersionNumber() {
-    console.log(version);
+    this.consoleLog(version);
   }
 
   asciiGreeting() {
     let art = '';
-    art = art + ' _______                             __' + os.EOL;
-    art = art + '|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.' + os.EOL;
-    art = art + '|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|' + os.EOL;
-    art = art + '|____   |_____|__|  \\___/|_____|__| |__|_____|_____|_____|' + os.EOL;
-    art = art + '|   |   |             The Serverless Application Framework' + os.EOL;
-    art = art + '|       |                           serverless.com, v' + version + os.EOL;
-    art = art + '`-------\'';
-
-    console.log(chalk.yellow(art));
-    console.log('');
+    art = `${art} _______                             __${os.EOL}`;
+    art = `${art}|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.${os.EOL}`;
+    art = `${art}|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|${os.EOL}`;
+    art = `${art}|____   |_____|__|  \\___/|_____|__| |__|_____|_____|_____|${os.EOL}`;
+    art = `${art}|   |   |             The Serverless Application Framework${os.EOL}`;
+    art = `${art}|       |                           serverless.com, v${version}${os.EOL}`;
+    art = `${art} -------\'`;
+
+    this.consoleLog(chalk.yellow(art));
+    this.consoleLog('');
   }
 
   log(message) {
-    console.log(`Serverless: ${chalk.yellow(`${message}`)}`);
-  }
-
-  isInteractive() {
-    return (process.stdout.isTTY && !process.env.CI) || this.interactive;
+    this.consoleLog(`Serverless: ${chalk.yellow(`${message}`)}`);
   }
 
-  spinner(message) {
-    let spinner;
-
-    if (this.isInteractive()) {
-      message = message ? message : '';
-      spinner = new Spinner('Serverless: ' + chalk.yellow('%s ' + message));
-      spinner.setSpinnerString('|/-\\');
-    } else {
-      // Non-interactive spinner object
-      spinner = {
-        start: (message) => {
-          message = message || 'Loading... ';
-          process.stdout.write(`Serverless: ${message}`);
-        },
-        stop: (message) => {
-          // Because of how spinner is used with normal library
-          // we do a small hack and still allow for setting message
-          if (message === true || message === false) {
-            message = 'Done!\n';
-          }
-          message = message || 'Done!\n';
-          process.stdout.write(message);
-        },
-      };
-    }
-    return spinner;
+  consoleLog(message) {
+    console.log(message); // eslint-disable-line no-console
   }
 }
 
diff --git a/lib/classes/Error.js b/lib/classes/Error.js
index bada56f610d..7c2fc71b70d 100644
--- a/lib/classes/Error.js
+++ b/lib/classes/Error.js
@@ -1,28 +1,52 @@
 'use strict';
+const chalk = require('chalk');
 
-const ServerlessError = class ServerlessError extends Error {
-  constructor(message, messageId) {
+module.exports.SError = class ServerlessError extends Error {
+  constructor(message) {
     super(message);
     this.name = this.constructor.name;
     this.message = message;
-    this.messageId = messageId;
-    Error.captureStackTrace(this, this.constructor.name);
+    Error.captureStackTrace(this, this.constructor);
   }
 };
 
-module.exports = ServerlessError;
-
-module.exports.errorCodes = {
-  UNKNOWN: 1,
-  MISSING_HOMEDIR: 2,
-  MISSING_AWS_CREDS_PROFILE: 3,
-  MISSING_AWS_CREDS: 4,
-  INVALID_PROJ_NAME: 5,
-  ZIP_TOO_BIG: 6,
-  INVALID_PROJECT_SERVERLESS: 7,
-  NO_LAMBDAS_TAGGED_DEPLOYABLE: 8,
-  ACCESS_DENIED: 9,
-  ENV_KEY_NOT_SET: 10,
-  INVALID_RESOURCE_NAME: 11,
-  NOT_IN_SERVERLESS_PROJECT: 12,
+module.exports.logError = (e) => {
+  const consoleLog = (message) => {
+    console.log(message); // eslint-disable-line no-console
+  };
+
+  const errorType = e.name.replace(/([A-Z])/g, ' $1');
+  let line = '';
+  while (line.length < 56 - errorType.length) {
+    line = `${line}-`;
+  }
+
+  consoleLog(chalk.yellow(` ${errorType} ${line}`));
+  consoleLog(' ');
+  consoleLog(chalk.yellow(`     ${e.message}`));
+  consoleLog(' ');
+
+  if (e.name !== 'ServerlessError') {
+    consoleLog(chalk.red('     For debugging logs, run again after setting SLS_DEBUG env var.'));
+    consoleLog(' ');
+  }
+
+  if (process.env.SLS_DEBUG) {
+    consoleLog(chalk.yellow('  Stack Trace --------------------------------------------'));
+    consoleLog(' ');
+    consoleLog(e.stack);
+    consoleLog(' ');
+  }
+
+  consoleLog(chalk.yellow('  Get Support --------------------------------------------'));
+  consoleLog(`${chalk.yellow('     Docs:          ')}${chalk.white('v1.docs.serverless.com')}`);
+  consoleLog(`${chalk.yellow('     Bugs:          ')}${chalk
+    .white('github.com/serverless/serverless/issues')}`);
+
+  if (e.name !== 'ServerlessError') {
+    consoleLog(' ');
+    consoleLog(chalk.red('     Please report this error. We think it might be a bug.'));
+  }
+
+  consoleLog(' ');
 };
diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js
index e20cc3d553d..34cee1db3ed 100644
--- a/lib/classes/PluginManager.js
+++ b/lib/classes/PluginManager.js
@@ -23,10 +23,17 @@ class PluginManager {
     this.loadServicePlugins(servicePlugins);
   }
 
+  validateCommands(commandsArray) {
+    // TODO: implement an option to get deeper than one level
+    if (!this.commands[commandsArray[0]]) {
+      throw new this.serverless.classes.Error(`command "${commandsArray[0]}" not found.`);
+    }
+  }
+
   validateOptions(commandsArray) {
     let options;
 
-    // TODO: implement an option to get deeper that two levels
+    // TODO: implement an option to get deeper than two levels
     if (commandsArray.length === 1) {
       options = this.commands[commandsArray[0]].options;
     } else {
@@ -35,12 +42,18 @@ class PluginManager {
 
     forEach(options, (value, key) => {
       if (value.required && (this.options[key] === true || !(this.options[key]))) {
-        throw new this.serverless.classes.Error(`Please provide the "${key}" option`);
+        const errorMessage = `This command requires the --${key} option. Please pass
+     it through. :)`;
+
+        throw new this.serverless.classes.Error(errorMessage);
       }
     });
   }
 
   run(commandsArray) {
+    // check if the command the user has entered is provided through a plugin
+    this.validateCommands(commandsArray);
+
     // check if all options are passed
     this.validateOptions(commandsArray);
 
@@ -61,8 +74,9 @@ class PluginManager {
     });
 
     if (hooks.length === 0) {
-      throw new this.serverless.classes.Error('The command you entered was not found. ' +
-        'Did you spell it correctly?');
+      const errorMessage = `The command you entered was not found.
+     Did you spell it correctly?`;
+      throw new this.serverless.classes.Error(errorMessage);
     }
 
     // using arr.reduce to sequentially run promises in array in order
diff --git a/lib/classes/Service.js b/lib/classes/Service.js
index cb0176172db..77a436ba768 100644
--- a/lib/classes/Service.js
+++ b/lib/classes/Service.js
@@ -1,6 +1,6 @@
 'use strict';
 
-const SError = require('./Error');
+const SError = require('./Error').SError;
 const path = require('path');
 const _ = require('lodash');
 const traverse = require('traverse');
@@ -71,8 +71,27 @@ class Service {
       })
       .then(() => that.serverless.yamlParser
         .parse(path.join(servicePath, 'serverless.env.yaml')))
-      .then((serverlessEnvYaml) => {
+      .then((serverlessEnvYamlParam) => {
+        const serverlessEnvYaml = serverlessEnvYamlParam;
+
+        // safely load serverless.env.yaml while avoiding
+        // reference errors
+        serverlessEnvYaml.vars = serverlessEnvYaml.vars || {};
+        serverlessEnvYaml.stages = serverlessEnvYaml.stages || {};
+        Object.keys(serverlessEnvYaml.stages).forEach(stage => {
+          serverlessEnvYaml.stages[stage] = serverlessEnvYaml.stages[stage] || {};
+          serverlessEnvYaml.stages[stage].vars = serverlessEnvYaml.stages[stage].vars || {};
+          serverlessEnvYaml.stages[stage].regions = serverlessEnvYaml.stages[stage].regions || {};
+          Object.keys(serverlessEnvYaml.stages[stage].regions).forEach(region => {
+            serverlessEnvYaml.stages[stage].regions[region] =
+              serverlessEnvYaml.stages[stage].regions[region] || {};
+            serverlessEnvYaml.stages[stage].regions[region].vars =
+              serverlessEnvYaml.stages[stage].regions[region].vars || {};
+          });
+        });
+
         that.environment = serverlessEnvYaml;
+
         return BbPromise.resolve(that);
       })
       .then(() => {
diff --git a/lib/plugins/aws/invoke/README.md b/lib/plugins/aws/invoke/README.md
index 155c3206216..7c347ec696d 100644
--- a/lib/plugins/aws/invoke/README.md
+++ b/lib/plugins/aws/invoke/README.md
@@ -8,3 +8,4 @@ This plugin invokes a lambda function.
 which is provided by the AWS SDK on the function the user passes in as a parameter.
 
 The output of the function is fetched and will be prompted on the console.
+
diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js
index a10516f393e..ec3f2ad46d0 100644
--- a/lib/plugins/create/create.js
+++ b/lib/plugins/create/create.js
@@ -37,17 +37,9 @@ class Create {
     };
   }
 
-  prompt() {
-    if (this.serverless.config.interactive && !this.options.noGreeting) {
-      this.serverless.cli.asciiGreeting();
-    }
-
+  validate() {
     this.serverless.cli.log('Creating new Serverless service...');
 
-    return BbPromise.resolve();
-  }
-
-  validate() {
     // Validate Name - AWS only allows Alphanumeric and - in name
     const nameOk = /^([a-zA-Z0-9-]+)$/.exec(this.options.name);
     if (!nameOk) {
diff --git a/package.json b/package.json
index 493d22e9a86..9bb0f5cb98f 100644
--- a/package.json
+++ b/package.json
@@ -35,7 +35,7 @@
   },
   "scripts": {
     "test": "istanbul cover _mocha tests/all -- -R spec --recursive",
-    "lint": "eslint . || true",
+    "lint": "eslint .",
     "integration-test": "mocha tests/integration_test"
   },
   "devDependencies": {
diff --git a/scripts/eslint.sh b/scripts/eslint.sh
deleted file mode 100755
index 61f632f10e4..00000000000
--- a/scripts/eslint.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-set -e
-
-mkdir -p tmp
-
-npm run lint | tee tmp/lint-output
-
-problems=`cat tmp/lint-output | grep -oE '[0-9]* problems' | grep -o "[0-9]*"`
-ratchet="100"
-
-echo "Problems found in current linting: $problems"
-
-if [ "$problems" -gt "$ratchet" ]
-then
-  echo "Linting issues above ratchet of $ratchet"
-  exit 1
-else
-  echo "Linting issues below ratchet of $ratchet"
-fi


Test patch:
diff --git a/lib/plugins/aws/deploy/tests/deployFunctions.js b/lib/plugins/aws/deploy/tests/deployFunctions.js
index 4cd92de472a..1cb539494a7 100644
--- a/lib/plugins/aws/deploy/tests/deployFunctions.js
+++ b/lib/plugins/aws/deploy/tests/deployFunctions.js
@@ -164,7 +164,7 @@ describe('deployFunctions', () => {
       serverless.config.servicePath = tmpDirPath;
 
       return awsDeploy.zipFunctions().then(() => {
-        expect(awsDeploy.deployedFunctions[0].zipFileData).to.be.not.empty;
+        expect(typeof awsDeploy.deployedFunctions[0].zipFileData).to.not.equal('undefined');
 
         // look into the zippedFileData
         const unzippedFileData = zip.load(awsDeploy.deployedFunctions[0].zipFileData);
@@ -195,7 +195,7 @@ describe('deployFunctions', () => {
       serverless.config.servicePath = tmpDirPath;
 
       return awsDeploy.zipFunctions().then(() => {
-        expect(awsDeploy.deployedFunctions[0].zipFileData).to.be.not.empty;
+        expect(typeof awsDeploy.deployedFunctions[0].zipFileData).to.not.equal('undefined');
 
         // look into the zippedFileData
         const unzippedFileData = zip.load(awsDeploy.deployedFunctions[0].zipFileData);
diff --git a/lib/plugins/aws/remove/tests/validate.js b/lib/plugins/aws/remove/tests/validate.js
index 303109f9f32..38756036f28 100644
--- a/lib/plugins/aws/remove/tests/validate.js
+++ b/lib/plugins/aws/remove/tests/validate.js
@@ -2,6 +2,7 @@
 
 const expect = require('chai').expect;
 const AwsRemove = require('../index');
+const BbPromise = require('bluebird');
 const Serverless = require('../../../../Serverless');
 
 describe('#validate()', () => {
@@ -15,12 +16,40 @@ describe('#validate()', () => {
       region: 'us-east-1',
     };
     awsRemove = new AwsRemove(serverless, options);
-
-    serverless.config.servicePath = true;
   });
 
   it('should throw an error if not inside a service (servicePath not defined)', () => {
     awsRemove.serverless.config.servicePath = false;
-    expect(() => awsRemove.validate()).to.throw(Error);
+
+    // if we go inside "then", then no error was thrown as expected
+    // so make assertion fail intentionally to let us know something is wrong
+    return BbPromise.resolve()
+      .then(() => awsRemove.validate())
+      .then(() => expect(1).to.equal(2))
+      .catch(e => expect(e.name)
+        .to.be.equal('ServerlessError'));
+  });
+
+  it('should throw an error if region does not exist', () => {
+    serverless.config.servicePath = true;
+    serverless.service.environment = {
+      vars: {},
+      stages: {
+        dev: {
+          vars: {},
+          regions: {},
+        },
+      },
+    };
+
+    awsRemove.options.region = 'whatever';
+
+    // if we go inside "then", then no error was thrown as expected
+    // so make assertion fail intentionally to let us know something is wrong
+    return BbPromise.resolve()
+      .then(() => awsRemove.validate())
+      .then(() => expect(1).to.equal(2))
+      .catch(e => expect(e.name)
+        .to.be.equal('ServerlessError'));
   });
 });
diff --git a/lib/plugins/aws/tests/index.js b/lib/plugins/aws/tests/index.js
index faea84ac60c..1c14eaf40b8 100644
--- a/lib/plugins/aws/tests/index.js
+++ b/lib/plugins/aws/tests/index.js
@@ -45,7 +45,6 @@ describe('AWS SDK', () => {
 
   describe('#request()', () => {
     it('should call correct aws method', () => {
-
       // mocking S3 for testing
       class FakeS3 {
         constructor(credentials) {
@@ -81,7 +80,6 @@ describe('AWS SDK', () => {
         expect(data.called).to.equal(true);
       });
     });
-
   });
 
   describe('#getCredentials()', () => {
diff --git a/lib/plugins/create/tests/create.js b/lib/plugins/create/tests/create.js
index d5b7dff97bb..cc240ccf227 100644
--- a/lib/plugins/create/tests/create.js
+++ b/lib/plugins/create/tests/create.js
@@ -23,31 +23,6 @@ describe('Create', () => {
     it('should have hooks', () => expect(create.hooks).to.be.not.empty);
   });
 
-  describe('#prompt()', () => {
-    beforeEach(() => {
-      create.options.name = 'valid-service-name';
-      create.options.provider = 'aws';
-    });
-
-    it('should NOT generate greeting if not interactive', () => {
-      const greetingStub = sinon.stub(create.serverless.cli, 'asciiGreeting');
-      return create.prompt().then(() => {
-        expect(greetingStub.notCalled).to.be.equal(true);
-        create.serverless.cli.asciiGreeting.restore();
-      });
-    });
-
-    it('should generate greeting if interactive', () => {
-      create.serverless.config.interactive = true;
-      const greetingStub = sinon.stub(create.serverless.cli, 'asciiGreeting');
-      return create.prompt().then(() => {
-        expect(greetingStub.calledOnce).to.be.equal(true);
-        create.serverless.cli.asciiGreeting.restore();
-        create.serverless.config.interactive = false;
-      });
-    });
-  });
-
   describe('#validate()', () => {
     it('it should resolve if name is valid and all required options provided', () => {
       create.options.name = 'valid-service-name';
diff --git a/tests/classes/CLI.js b/tests/classes/CLI.js
index 6125bdcd048..0571b1740f7 100644
--- a/tests/classes/CLI.js
+++ b/tests/classes/CLI.js
@@ -11,7 +11,6 @@ const Serverless = require('../../lib/Serverless');
 describe('CLI', () => {
   let cli;
   let serverless;
-  const interactive = false;
 
   beforeEach(() => {
     serverless = new Serverless({});
@@ -19,27 +18,22 @@ describe('CLI', () => {
 
   describe('#construtor()', () => {
     it('should set the serverless instance', () => {
-      cli = new CLI(serverless, interactive);
+      cli = new CLI(serverless);
       expect(cli.serverless).to.deep.equal(serverless);
     });
 
-    it('should set the isInteractive option', () => {
-      cli = new CLI(serverless, interactive);
-      expect(cli.interactive).to.equal(interactive);
-    });
-
     it('should set an empty loadedPlugins array', () => {
-      cli = new CLI(serverless, interactive);
+      cli = new CLI(serverless);
       expect(cli.loadedPlugins.length).to.equal(0);
     });
 
     it('should set an empty inputArray when none is provided', () => {
-      cli = new CLI(serverless, interactive);
+      cli = new CLI(serverless);
       expect(cli.inputArray.length).to.equal(0);
     });
 
     it('should set the inputObject when provided', () => {
-      cli = new CLI(serverless, interactive, ['foo', 'bar', '--baz', '-qux']);
+      cli = new CLI(serverless, ['foo', 'bar', '--baz', '-qux']);
 
       expect(cli.inputArray[0]).to.equal('foo');
       expect(cli.inputArray[1]).to.equal('bar');
@@ -55,7 +49,7 @@ describe('CLI', () => {
       const pluginMock = new PluginMock();
       const plugins = [pluginMock];
 
-      cli = new CLI(serverless, interactive);
+      cli = new CLI(serverless);
 
       cli.setLoadedPlugins(plugins);
 
@@ -65,7 +59,7 @@ describe('CLI', () => {
 
   describe('#displayHelp()', () => {
     it('should return true when the "help" parameter is given', () => {
-      cli = new CLI(serverless, interactive, ['help']);
+      cli = new CLI(serverless, ['help']);
       const processedInput = cli.processInput();
       const helpDisplayed = cli.displayHelp(processedInput);
 
@@ -73,15 +67,56 @@ describe('CLI', () => {
     });
 
     it('should return true when the "--help" parameter is given', () => {
-      cli = new CLI(serverless, interactive, ['--help']);
+      cli = new CLI(serverless, ['--help']);
+
+      class PluginMock {
+        constructor() {
+          this.commands = {
+            test: {
+              usage: 'test',
+              lifecycleEvents: [
+                'test',
+              ],
+              options: {
+                name: {
+                  usage: 'test',
+                },
+                provider: {
+                  usage: 'test',
+                },
+              },
+              commands: {
+                test: {
+                  usage: 'test',
+                  lifecycleEvents: [
+                    'test',
+                  ],
+                  options: {
+                    name: {
+                      usage: 'test',
+                    },
+                    provider: {
+                      usage: 'test',
+                    },
+                  },
+                },
+              },
+            },
+          };
+        }
+      }
+      const pluginMock = new PluginMock();
+      const plugins = [pluginMock];
+
+      cli.setLoadedPlugins(plugins);
       const processedInput = cli.processInput();
       const helpDisplayed = cli.displayHelp(processedInput);
 
       expect(helpDisplayed).to.equal(true);
     });
 
-    it('should return truewhen the "--h" parameter is given', () => {
-      cli = new CLI(serverless, interactive, ['--h']);
+    it('should return true when the "--h" parameter is given', () => {
+      cli = new CLI(serverless, ['--h']);
       const processedInput = cli.processInput();
       const helpDisplayed = cli.displayHelp(processedInput);
 
@@ -89,7 +124,7 @@ describe('CLI', () => {
     });
 
     it('should return true when the "version" parameter is given', () => {
-      cli = new CLI(serverless, interactive, ['version']);
+      cli = new CLI(serverless, ['version']);
       const processedInput = cli.processInput();
       const helpDisplayed = cli.displayHelp(processedInput);
 
@@ -97,7 +132,7 @@ describe('CLI', () => {
     });
 
     it('should return true when the "--version" parameter is given', () => {
-      cli = new CLI(serverless, interactive, ['--version']);
+      cli = new CLI(serverless, ['--version']);
       const processedInput = cli.processInput();
       const helpDisplayed = cli.displayHelp(processedInput);
 
@@ -105,7 +140,87 @@ describe('CLI', () => {
     });
 
     it('should return true when the "--v" parameter is given', () => {
-      cli = new CLI(serverless, interactive, ['--v']);
+      cli = new CLI(serverless, ['--v']);
+      const processedInput = cli.processInput();
+      const helpDisplayed = cli.displayHelp(processedInput);
+
+      expect(helpDisplayed).to.equal(true);
+    });
+
+    it('should return true when the "--h" parameter is given with a command', () => {
+      cli = new CLI(serverless, ['test', '--h']);
+      class PluginMock {
+        constructor() {
+          this.commands = {
+            test: {
+              usage: 'test',
+              lifecycleEvents: [
+                'test',
+              ],
+              options: {
+                name: {
+                  usage: 'test',
+                },
+                provider: {
+                  usage: 'test',
+                },
+              },
+            },
+          };
+        }
+      }
+      const pluginMock = new PluginMock();
+      const plugins = [pluginMock];
+
+      cli.setLoadedPlugins(plugins);
+      const processedInput = cli.processInput();
+      const helpDisplayed = cli.displayHelp(processedInput);
+
+      expect(helpDisplayed).to.equal(true);
+    });
+
+    it('should return true when the "--h" parameter is given with a deep command', () => {
+      cli = new CLI(serverless, ['test', 'test', '--h']);
+      class PluginMock {
+        constructor() {
+          this.commands = {
+            test: {
+              usage: 'test',
+              lifecycleEvents: [
+                'test',
+              ],
+              options: {
+                name: {
+                  usage: 'test',
+                },
+                provider: {
+                  usage: 'test',
+                },
+              },
+              commands: {
+                test: {
+                  usage: 'test',
+                  lifecycleEvents: [
+                    'test',
+                  ],
+                  options: {
+                    name: {
+                      usage: 'test',
+                    },
+                    provider: {
+                      usage: 'test',
+                    },
+                  },
+                },
+              },
+            },
+          };
+        }
+      }
+      const pluginMock = new PluginMock();
+      const plugins = [pluginMock];
+
+      cli.setLoadedPlugins(plugins);
       const processedInput = cli.processInput();
       const helpDisplayed = cli.displayHelp(processedInput);
 
@@ -115,7 +230,7 @@ describe('CLI', () => {
 
   describe('#processInput()', () => {
     it('should only return the commands when only commands are given', () => {
-      cli = new CLI(serverless, interactive, ['deploy', 'functions']);
+      cli = new CLI(serverless, ['deploy', 'functions']);
       const inputToBeProcessed = cli.processInput();
 
       const expectedObject = { commands: ['deploy', 'functions'], options: {} };
@@ -124,7 +239,7 @@ describe('CLI', () => {
     });
 
     it('should only return the options when only options are given', () => {
-      cli = new CLI(serverless, interactive, ['-f', 'function1', '-r', 'resource1']);
+      cli = new CLI(serverless, ['-f', 'function1', '-r', 'resource1']);
       const inputToBeProcessed = cli.processInput();
 
       const expectedObject = { commands: [], options: { f: 'function1', r: 'resource1' } };
@@ -133,7 +248,7 @@ describe('CLI', () => {
     });
 
     it('should return commands and options when both are given', () => {
-      cli = new CLI(serverless, interactive, ['deploy', 'functions', '-f', 'function1']);
+      cli = new CLI(serverless, ['deploy', 'functions', '-f', 'function1']);
       const inputToBeProcessed = cli.processInput();
 
       const expectedObject = { commands: ['deploy', 'functions'], options: { f: 'function1' } };
diff --git a/tests/classes/PluginManager.js b/tests/classes/PluginManager.js
index 61e110dca36..a565c4fd1f5 100644
--- a/tests/classes/PluginManager.js
+++ b/tests/classes/PluginManager.js
@@ -306,6 +306,17 @@ describe('PluginManager', () => {
     });
   });
 
+  describe('#validateCommands()', () => {
+    it('should throw an error if a first level command is not found in the commands object', () => {
+      pluginManager.commands = {
+        foo: {},
+      };
+      const commandsArray = ['bar'];
+
+      expect(() => { pluginManager.validateCommands(commandsArray); }).to.throw(Error);
+    });
+  });
+
   describe('#validateOptions()', () => {
     it('should throw an error if a required option is not set in a plain commands object', () => {
       pluginManager.commands = {
diff --git a/tests/integration_test.js b/tests/integration_test.js
index 1734b80684a..92aa7d73a1e 100644
--- a/tests/integration_test.js
+++ b/tests/integration_test.js
@@ -49,7 +49,7 @@ describe('Service Lifecyle Integration Test', () => {
 
   it('should invoke function from aws', function () {
     this.timeout(0);
-    const invoked = execSync(`${serverlessExec} invoke --function hello`);
+    const invoked = execSync(`${serverlessExec} invoke --function hello --noGreeting true`);
     const result = JSON.parse(new Buffer(invoked, 'base64').toString());
     expect(result.message).to.be.equal('Go Serverless v1.0! Your function executed successfully!');
   });
@@ -71,7 +71,7 @@ describe('Service Lifecyle Integration Test', () => {
 
   it('should invoke updated function from aws', function () {
     this.timeout(0);
-    const invoked = execSync(`${serverlessExec} invoke --function hello`);
+    const invoked = execSync(`${serverlessExec} invoke --function hello --noGreeting true`);
     const result = JSON.parse(new Buffer(invoked, 'base64').toString());
     expect(result.message).to.be.equal('Service Update Succeeded');
   });