diff --git a/README.md b/README.md index e19ccf8ab..fda744cd4 100644 --- a/README.md +++ b/README.md @@ -305,17 +305,17 @@ custom: #### Runtime dependencies If a runtime dependency is detected that is found in the `devDependencies` section and -so would not be packaged, the plugin will error until you explicitly exclude it (see `forceExclude` below) +so would not be packaged, the plugin will error until you explicitly exclude it (see `forceExclude` below) or move it to the `dependencies` section. #### AWS-SDK An exception for the runtime dependency error is the AWS-SDK. All projects using the AWS-SDK normally -have it listed in `devDependencies` because AWS provides it already in their Lambda environment. In this case +have it listed in `devDependencies` because AWS provides it already in their Lambda environment. In this case the aws-sdk is automatically excluded and only an informational message is printed (in `--verbose` mode). The main reason for the warning is, that silently ignoring anything contradicts the declarative nature -of Serverless' service definition. So the correct way to define the handling for the aws-sdk is, as +of Serverless' service definition. So the correct way to define the handling for the aws-sdk is, as you would do for all other excluded modules (see `forceExclude` below). ```yaml @@ -342,7 +342,7 @@ custom: ``` You should select the packager, that you use to develop your projects, because only -then locked versions will be handled correctly, i.e. the plugin uses the generated +then locked versions will be handled correctly, i.e. the plugin uses the generated (and usually committed) package lock file that is created by your favorite packager. Each packager might support specific options that can be set in the `packagerOptions` @@ -375,7 +375,7 @@ You can specify custom scripts that are executed after the installation of the f has been finished. These are standard packager scripts as they can be used in any `package.json`. Warning: The use cases for them are very rare and specific and you should investigate first, -if your use case can be covered with webpack plugins first. They should never access files +if your use case can be covered with webpack plugins first. They should never access files outside of their current working directory which is the compiled function folder, if any. A valid use case would be to start anything available as binary from `node_modules`. @@ -439,7 +439,7 @@ 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 +This config option allows you to exclude files that match a glob from function resolution. Just add: `excludeFiles: **/*.test.js` (with whatever glob you want to exclude). @@ -452,6 +452,19 @@ custom: This is also useful for projects that use TypeScript. +#### Exclude Files with Regular Expression + +This config option allows you to filter files that match a regex pattern before +adding to the zip file. Just add: `excludeRegex: \.ts|test|\.map` (with whatever +regex you want to exclude). + +```yaml +# serverless.yml +custom: + webpack: + excludeRegex: /\.ts|test|\.map/ +``` + #### 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 2022ca913..d2fea80c7 100644 --- a/lib/Configuration.js +++ b/lib/Configuration.js @@ -53,6 +53,10 @@ class Configuration { get excludeFiles() { return this._config.excludeFiles; } + + get excludeRegex() { + return this._config.excludeRegex; + } get packager() { return this._config.packager; diff --git a/lib/packageModules.js b/lib/packageModules.js index 5026f2c29..4d6eac5f2 100644 --- a/lib/packageModules.js +++ b/lib/packageModules.js @@ -34,13 +34,17 @@ function zip(directory, name) { const output = fs.createWriteStream(artifactFilePath); - const files = glob.sync('**', { + let files = glob.sync('**', { cwd: directory, dot: true, silent: true, follow: true }); + if (this.configuration.excludeRegex) { + files = _.filter(files, f => f.match(this.configuration.excludeRegex) === null); + } + if (_.isEmpty(files)) { const error = new this.serverless.classes.Error('Packaging: No files found'); return BbPromise.reject(error); diff --git a/tests/packageModules.test.js b/tests/packageModules.test.js index 514a230ec..675b51cb6 100644 --- a/tests/packageModules.test.js +++ b/tests/packageModules.test.js @@ -7,6 +7,7 @@ const path = require('path'); const sinon = require('sinon'); const mockery = require('mockery'); const Serverless = require('serverless'); +const Configuration = require('../lib/Configuration'); // Mocks const fsMockFactory = require('./mocks/fs.mock'); @@ -73,7 +74,8 @@ describe('packageModules', () => { { serverless, options: {}, - webpackOutputPath: '.webpack' + webpackOutputPath: '.webpack', + configuration: new Configuration() }, baseModule ); @@ -308,6 +310,54 @@ describe('packageModules', () => { module.compileStats = stats; return expect(module.packageModules()).to.be.rejectedWith('Packaging: No files found'); }); + + it('should reject if no files are found because all files are excluded using regex', () => { + module.configuration = new Configuration({ + webpack: { + excludeRegex: /.*/ + } + }); + + // Test data + const stats = { + stats: [ + { + compilation: { + compiler: { + outputPath: '/my/Service/Path/.webpack/service' + } + } + } + ] + }; + const files = [ 'README.md', 'src/handler1.js', 'src/handler1.js.map', 'src/handler2.js', 'src/handler2.js.map' ]; + const allFunctions = [ 'func1', 'func2' ]; + const func1 = { + handler: 'src/handler1', + events: [] + }; + const func2 = { + handler: 'src/handler2', + events: [] + }; + // Serverless behavior + sandbox.stub(serverless.config, 'servicePath').value('/my/Service/Path'); + getVersionStub.returns('1.18.0'); + getServiceObjectStub.returns({ + name: 'test-service' + }); + getAllFunctionsStub.returns(allFunctions); + getFunctionStub.withArgs('func1').returns(func1); + getFunctionStub.withArgs('func2').returns(func2); + // Mock behavior + globMock.sync.returns(files); + fsMock._streamMock.on.withArgs('open').yields(); + fsMock._streamMock.on.withArgs('close').yields(); + fsMock._statMock.isDirectory.returns(false); + + module.compileStats = stats; + return expect(module.packageModules()).to.be.rejectedWith('Packaging: No files found'); + }); }); describe('with individual packaging', () => {