diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index fdcabd80b..3c61bf07e 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -15,7 +15,8 @@ const getCommandSuggestion = require('../utils/getCommandSuggestion'); const requireServicePlugin = (servicePath, pluginPath, localPluginsPath) => { if (localPluginsPath && !pluginPath.startsWith('./')) { // TODO (BREAKING): Consider removing support for localPluginsPath with next major - const absoluteLocalPluginPath = path.join(localPluginsPath, pluginPath); + const absoluteLocalPluginPath = path.resolve(localPluginsPath, pluginPath); + try { return require(absoluteLocalPluginPath); } catch (error) { diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 941a9b6e5..a676d69f6 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -2014,6 +2014,22 @@ describe('PluginManager', () => { ); }); + it('should load plugins from custom folder outside of serviceDir', () => { + serviceDir = path.join(tmpDir, 'serverless-plugins-custom'); + const localPluginDir = path.join(serviceDir, 'local-plugin'); + installPlugin(localPluginDir, SynchronousPluginMock); + + pluginManager.loadAllPlugins({ + localPath: '../serverless-plugins-custom', + modules: ['local-plugin'], + }); + // Had to use contructor.name because the class will be loaded via + // require and the reference will not match with SynchronousPluginMock + expect(pluginManager.plugins).to.satisfy(plugins => + plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock') + ); + }); + afterEach(() => { // eslint-disable-line prefer-arrow-callback process.chdir(cwd);