Repository: serverless-heaven/serverless-webpack (js)

Issue:
Add ability to exclude files from handler lookup
<!--
1. Please check if an issue already exists so there are no duplicates
2. Fill out the whole template so we have a good overview on the issue
3. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
4. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Feature Proposal

## Description
Please add the ability to exclude certain files from the handler lookup.  This is to get around the extraneous (and possibly error-prone) `WARNING: More than one matching handlers found for 'handler'. Using 'handler.js'` message when there are unit test files in the same directory.

Possible config example:
```
custom:
  webpack:
    excludeFiles: src/**/*.test.[t|j]s
```

Similar or dependent issue(s):
* #405 

Pull request description:
Adds the ability to specify an excludeFiles glob
<!--
1. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the PR
2. Please follow the template, otherwise we'll have to ask you to update it and it will take longer until your PR is merged
-->

## What did you implement:

Closes #433

<!--
Briefly describe the feature if no issue exists for this PR. If possible only
submit PRs for existing issues. If the PR is trivial (like doc changes or simple
code fixes) it can be submitted without a related issue, but as soon as it adds
or changes functionality, a related issue should be present.
-->

## How did you implement it:

Fairly trivial, just used the ignore option in glob to exclude files matching the glob.

## How can we verify it:

1. Setup a serverless project with an index.test.js and an index.js that handle a route
2. Start it up and see that you get a warning about duplicate files matching for index
3. Now pull in this PR
4. add the following to your serverless.yml file in the custom > webpack section
`excludeFiles: **/*.test.js`
5. restart serverless
6. see that the warnings are now gone!

## Todos:

- [x] Write tests
- [x] Write documentation
- [x] Fix linting errors
- [x] 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/README.md b/README.md
index f616a53..ad26152 100644
--- a/README.md
+++ b/README.md
@@ -62,6 +62,7 @@ custom:
     webpackConfig: 'webpack.config.js'   # Name of webpack configuration file
     includeModules: false   # Node modules configuration for packaging
     packager: 'npm'   # Packager that will be used to package your external modules
+    excludeFiles: src/**/*.test.js # Provide a glob for files to ignore
 ```
 
 ### Webpack configuration file
@@ -432,6 +433,25 @@ from a local folder (e.g. `"mymodule": "file:../../myOtherProject/mymodule"`).
 With that you can do test deployments from the local machine with different
 module versions or modules before they are published officially.
 
+#### Exclude Files with similar names
+
+If you have a project structure that uses something like `index.js` and a
+co-located `index.test.js` then you have likely seen an error like:
+`WARNING: More than one matching handlers found for index. Using index.js`
+
+This config option allows you to exlcude files that match a glob from function
+resolution. Just add: `excludeFiles: **/*.test.js` (with whatever glob you want
+to exclude).
+
+```yaml
+# serverless.yml
+custom:
+  webpack:
+    excludeFiles: **/*.test.js
+```
+
+This is also useful for projects that use TypeScript.
+
 #### Keep output directory after packaging
 
 You can keep the output directory (defaults to `.webpack`) from being removed
diff --git a/lib/Configuration.js b/lib/Configuration.js
index 75d81cd..705385c 100644
--- a/lib/Configuration.js
+++ b/lib/Configuration.js
@@ -5,7 +5,7 @@
 
 const _ = require('lodash');
 
-/** 
+/**
  * Plugin defaults
  */
 const DefaultConfig = {
@@ -51,6 +51,10 @@ class Configuration {
     return this._config.includeModules;
   }
 
+  get excludeFiles() {
+    return this._config.excludeFiles;
+  }
+
   get packager() {
     return this._config.packager;
   }
diff --git a/lib/validate.js b/lib/validate.js
index ae0f855..61103ff 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -33,7 +33,8 @@ module.exports = {
     const getEntryExtension = fileName => {
       const files = glob.sync(`${fileName}.*`, {
         cwd: this.serverless.config.servicePath,
-        nodir: true
+        nodir: true,
+        ignore: this.configuration.excludeFiles ? this.configuration.excludeFiles : undefined
       });
 
       if (_.isEmpty(files)) {
@@ -117,7 +118,7 @@ module.exports = {
         return BbPromise.reject(err);
       }
     }
-    
+
     // Intermediate function to handle async webpack config
     const processConfig = _config => {
       this.webpackConfig = _config;
@@ -220,7 +221,7 @@ module.exports = {
 
       return BbPromise.resolve();
     };
-    
+
     // Webpack config can be a Promise, If it's a Promise wait for resolved config object.
     if (this.webpackConfig && _.isFunction(this.webpackConfig.then)) {
       return BbPromise.resolve(this.webpackConfig.then(config => processConfig(config)));


Test patch:
diff --git a/tests/validate.test.js b/tests/validate.test.js
index f6bd6d3..433f501 100644
--- a/tests/validate.test.js
+++ b/tests/validate.test.js
@@ -745,6 +745,40 @@ describe('validate', () => {
         });
       });
 
+      it('should call glob with ignore parameter if there is an excludeFiles config', () => {
+        const testOutPath = 'test';
+        const testFunction = 'func1';
+        const testConfig = {
+          entry: 'test',
+          context: 'testcontext',
+          output: {
+            path: testOutPath,
+          },
+        };
+        _.set(module.serverless.service, 'custom.webpack.config', testConfig);
+        _.set(module.serverless.service, 'custom.webpack.excludeFiles', '**/*.ts');
+        module.serverless.service.functions = testFunctionsConfig;
+        module.options.function = testFunction;
+        globSyncStub.returns(['module1.js']);
+        return expect(module.validate()).to.be.fulfilled
+        .then(() => {
+          const lib = require('../lib/index');
+          const expectedLibEntries = {
+            'module1': './module1.js'
+          };
+
+          expect(lib.entries).to.deep.equal(expectedLibEntries);
+
+          expect(globSyncStub).to.have.been.calledOnceWith('module1.*', {
+            ignore: '**/*.ts',
+            cwd: null,
+            nodir: true
+          });
+          expect(serverless.cli.log).not.to.have.been.called;
+          return null;
+        });
+      });
+
       it('should throw an exception if no handler is found', () => {
         const testOutPath = 'test';
         const testFunction = 'func1';