Repository: serverless/serverless (JavaScript)
Date: 2017-07-26 03:57:37+00:00
Task type: Bug Fix

Issue:
1.18.0 breaks referencing variables in Javascript Files
# This is a Bug Report

## Description

This might be considered an edge case based upon how I make use of a JavaScript configuration file but thought I would share this issue nonetheless as it tripped me up when I went to do a deploy this evening after updating my deps.

Consider the following contrived example.

**config.js**
```
const env    = process.env.NODE_ENV || 'dev';
const config = { destinationStream : `kinesis-stream-${env}` }

config._get = function () { return this; }
module.exports = config;
```
**serverless.yml**
```
custom :
    destinationStream : ${file(./config.js):_get.destinationStream}
```
**cmd to reproduce**
```shell
> NODE_ENV=staging serverless package
```
Running the above cmd results in the following error. 

` Invalid variable syntax when referencing file "./config.js". Check if your javascript is returning the correct data.`

> **Note** this issue did not exist prior to 1.18.

I've reviewed the PR's which made up 1.18 and have pin-pointed #3888 as being the change which introduced this new behaviour.

Prior to 1.18 when a JS file was resolved it was immediately invoked and so it's context was maintained. However in #3888 it is assigned to a local variable and so no-longer has it's original execution context. ( see [lib/classes/Variables#L255](https://github.com/serverless/serverless/blob/master/lib/classes/Variables.js#L255) )

This can be fixed by amending L255 to :

`valueToPopulate = returnValueFunction.call(jsFile);`

I'd be happy to submit a PR for this if you think it's worth fixing. I've since worked around it but as per above I thought I'd share the info.

## Additional Data

* ***Serverless Framework Version you're using***: 1.18
* ***Operating System***: 10.12.5
* ***Stack Trace***: N/A
* ***Provider Error messages***: N/A



Issue discussion:
@indieisaconcept thanks for reporting! thats a pretty creative edge case! have you been able to reproduce it with a simpler config? just wondering if it's a bigger issue than we think.

a PR with the fix would be great! 😊 
We're seeing this too. Libraries like `request` rely on it somehow, and are breaking. Reverting to 1.17 fixes the issue.
@eahefnawy I don't expect it to be an issue if your not doing any additional processing from within a *.js variables file - so a basic config should be fine. I'll confirm though.

As this is a simple fix, I'll submit a PR shortly.

Solution patch:
diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js
index fe0c9444de0..567974abdfd 100644
--- a/lib/classes/Variables.js
+++ b/lib/classes/Variables.js
@@ -252,7 +252,7 @@ class Variables {
             ' Check if your javascript is exporting a function that returns a value.',
           ].join(''));
       }
-      valueToPopulate = returnValueFunction();
+      valueToPopulate = returnValueFunction.call(jsFile);
 
       return BbPromise.resolve(valueToPopulate).then(valueToPopulateResolved => {
         let deepProperties = variableString.replace(matchedFileRefString, '');


Test patch:
diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js
index 8ce01fdc9d5..e4b9db61a33 100644
--- a/lib/classes/Variables.test.js
+++ b/lib/classes/Variables.test.js
@@ -683,7 +683,7 @@ describe('Variables', () => {
         });
     });
 
-    it('should thow if property exported by a javascript file is not a function', () => {
+    it('should throw if property exported by a javascript file is not a function', () => {
       const serverless = new Serverless();
       const SUtils = new Utils();
       const tmpDirPath = testUtils.getTmpDirPath();
@@ -716,6 +716,25 @@ describe('Variables', () => {
         });
     });
 
+    it('should preserve the exported function context when executing', () => {
+      const serverless = new Serverless();
+      const SUtils = new Utils();
+      const tmpDirPath = testUtils.getTmpDirPath();
+      const jsData = `
+      module.exports.one = {two: {three: 'hello world'}}
+      module.exports.hello=function(){ return this; };`;
+
+      SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData);
+
+      serverless.config.update({ servicePath: tmpDirPath });
+      serverless.variables.loadVariableSyntax();
+
+      return serverless.variables.getValueFromFile('file(./hello.js):hello.one.two.three')
+        .then(valueToPopulate => {
+          expect(valueToPopulate).to.equal('hello world');
+        });
+    });
+
     it('should throw error if not using ":" syntax', () => {
       const serverless = new Serverless();
       const SUtils = new Utils();