Repository: serverless/serverless (JavaScript)
Date: 2019-04-22 07:05:26+00:00
Task type: Bug Fix

Issue:
`sls plugin install` failing
<!--
1. If you have a question and not a bug report please ask first at http://forum.serverless.com
2. Please check if an issue already exists. This bug may have already been documented
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

* What went wrong?
I wanted to install a serverless plugin through the CLI ` $ sls plugin install serverless-express`  but was met with an error `  Serverless command "plugin install serverless-express" not found. Did you mean "uninstall"? Run "serverless help" for a list of all available commands.`
* What did you expect should have happened?
I expected based on the following that I could use the `sls` command line to install a plugin. I normally just install by adding it to my `serverless.yml` and using, but I gave this a try for the first time and it wasn't as I expected
```bash
tyler:~/environment/sls-starters/sls-express (master) $ sls help | grep plugin
* Pass "--verbose" to this command to get in-depth plugin info
install ....................... Install a Serverless service from GitHub or a plugin from the Serverless registry
plugin ........................ Plugin management for Serverless
plugin install ................ Install and add a plugin to your service
plugin uninstall .............. Uninstall and remove a plugin from your service
plugin list ................... Lists all available plugins
plugin search ................. Search for plugins
tyler:~/environment/sls-starters/sls-express (master) $ sls plugin list | grep serverless-express
serverless-express - Making express app development compatible with serverless framework.
tyler:~/environment/sls-starters/sls-express (master) $ sls plugin install serverless-express

  Serverless Error ---------------------------------------

  Serverless command "plugin install serverless-express" not found. Did you mean "uninstall"? Run "serverless help" for a list of all available commands.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information ---------------------------
     OS:                     linux
     Node Version:           8.10.0
     Serverless Version:     1.41.0
 
tyler:~/environment/sls-starters/sls-express (master) $ sls plugin install help                                                                                            
 
  Serverless Error ---------------------------------------
 
  Serverless command "plugin install help" not found. Did you mean "uninstall"? Run "serverless help" for a list of all available commands.
 
  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information ---------------------------
     OS:                     linux
     Node Version:           8.10.0
     Serverless Version:     1.41.0
```
 
* What was the config you used?
Basic starter from `$ serverless create --template aws-nodejs --path my-service` no custom config. `serverless.yml` here 
```yml
service: sls-express 
provider:
  name: aws
  runtime: nodejs8.10
functions:
  hello:
    handler: handler.hello
```

* What stacktrace or error message from your provider did you see?
```bash
  Serverless Error ---------------------------------------

  Serverless command "plugin install serverless-express" not found. Did you mean "uninstall"? Run "serverless help" for a list of all available commands.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information ---------------------------
     OS:                     linux
     Node Version:           8.10.0
     Serverless Version:     1.41.0
 
```
Similar or dependent issues:
* #4258

## Additional Data

* ***Serverless Framework Version you're using***:
1.41.0
* ***Operating System***:
Amazon Linux inside an EC2 using Cloud9
* ***Stack Trace***:
N/A
* ***Provider Error messages***:
```bash

  Serverless Error ---------------------------------------

  Serverless command "plugin install serverless-express" not found. Did you mean "uninstall"? Run "serverless help" for a list of all available commands.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information ---------------------------
     OS:                     linux
     Node Version:           8.10.0
     Serverless Version:     1.41.0

```


Issue discussion:
Ahh I see what I did wrong. I searched for plugins with `$ sls plugin list | grep serverless-express` I didn't use `$ sls plugin search --query express`. 

The `plugin search` command tells you that you should 
```
To install a plugin run 'serverless plugin install --name plugin-name-here'

It will be automatically downloaded and added to your package.json and serverless.yml file
```

When I did `$ sls plugin install --name serverless-express` it worked like a charm. 
I'll be closing this issue and opening up a feature request for the `$ sls plugin install` utility

Solution patch:
diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js
index ebe0d2e45be..433e3dac735 100644
--- a/lib/classes/PluginManager.js
+++ b/lib/classes/PluginManager.js
@@ -359,6 +359,20 @@ class PluginManager {
         && (isNotEntrypoint || allowEntryPoints)) {
         return current.commands[name];
       }
