Repository: serverless-heaven/serverless-webpack
Pull request #876 (2021-06-10 05:38:02)

Fix packaging for non-node functions
## What did you implement:

Closes: --

Continuation of https://github.com/serverless-heaven/serverless-webpack/pull/808

This PR will make it so all non-node runtimes are ignored.

## How did you implement it:

Replaced instances of this.serverless.service.getAllFunctions with a new util function that filters and returns only node functions.

## How can we verify it:

Unit tests are added and updated:

Can be tested with any config that include other runtimes like python,java etc.
## Todos:

- [x] Write tests
- [x] Write documentation
- [x] Fix linting errors
- [ ] Make sure code coverage hasn't dropped
- [x] Provide verification config / commands / resources
- [x] Enable "Allow edits from maintainers" for this PR
- [x] Update the messages below

***Is this ready for review?:*** Yes
***Is it a breaking change?:*** NO



Patch:
diff --git a/lib/packageModules.js b/lib/packageModules.js
index 70d0397..b7b8775 100644
--- a/lib/packageModules.js
+++ b/lib/packageModules.js
@@ -7,6 +7,7 @@ const { nativeZip, nodeZip, hasNativeZip } = require('bestzip');
 const glob = require('glob');
 const semver = require('semver');
 const fs = require('fs');
