Repository: serverless-heaven/serverless-webpack
Pull request #313 (2018-01-25 14:49:08)

add hook *before:step-functions-offline:start*
<!--
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:

Integration **serverless-step-functions-offline** with **serverless-webpack**

<!--
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:

Just add hook **'before:step-functions-offline:start'**

<!--
If this is a nontrivial change please briefly describe your implementation so its easy for us to understand and review your code.
-->

## How can we verify it:
Run **serverless-step-functions-offline** plugin in serverless project where **serverless-webpack** will be first runned

```yaml
plugins:
   - serverless-webpack
   ...
   - serverless-step-functions-offline
```

<!--
Add any applicable config, commands, screenshots or other resources
to make it easy for us to verify this works. The easier you make it for us
to review a PR, the faster we can review and merge it.

Examples:
* Step by step description, how to verify
* Screenshots - Showing the difference between your output and the master
* Other - Anything else that comes to mind to help us evaluate
-->

## Todos:

- [ ] Write tests
- [ ] Write documentation
- [x] Fix linting errors
- [ ] Make sure code coverage hasn't dropped
- [ ] 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/.editorconfig b/.editorconfig
index 9fbef2a..93dab0f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,10 +1,12 @@
-root = true
-
-[*]
-indent_style = space
-end_of_line = lf
-
-[*.{js,jsx}]
-indent_style = space
-end_of_line = lf
-charset = utf-8
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+
+[*.{js,jsx}]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
diff --git a/README.md b/README.md
index 67bdd0c..06e6664 100644
--- a/README.md
+++ b/README.md
@@ -453,6 +453,14 @@ Plugin commands are supported by the following providers. ⁇ indicates that com
 | invoke local          |      ✔︎     |         ✔︎        |        ⁇        |            ⁇           |
 | invoke local --watch  |      ✔︎     |         ✔︎        |        ⁇        |            ⁇           |
 
+## Plugin support
+
+The following serverless plugins are explicitly supported with `serverless-webpack`
+
+| Plugin                            | NPM |
+|-----------------------------------|-----|
+| serverless-step-functions-offline | [![NPM][ico-step-functions-offline]][link-step-functions-offline] |
+
 ## For developers
 
 The plugin exposes a complete lifecycle model that can be hooked by other plugins to extend
@@ -602,6 +610,8 @@ plugin when running a command or invoked by a hook.
 [link-examples]: ./examples
 [link-serverless-offline]: https://www.npmjs.com/package/serverless-offline
 [link-serverless-dynamodb-local]: https://www.npmjs.com/package/serverless-dynamodb-local
+[link-step-functions-offline]: https://www.npmjs.com/package/serverless-step-functions-offline
+[ico-step-functions-offline]: https://img.shields.io/npm/v/serverless-step-functions-offline.svg
 
 [comment]: # (Referenced issues)
 
diff --git a/index.js b/index.js
index 5b5e471..b3c7d72 100644
--- a/index.js
+++ b/index.js
@@ -12,6 +12,7 @@ const run = require('./lib/run');
 const prepareLocalInvoke = require('./lib/prepareLocalInvoke');
 const runPluginSupport = require('./lib/runPluginSupport');
 const prepareOfflineInvoke = require('./lib/prepareOfflineInvoke');
+const prepareStepOfflineInvoke = require('./lib/prepareStepOfflineInvoke');
 const packExternalModules = require('./lib/packExternalModules');
 const packageModules = require('./lib/packageModules');
 const lib = require('./lib');
@@ -44,7 +45,8 @@ class ServerlessWebpack {
       packageModules,
       prepareLocalInvoke,
       runPluginSupport,
-      prepareOfflineInvoke
+      prepareOfflineInvoke,
+      prepareStepOfflineInvoke
     );
 
     this.commands = {
@@ -153,6 +155,9 @@ class ServerlessWebpack {
         .then(this.prepareOfflineInvoke)
         .then(this.wpwatch),
 
+      'before:step-functions-offline:start': () => BbPromise.bind(this)
+         .then(this.prepareStepOfflineInvoke)
+         .then(this.compile)
     };
   }
 }
diff --git a/lib/prepareStepOfflineInvoke.js b/lib/prepareStepOfflineInvoke.js
new file mode 100644
index 0000000..ddc12fe
--- /dev/null
+++ b/lib/prepareStepOfflineInvoke.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const _ = require('lodash');
+const path = require('path');
+
+/**
+ * Special settings for use with serverless-step-functions-offline.
+ */
+
+module.exports = {
+  prepareStepOfflineInvoke() {
+    _.set(this.serverless, 'service.package.individually', false);
+
+    return this.serverless.pluginManager.spawn('webpack:validate')
+      .then(() => {
+        if (!this.options.location && !_.get(this.serverless, 'service.custom.stepFunctionsOffline.location')) {
+          _.set(this.serverless, 'service.custom.stepFunctionsOffline.location',
+            path.relative(this.serverless.config.servicePath, path.join(this.webpackOutputPath, 'service'))
+          );
+        }
+        return null;
+      });
+  }
+};


