Repository: serverless/serverless (JavaScript) — Bug Fix

Issue:
Commands Help prints entrypoint subcommands too
<!--
1. If you have a question and not a bug/feature request please ask it at http://forum.serverless.com
2. Please check if an issue already exists so there are no duplicates
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Bug Report

## Description

For bug reports:
* What went wrong?

Ran `sls package -h` and got:
```
$ sls package -h
Plugin: Package
package ....................... Packages a Serverless service
package function .............. undefined
    --stage / -s ....................... Stage of the service
    --region / -r ...................... Region of the service
    --package / -p ..................... Output path for the package
```

* What did you expect should have happened?

`package function ............. undefined` shouldn't be printed, since it's an entrypoint subcommand, refer:[package.js](https://github.com/serverless/serverless/blob/master/lib/plugins/package/package.js#L53).

## Additional Data

* ***Serverless Framework Version you're using***: 1.16.0
* ***Operating System***: macOS


Discussion:

Resolving patch:
diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js
index 05698c36da2..28f61d851a2 100644
--- a/lib/classes/CLI.js
+++ b/lib/classes/CLI.js
@@ -146,9 +146,27 @@ class CLI {
   }
 
   generateCommandsHelp(commandsArray) {
-    const command = this.serverless.pluginManager.getCommand(commandsArray);
     const commandName = commandsArray.join(' ');
 
+    // Get all the commands using getCommands() with filtered entrypoint
+    // commands and reduce to the required command.
+    const allCommands = this.serverless.pluginManager.getCommands();
+    const command = _.reduce(commandsArray, (currentCmd, cmd) => {
+      if (currentCmd.commands && cmd in currentCmd.commands) {
+        return currentCmd.commands[cmd];
+      }
+      return null;
+    }, { commands: allCommands });
+
+    // Throw error if command not found.
+    if (!command) {
+      const errorMessage = [
+        `Serverless command "${commandName}" not found.`,
+        ' Run "serverless help" for a list of all available commands.',
+      ].join('');
+      throw new this.serverless.classes.Error(errorMessage);
+    }
+
     // print the name of the plugin
     this.consoleLog(chalk.yellow.underline(`Plugin: ${command.pluginName}`));
 


Test patch:
diff --git a/lib/classes/CLI.test.js b/lib/classes/CLI.test.js
index 3a1d6b326ab..fd7daceeda5 100644
--- a/lib/classes/CLI.test.js
+++ b/lib/classes/CLI.test.js
@@ -5,6 +5,7 @@
  */
 
 const expect = require('chai').expect;
+const sinon = require('sinon');
 const CLI = require('../../lib/classes/CLI');
 const os = require('os');
 const fse = require('fs-extra');
@@ -273,6 +274,79 @@ describe('CLI', () => {
     });
   });
 
+  describe('#generateCommandsHelp()', () => {
+    let getCommandsStub;
+    let consoleLogStub;
+    let displayCommandUsageStub;
+    let displayCommandOptionsStub;
+
+    const commands = {
+      package: {
+        usage: 'Packages a Serverless service',
+        lifecycleEvents: ['cleanup', 'initialize'],
+        options: {},
+        key: 'package',
+        pluginName: 'Package',
+      },
+      deploy: {
+        usage: 'Deploy a Serverless service',
+        lifecycleEvents: ['cleanup', 'initialize'],
+        options: {},
+        key: 'deploy',
+        pluginName: 'Deploy',
+        commands: {},
+      },
+    };
+
+    beforeEach(() => {
+      cli = new CLI(serverless);
+      getCommandsStub = sinon.stub(cli.serverless.pluginManager, 'getCommands')
+        .returns(commands);
+      consoleLogStub = sinon.stub(cli, 'consoleLog').returns();
+      displayCommandUsageStub = sinon.stub(cli, 'displayCommandUsage').returns();
+      displayCommandOptionsStub = sinon.stub(cli, 'displayCommandOptions').returns();
+    });
+
+    afterEach(() => {
+      cli.serverless.pluginManager.getCommands.restore();
+      cli.consoleLog.restore();
+      cli.displayCommandUsage.restore();
+      cli.displayCommandOptions.restore();
+    });
+
+    it('should gather and generate the commands help info if the command can be found', () => {
+      const commandsArray = ['package'];
+      cli.inputArray = commandsArray;
+
+      cli.generateCommandsHelp(commandsArray);
+
+      expect(getCommandsStub.calledOnce).to.equal(true);
+      expect(consoleLogStub.called).to.equal(true);
+      expect(displayCommandUsageStub.calledOnce).to.equal(true);
+      expect(displayCommandUsageStub.calledWithExactly(
+        commands.package,
+        'package'
+      )).to.equal(true);
+      expect(displayCommandOptionsStub.calledOnce).to.equal(true);
+      expect(displayCommandOptionsStub.calledWithExactly(
+        commands.package
+      )).to.equal(true);
+    });
+
+    it('should throw an error if the command could not be found', () => {
+      const commandsArray = ['invalid-command'];
+
+      cli.inputArray = commandsArray;
+
+      expect(() => { cli.generateCommandsHelp(commandsArray); })
+        .to.throw(Error, 'not found');
+      expect(getCommandsStub.calledOnce).to.equal(true);
+      expect(consoleLogStub.called).to.equal(false);
+      expect(displayCommandUsageStub.calledOnce).to.equal(false);
+      expect(displayCommandOptionsStub.calledOnce).to.equal(false);
+    });
+  });
+
   describe('#processInput()', () => {
     it('should only return the commands when only commands are given', () => {
       cli = new CLI(serverless, ['deploy', 'functions']);