Repository: serverless/serverless (JavaScript)
Date: 2017-08-15 09:42:50+00:00
Task type: Bug Fix

Issue:
Development of Local Plugins No Longer Works
# This is a Bug Report 

Can no longer test a serverless plugin. 



## Description

Put the plugin test-plugin in folder .serverless_plugins/test-plugin

and included it in plugins section of serverless.yml

Yet it is not recognized by serverless, says 

Serverless plugin "test-plugin" not found. Make sure it's installed and listed in the "plugins" section of your serverless config file.

This was also tested with this example repo, [sls-plugins-example](https://github.com/lithin/sls-plugins-example) by Anna Doubkova.

Where that example, just puts a file plugin.js inside the serverless_plugins folder

## Additional Data

MacOSX
Node 8.2.1
NPM 5.3.0
Serverless Version:     1.19.0


Issue discussion:
Hey @kornatzky ,
I had the same issue yesterday.
I guess that you have some error in your test-plugin. npm install maybe?

If the plugin throws an exception when "required", the framework will print the "not found" error.
@pmuens as I said in the previous comment,
If the plugin throws an exception when "required", the framework will print the "not found" error.

You should distinguish between really not found to an internal error of the plugin startup.
Fully agree. It is annoying for plugin development to have no accurate callstack on crashes.
The reason is, that Serverless' `pluginManager` encloses the plugin require with a try/catch and does not forward the original exception but creates a new one. That hides the original callstack.

I also agree that for the user using Serverless a plugin callstack would only pollute the output and be of no value for the user himself.

My suggestion here would be, that we rethrow the original exception/rejection **BUT ONLY**, if `SLS_DEBUG` is set. That has the advantage, that plugin developers can debug their plugins and find the reason of the crash, but without giving the user useless information. This should be an easy-pick and can be implemented without adding tech debt and risk.

@pmuens What do you think?

Solution patch:
diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js
index d32efc31c79..7001cc9c77a 100644
--- a/lib/classes/PluginManager.js
+++ b/lib/classes/PluginManager.js
@@ -71,6 +71,11 @@ class PluginManager {
 
         this.addPlugin(Plugin);
       } catch (error) {
+        // Rethrow the original error in case we're in debug mode.
+        if (process.env.SLS_DEBUG) {
+          throw error;
+        }
+
         const errorMessage = [
           `Serverless plugin "${plugin}" not found.`,
           ' Make sure it\'s installed and listed in the "plugins" section',


Test patch:
diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js
index 5211df188e7..bab03ad6698 100644
--- a/lib/classes/PluginManager.test.js
+++ b/lib/classes/PluginManager.test.js
@@ -6,6 +6,7 @@ const Serverless = require('../../lib/Serverless');
 const CLI = require('../../lib/classes/CLI');
 const Create = require('../../lib/plugins/create/create');
 
+const _ = require('lodash');
 const path = require('path');
 const fs = require('fs');
 const fse = require('fs-extra');
@@ -26,6 +27,12 @@ describe('PluginManager', () => {
 
   class ServicePluginMock2 {}
 
+  class BrokenPluginMock {
+    constructor() {
+      throw new Error('Broken plugin');
+    }
+  }
+
   class Provider1PluginMock {
     constructor() {
       this.provider = 'provider1';
@@ -528,6 +535,7 @@ describe('PluginManager', () => {
   describe('#loadPlugins()', () => {
     beforeEach(() => {
       mockRequire('ServicePluginMock1', ServicePluginMock1);
+      mockRequire('BrokenPluginMock', BrokenPluginMock);
     });
 
     it('should throw an error when trying to load unknown plugin', () => {
@@ -549,6 +557,26 @@ describe('PluginManager', () => {
       expect(consoleLogStub.calledOnce).to.equal(true);
     });
 
+    it('should throw an error when trying to load a broken plugin (without SLS_DEBUG)', () => {
+      const servicePlugins = ['BrokenPluginMock'];
+
+      expect(() => pluginManager.loadPlugins(servicePlugins))
+        .to.throw(serverless.classes.Error);
+    });
+
+    it('should forward any errors when trying to load a broken plugin (with SLS_DEBUG)', () => {
+      const servicePlugins = ['BrokenPluginMock'];
+
+      return BbPromise.try(() => {
+        _.set(process.env, 'SLS_DEBUG', '*');
+        expect(() => pluginManager.loadPlugins(servicePlugins))
+          .to.throw(/Broken plugin/);
+      })
+      .finally(() => {
+        _.unset(process.env, 'SLS_DEBUG');
+      });
+    });
+
     afterEach(() => {
       mockRequire.stop('ServicePluginMock1');
     });