+const { getAllNodeFunctions } = require('./utils');
 
 function setArtifactPath(funcName, func, artifactPath) {
   const version = this.serverless.getVersion();
@@ -150,7 +151,7 @@ module.exports = {
     this.serverless.cli.log('Copying existing artifacts...');
     // When invoked as a part of `deploy function`,
     // only function passed with `-f` flag should be processed.
-    const functionNames = this.options.function ? [this.options.function] : this.serverless.service.getAllFunctions();
+    const functionNames = this.options.function ? [this.options.function] : getAllNodeFunctions.call(this);
 
     // Copy artifacts to package location
     if (isIndividialPackaging.call(this)) {
diff --git a/lib/utils.js b/lib/utils.js
index 64b5e29..47c4449 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -108,6 +108,15 @@ function splitLines(str) {
   return _.split(str, /\r?\n/);
 }
 
+function getAllNodeFunctions() {
+  const functions = this.serverless.service.getAllFunctions();
+  return _.filter(functions, funcName => {
+    const func = this.serverless.service.getFunction(funcName);
+    const runtime = func.runtime || this.serverless.service.provider.runtime || 'nodejs';
+    return runtime.match(/node/);
+  });
+}
+
 module.exports = {
   guid,
   purgeCache,
@@ -115,5 +124,6 @@ module.exports = {
   SpawnError,
   spawnProcess,
   safeJsonParse,
-  splitLines
+  splitLines,
+  getAllNodeFunctions
 };
diff --git a/lib/validate.js b/lib/validate.js
index 588722b..32f8c89 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -7,6 +7,7 @@ const glob = require('glob');
 const lib = require('./index');
 const _ = require('lodash');
 const Configuration = require('./Configuration');
+const { getAllNodeFunctions } = require('./utils');
 
 /**
  * For automatic entry detection we sort the found files to solve ambiguities.
@@ -109,7 +110,7 @@ module.exports = {
     // Expose entries - must be done before requiring the webpack configuration
     const entries = {};
 
-    const functions = this.serverless.service.getAllFunctions();
+    const functions = getAllNodeFunctions.call(this);
     if (this.options.function) {
       const serverlessFunction = this.serverless.service.getFunction(this.options.function);
       const entry = getEntryForFunction.call(this, this.options.function, serverlessFunction);
@@ -222,23 +223,16 @@ module.exports = {
         }
 
         // Lookup associated Serverless functions
-        const allEntryFunctions = _.map(
-          _.filter(this.serverless.service.getAllFunctions(), funcName => {
-            const func = this.serverless.service.getFunction(funcName);
-            const runtime = func.runtime || this.serverless.service.provider.runtime || 'nodejs';
-            return runtime.match(/node/);
-          }),
-          funcName => {
-            const func = this.serverless.service.getFunction(funcName);
-            const handler = getHandlerFileAndFunctionName(func);
-            const handlerFile = path.relative('.', getHandlerFile(handler));
-            return {
-              handlerFile,
-              funcName,
-              func
-            };
-          }
-        );
+        const allEntryFunctions = _.map(getAllNodeFunctions.call(this), funcName => {
+          const func = this.serverless.service.getFunction(funcName);
+          const handler = getHandlerFileAndFunctionName(func);
+          const handlerFile = path.relative('.', getHandlerFile(handler));
+          return {
+            handlerFile,
+            funcName,
+            func
+          };
+        });
 
         this.entryFunctions = _.flatMap(entries, (value, key) => {
           const entry = path.relative('.', value);
diff --git a/lib/wpwatch.js b/lib/wpwatch.js
index 36c34ca..ecf6940 100644
--- a/lib/wpwatch.js
+++ b/lib/wpwatch.js
@@ -110,9 +110,9 @@ module.exports = {
           callback();
         } else if (canEmit && currentCompileWatch === null) {
           // eslint-disable-next-line promise/no-promise-in-callback
-          currentCompileWatch = BbPromise.resolve(
-            this.serverless.pluginManager.spawn('webpack:compile:watch')
-          ).then(() => this.serverless.cli.log('Watching for changes...'));
+          currentCompileWatch = BbPromise.resolve(this.serverless.pluginManager.spawn('webpack:compile:watch')).then(
+            () => this.serverless.cli.log('Watching for changes...')
+          );
         }
       });
     };


Test patch:
diff --git a/index.test.js b/index.test.js
index 6ccbf69..45a3499 100644
--- a/index.test.js
+++ b/index.test.js
@@ -145,7 +145,8 @@ describe('ServerlessWebpack', () => {
 
     before(() => {
       slsw = new ServerlessWebpack(serverless, rawOptions);
-      if(serverless.processedInput) { // serverless.processedInput does not exist in serverless@<2.0.0
+      if (serverless.processedInput) {
+        // serverless.processedInput does not exist in serverless@<2.0.0
         serverless.processedInput.options = processedOptions;
       }
       sandbox.stub(slsw, 'cleanup').returns(BbPromise.resolve());
@@ -477,7 +478,7 @@ describe('ServerlessWebpack', () => {
           test: () => {
             it('should override the raw options with the processed ones', () => {
               slsw.hooks.initialize();
-              if(serverless.processedInput) {
+              if (serverless.processedInput) {
                 expect(slsw.options).to.equal(processedOptions);
               } else {
                 // serverless.processedInput does not exist in serverless@<2.0.0
diff --git a/tests/compile.test.js b/tests/compile.test.js
index b9134cf..c929d16 100644
--- a/tests/compile.test.js
+++ b/tests/compile.test.js
@@ -256,7 +256,6 @@ describe('compile', () => {
     webpackMock.compilerMock.run.reset();
     webpackMock.compilerMock.run.yields(null, multiStats);
     return expect(module.compile()).to.be.fulfilled.then(() => {
-      console.log(JSON.stringify(module.compileStats.stats[0].externalModules));
       expect(module.compileStats.stats[0].externalModules).to.eql([
         { external: '@scoped/vendor', origin: undefined },
         { external: 'uuid', origin: undefined },
diff --git a/tests/packageModules.test.js b/tests/packageModules.test.js
index c7040aa..c5f10db 100644
--- a/tests/packageModules.test.js
+++ b/tests/packageModules.test.js
@@ -445,14 +445,21 @@ describe('packageModules', () => {
   });
 
   describe('copyExistingArtifacts()', () => {
-    const allFunctions = [ 'func1', 'func2' ];
+    const allFunctions = [ 'func1', 'func2', 'funcPython' ];
     const func1 = {
       handler: 'src/handler1',
       events: []
     };
     const func2 = {
       handler: 'src/handler2',
-      events: []
+      events: [],
+      runtime: 'node14'
+    };
+
+    const funcPython = {
+      handler: 'src/handlerPython',
+      events: [],
+      runtime: 'python3.7'
     };
 
     const entryFunctions = [
@@ -465,6 +472,11 @@ describe('packageModules', () => {
         handlerFile: 'src/handler2.js',
         funcName: 'func2',
         func: func2
+      },
+      {
+        handlerFile: 'src/handlerPython.js',
+        funcName: 'funcPython',
+        func: funcPython
       }
     ];
 
@@ -483,6 +495,7 @@ describe('packageModules', () => {
         getAllFunctionsStub.returns(allFunctions);
         getFunctionStub.withArgs('func1').returns(func1);
         getFunctionStub.withArgs('func2').returns(func2);
+        getFunctionStub.withArgs('funcPython').returns(funcPython);
       });
 
       it('copies the artifact', () => {
@@ -624,9 +637,10 @@ describe('packageModules', () => {
         getAllFunctionsStub.returns(allFunctions);
         getFunctionStub.withArgs('func1').returns(func1);
         getFunctionStub.withArgs('func2').returns(func2);
+        getFunctionStub.withArgs('funcPython').returns(funcPython);
       });
 
-      it('copies each artifact', () => {
+      it('copies each node artifact', () => {
         const expectedFunc1Destination = path.join('.serverless', 'func1.zip');
         const expectedFunc2Destination = path.join('.serverless', 'func2.zip');
 
diff --git a/tests/validate.test.js b/tests/validate.test.js
index 35ba2ec..7eca04b 100644
--- a/tests/validate.test.js
+++ b/tests/validate.test.js
@@ -596,7 +596,7 @@ describe('validate', () => {
         });
       });
 
-      it('should allow custom runtime', () => {
+      it('should ignore non-node runtimes', () => {
         const testOutPath = 'test';
         const testFunctionsConfig = {
           func1: {
@@ -683,17 +683,58 @@ describe('validate', () => {
           const lib = require('../lib/index');
           const expectedLibEntries = {
             module1: './module1.js',
-            module2: './module2.js',
             module4: './module4.js'
           };
 
           expect(lib.entries).to.deep.equal(expectedLibEntries);
-          expect(globSyncStub).to.have.callCount(3);
+          expect(globSyncStub).to.have.callCount(2);
           expect(serverless.cli.log).to.not.have.been.called;
           return null;
         });
       });
 
+      it('should throw error if container image is not well defined', () => {
+        const testOutPath = 'test';
+        const testFunctionsConfig = {
+          func1: {
+            artifact: 'artifact-func1.zip',
+            events: [
+              {
+                http: {
+                  method: 'POST',
+                  path: 'func1path'
+                }
+              },
+              {
+                nonhttp: 'non-http'
+              }
+            ],
+            image: {
+              name: 'custom-image',
+              command: []
+            }
+          }
+        };
+
+        const testConfig = {
+          entry: 'test',
+          context: 'testcontext',
+          output: {
+            path: testOutPath
+          },
+          getFunction: func => {
+            return testFunctionsConfig[func];
+          }
+        };
+
+        _.set(module.serverless.service, 'custom.webpack.config', testConfig);
+        module.serverless.service.functions = testFunctionsConfig;
+        globSyncStub.callsFake(filename => [_.replace(filename, '*', 'js')]);
+        expect(() => {
+          module.validate();
+        }).to.throw(/Either function.handler or function.image must be defined/);
+      });
+
       describe('google provider', () => {
         beforeEach(() => {
           _.set(module.serverless, 'service.provider.name', 'google');