+      // if user is using a top level command properly, but sub commands are not
+      if (this.serverless.cli.loadedCommands[commandOrAlias[0]]) {
+        const errorMessage = [`"${name}" is not a valid sub command. Run "serverless `];
+        for (let i = 0; commandOrAlias[i] !== name; i++) {
+          errorMessage.push(`${commandOrAlias[i]}`);
+          if (commandOrAlias[i + 1] !== name) {
+            errorMessage.push(' ');
+          }
+        }
+        errorMessage.push('" to see a more helpful error message for this command.');
+        throw new this.serverless.classes.Error(errorMessage.join(''));
+      }
+
+      // top level command isn't valid. give a suggestion
       const commandName = commandOrAlias.slice(0, index + 1).join(' ');
       const suggestedCommand = getCommandSuggestion(commandName,
         this.serverless.cli.loadedCommands);


Test patch:
diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js
index 595c2caf6c2..b1a9a14c5ce 100644
--- a/lib/classes/PluginManager.test.js
+++ b/lib/classes/PluginManager.test.js
@@ -1586,6 +1586,107 @@ describe('PluginManager', () => {
     });
   });
 
+  describe('#getCommand()', () => {
+    beforeEach(() => {
+      pluginManager.addPlugin(SynchronousPluginMock);
+      pluginManager.serverless.cli.loadedCommands = {
+        create: {
+          usage: 'Create new Serverless service',
+          lifecycleEvents: [
+            'create',
+          ],
+          options: {
+            template: {
+              usage: 'Template for the service. Available templates: ", "aws-nodejs", "..."',
+              shortcut: 't',
+            },
+          },
+          key: 'create',
+          pluginName: 'Create',
+        },
+        deploy: {
+          usage: 'Deploy a Serverless service',
+          configDependent: true,
+          lifecycleEvents: [
+            'cleanup',
+            'initialize',
+          ],
+          options: {
+            conceal: {
+              usage: 'Hide secrets from the output (e.g. API Gateway key values)',
+            },
+            stage: {
+              usage: 'Stage of the service',
+              shortcut: 's',
+            },
+          },
+          key: 'deploy',
+          pluginName: 'Deploy',
+          commands: {
+            function: {
+              usage: 'Deploy a single function from the service',
+              lifecycleEvents: [
+                'initialize',
+                'packageFunction',
+                'deploy',
+              ],
+              options: {
+                function: {
+                  usage: 'Name of the function',
+                  shortcut: 'f',
+                  required: true,
+                },
+              },
+              key: 'deploy:function',
+              pluginName: 'Deploy',
+            },
+            list: {
+              usage: 'List deployed version of your Serverless Service',
+              lifecycleEvents: [
+                'log',
+              ],
+              key: 'deploy:list',
+              pluginName: 'Deploy',
+              commands: {
+                functions: {
+                  usage: 'List all the deployed functions and their versions',
+                  lifecycleEvents: [
+                    'log',
+                  ],
+                  key: 'deploy:list:functions',
+                  pluginName: 'Deploy',
+                },
+              },
+            },
+          },
+        },
+      };
+    });
+    it('should give a suggestion for an unknown command', (done) => {
+      try {
+        pluginManager.getCommand(['creet']);
+        done('Test failed. Expected an error to be thrown');
+      } catch (error) {
+        expect(error.name).to.eql('ServerlessError');
+        expect(error.message).to.eql('Serverless command "creet" not found. ' +
+          'Did you mean "create"? Run "serverless help" for a list of all available commands.');
+        done();
+      }
+    });
+
+    it('should not give a suggestion for valid top level command', (done) => {
+      try {
+        pluginManager.getCommand(['deploy', 'function-misspelled']);
+        done('Test failed. Expected an error to be thrown');
+      } catch (error) {
+        expect(error.name).to.eql('ServerlessError');
+        expect(error.message).to.eql('"function-misspelled" is not a valid sub command. '
+          + 'Run "serverless deploy" to see a more helpful error message for this command.');
+        done();
+      }
+    });
+  });
+
   describe('#spawn()', () => {
     it('should throw an error when the given command is not available', () => {
       pluginManager.addPlugin(EntrypointPluginMock);