Repository: serverless/serverless (JavaScript)
Date: 2017-12-11 20:18:35+00:00
Task type: Bug Fix

Issue:
`sls plugin search` fails if a plugin is uninstalled
# This is a Bug Report

## Description

In a given directory that has a plugin listed in the `serverless.yml` which is not installed, a call to `sls plugin search --query webpack` fails with the message:

`Serverless plugin "serverless-spotinst-functions" not found. Make sure it's installed and listed in the "plugins" section of your serverless config file.`

I would expect this plugin search to complete even if I have an uninstalled plugin locally.

## Additional Data

* ***Serverless Framework Version you're using***: 1.22
* ***Node.js Version you're using***: 8.5.0
* ***Operating System***: macos




Issue discussion:
Thanks for reporting @ryanmurakami 👍

Nice catch! /cc @horike37
All plugin commands(list, search, install, uninstall) might want not to catch the error in the above situation. Otherwise, users who set plugins manually within the yaml file would face the same error.
What do you think?
@horike37 I agree. Just to add context, this happened with a new service from a template that had a plugin hardcoded in the `serverless.yml`. So, this could happen to someone who didn't even know there was an uninstalled plugin.
Good idea @horike37 and nice catch with the new service created from a template @ryanmurakami (haven't thought about that use case...) 👍

Just wondering if all commands should skip the check if you're in a service. I'd say that `install` and `uninstall` should still check if the command was run in a service directory (what should happen if I run them globally 🤔?).

But `list` and `search` should should definitely work globally 👍
@pmuens I like the idea of `install` and `uninstall` checking the location of execution, just not the state of other plugins. I think the plugin commands shouldn't fail based on the state of another plugin.
### Conclusion for the implementation 
- `install` and `uninstall` should check the location of execution (if you are in services)
- All plugin commands shouldn't catch the error related to the state of another plugin.
Is there anything new on this issue?
Just ran into a related issue.  I added some plugins I know I need to install while working on my project's serverless.yml.  I had to comment the section out in order to run `sls plugin install` to get the plugins I needed and then uncomment.  I'm a little unclear on what people think the default behavior of `install` should be.  I think it should skip the plugin check so people can install plugins easily.  Or, is there another command that I can run that'll look at what should be installed and install the plugins?

Solution patch:
diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js
index 7cf4f75da27..b2714300fc1 100644
--- a/lib/classes/PluginManager.js
+++ b/lib/classes/PluginManager.js
@@ -90,6 +90,9 @@ class PluginManager {
 
         this.addPlugin(Plugin);
       } catch (error) {
+        if (this.cliCommands[0] === 'plugin') {
+          return;
+        }
         let errorMessage;
         if (error && error.code === 'MODULE_NOT_FOUND' && error.message.endsWith(`'${plugin}'`)) {
           // Plugin not installed
@@ -135,7 +138,6 @@ class PluginManager {
     if (this.serverless && this.serverless.config && this.serverless.config.servicePath) {
       module.paths.unshift(path.join(this.serverless.config.servicePath, '.serverless_plugins'));
     }
-
     this.loadPlugins(servicePlugins);
   }
 


Test patch:
diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js
index 95864844cef..437a515ffd0 100644
--- a/lib/classes/PluginManager.test.js
+++ b/lib/classes/PluginManager.test.js
@@ -665,6 +665,16 @@ describe('PluginManager', () => {
       });
     });
 
+    it('should not throw error when running the plugin commands and given plugins does not exist',
+    () => {
+      const servicePlugins = ['ServicePluginMock3'];
+      const cliCommandsMock = ['plugin'];
+      pluginManager.setCliCommands(cliCommandsMock);
+
+      expect(() => pluginManager.loadPlugins(servicePlugins))
+        .to.not.throw(serverless.classes.Error);
+    });
+
     afterEach(() => {
       mockRequire.stop('ServicePluginMock1');
     });