Test patch:
diff --git a/tests/all.js b/tests/all.js
index b68fc27..e9d3d5b 100644
--- a/tests/all.js
+++ b/tests/all.js
@@ -9,4 +9,5 @@ describe('serverless-webpack', () => {
   require('./cleanup.test');
   require('./wpwatch.test');
   require('./runPluginSupport.test');
+  require('./prepareStepOfflineInvoke.test');
 });
diff --git a/tests/prepareStepOfflineInvoke.test.js b/tests/prepareStepOfflineInvoke.test.js
new file mode 100644
index 0000000..91c651e
--- /dev/null
+++ b/tests/prepareStepOfflineInvoke.test.js
@@ -0,0 +1,132 @@
+'use strict';
+
+const _ = require('lodash');
+const BbPromise = require('bluebird');
+const chai = require('chai');
+const sinon = require('sinon');
+const Serverless = require('serverless');
+
+chai.use(require('chai-as-promised'));
+chai.use(require('sinon-chai'));
+
+const expect = chai.expect;
+
+describe('prepareStepOfflineInvoke', () => {
+  let serverless;
+  let baseModule;
+  let module;
+  let sandbox;
+
+  before(() => {
+    sandbox = sinon.createSandbox();
+    sandbox.usingPromise(BbPromise.Promise);
+
+    baseModule = require('../lib/prepareStepOfflineInvoke');
+    Object.freeze(baseModule);
+  });
+
+  beforeEach(() => {
+    serverless = new Serverless();
+    serverless.cli = {
+      log: sandbox.stub()
+    };
+    sandbox.stub(serverless.pluginManager, 'spawn');
+    module = _.assign({
+      serverless,
+      options: {},
+    }, baseModule);
+  });
+
+  afterEach(() => {
+    sandbox.restore();
+  });
+
+  it('should set service packaging explicitly', () => {
+    serverless.pluginManager.spawn.resolves();
+    serverless.config.servicePath = 'myPath';
+    module.webpackOutputPath = '.';
+    module.serverless.service.package = {};
+
+    return expect(module.prepareStepOfflineInvoke()).to.be.fulfilled
+    .then(() => {
+      expect(module.serverless.service.package).to.have.a.property('individually').that.is.false;
+      return null;
+    });
+  });
+
+  it('should switch to service packaging', () => {
+    serverless.pluginManager.spawn.resolves();
+    serverless.config.servicePath = 'myPath';
+    module.webpackOutputPath = '.';
+    module.serverless.service.package = {
+      individually: true
+    };
+
+    return expect(module.prepareStepOfflineInvoke()).to.be.fulfilled
+    .then(() => {
+      expect(module.serverless.service.package).to.have.a.property('individually').that.is.false;
+      return null;
+    });
+  });
+
+  it('should spawn webpack:validate', () => {
+    serverless.pluginManager.spawn.resolves();
+    serverless.config.servicePath = 'myPath';
+    module.webpackOutputPath = '.';
+
+    return expect(module.prepareStepOfflineInvoke()).to.be.fulfilled
+    .then(() => {
+      expect(serverless.pluginManager.spawn).to.have.been.calledOnce;
+      expect(serverless.pluginManager.spawn).to.have.been.calledWithExactly('webpack:validate');
+      return null;
+    });
+  });
+
+  it('should reject if spawn rejects', () => {
+    serverless.pluginManager.spawn.returns(BbPromise.reject(new Error('spawn failed')));
+    serverless.config.servicePath = 'myPath';
+    module.webpackOutputPath = '.';
+
+    return expect(module.prepareStepOfflineInvoke()).to.be.rejectedWith('spawn failed');
+  });
+
+  it('should set location if not given by user', () => {
+    serverless.pluginManager.spawn.resolves();
+    serverless.config.servicePath = '.';
+    module.webpackOutputPath = '.';
+
+    return expect(module.prepareStepOfflineInvoke()).to.be.fulfilled
+    .then(() => {
+      expect(serverless.service).to.have.a.nested.property('custom.stepFunctionsOffline.location', 'service');
+      return null;
+    });
+  });
+
+  it('should keep location if set in service config', () => {
+    serverless.pluginManager.spawn.resolves();
+    serverless.config.servicePath = '.';
+    module.webpackOutputPath = '.';
+    _.set(module.serverless, 'service.custom.stepFunctionsOffline.location', 'myLocation');
+
+    return expect(module.prepareStepOfflineInvoke()).to.be.fulfilled
+    .then(() => {
+      expect(serverless.service).to.have.a.nested.property('custom.stepFunctionsOffline.location', 'myLocation');
+      return null;
+    });
+  });
+
+  it('should keep location if set in options', () => {
+    serverless.pluginManager.spawn.resolves();
+    serverless.config.servicePath = '.';
+    module.webpackOutputPath = '.';
+    module.options = {
+      location: 'myLocation'
+    };
+
+    return expect(module.prepareStepOfflineInvoke()).to.be.fulfilled
+    .then(() => {
+      expect(serverless.service).to.not.have.a.nested.property('custom.stepFunctionsOffline.location');
+      return null;
+    });
+  });
+});