Reverts runner refactoring changes. (#141)
* Partially revert runner changes related to code eval.
* Add webpacked test case.
* Simplify test case.
diff --git a/core/nodejsActionBase/runner.js b/core/nodejsActionBase/runner.js
index 08bb07e..25cdc6c 100644
--- a/core/nodejsActionBase/runner.js
+++ b/core/nodejsActionBase/runner.js
@@ -23,56 +23,50 @@
const fs = require('fs');
const path = require('path');
+/** Initializes the handler for the user function. */
+function initializeActionHandler(message) {
+ if (message.binary) {
+ // The code is a base64-encoded zip file.
+ return unzipInTmpDir(message.code)
+ .then(moduleDir => {
+ let parts = splitMainHandler(message.main);
+ if (parts === undefined) {
+ // message.main is guaranteed to not be empty but be defensive anyway
+ return Promise.reject('Name of main function is not valid.');
+ }
+
+ // If there is only one property in the "main" handler, it is the function name
+ // and the module name is specified either from package.json or assumed to be index.js.
+ let [index, main] = parts;
+
+ // Set the executable directory to the project dir.
+ process.chdir(moduleDir);
+
+ if (index === undefined && !fs.existsSync('package.json') && !fs.existsSync('index.js')) {
+ return Promise.reject('Zipped actions must contain either package.json or index.js at the root.');
+ }
+
+ // The module to require.
+ let whatToRequire = index !== undefined ? path.join(moduleDir, index) : moduleDir;
+ let handler = eval('require("' + whatToRequire + '").' + main);
+ return assertMainIsFunction(handler, message.main);
+ })
+ .catch(error => Promise.reject(error));
+ } else try {
+ // The code is a plain old JS file.
+ let handler = eval('(function(){' + message.code + '\nreturn ' + message.main + '})()');
+ return assertMainIsFunction(handler, message.main);
+ } catch (e) {
+ return Promise.reject(e);
+ }
+}
+
class NodeActionRunner {
- constructor() {
- this.userScriptMain = undefined;
+ constructor(handler) {
+ this.userScriptMain = handler;
}
- /** Initializes the runner with the user function. */
- init(message) {
- if (message.binary) {
- // The code is a base64-encoded zip file.
- return unzipInTmpDir(message.code)
- .then(moduleDir => {
- let parts = splitMainHandler(message.main);
- if (parts === undefined) {
- // message.main is guaranteed to not be empty but be defensive anyway
- return Promise.reject('Name of main function is not valid.');
- }
-
- // If there is only one property in the "main" handler, it is the function name
- // and the module name is specified either from package.json or assumed to be index.js.
- let [index, main] = parts;
-
- // Set the executable directory to the project dir.
- process.chdir(moduleDir);
-
- if (index === undefined && !fs.existsSync('package.json') && !fs.existsSync('index.js')) {
- return Promise.reject('Zipped actions must contain either package.json or index.js at the root.');
- }
-
- // The module to require.
- let whatToRequire = index !== undefined ? path.join(moduleDir, index) : moduleDir;
- this.userScriptMain = evalScript(main, whatToRequire)
- assertMainIsFunction(this.userScriptMain, message.main);
-
- // The value 'true' has no special meaning here; the successful state is
- // fully reflected in the successful resolution of the promise.
- return true;
- })
- .catch(error => Promise.reject(error));
- } else try {
- // The code is a plain old JS file.
- this.userScriptMain = evalScript(message.main, false, message.code)
- assertMainIsFunction(this.userScriptMain, message.main);
-
- return Promise.resolve(true); // See comment above about 'true'; it has no specific meaning.
- } catch (e) {
- return Promise.reject(e);
- }
- };
-
run(args) {
return new Promise((resolve, reject) => {
try {
@@ -163,22 +157,15 @@
} else return undefined
}
-function assertMainIsFunction(userScriptMain, main) {
- if (typeof userScriptMain !== 'function') {
- throw "Action entrypoint '" + main + "' is not a function.";
- }
-}
-
-/**
- * Evals the code to execute. This is a global function so that the eval is in the global context
- * and hence functions which use variables without 'var' are permitted.
- */
-function evalScript(main, whatToRequire, code) {
- if (whatToRequire) {
- return eval('require("' + whatToRequire + '").' + main);
+function assertMainIsFunction(handler, name) {
+ if (typeof handler === 'function') {
+ return Promise.resolve(handler);
} else {
- return eval('(function(){' + code + '\nreturn ' + main + '})()');
+ return Promise.reject("Action entrypoint '" + name + "' is not a function.");
}
}
-module.exports = NodeActionRunner;
+module.exports = {
+ NodeActionRunner,
+ initializeActionHandler
+};
diff --git a/core/nodejsActionBase/src/service.js b/core/nodejsActionBase/src/service.js
index 11ffeb7..4d994af 100644
--- a/core/nodejsActionBase/src/service.js
+++ b/core/nodejsActionBase/src/service.js
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-const NodeActionRunner = require('../runner');
+const { initializeActionHandler, NodeActionRunner } = require('../runner');
function NodeActionService(config) {
@@ -160,13 +160,10 @@
};
function doInit(message) {
- userCodeRunner = new NodeActionRunner();
-
- return userCodeRunner
- .init(message)
- // returning 'true' has no particular meaning here. The fact that the promise resolved
- // successfully in itself carries the intended message that initialization succeeded.
- .then(_ => true)
+ return initializeActionHandler(message)
+ .then(handler => {
+ userCodeRunner = new NodeActionRunner(handler);
+ })
// emit error to activation log then flush the logs as this is the end of the activation
.catch(error => {
console.error('Error during initialization:', error);
diff --git a/tests/src/test/scala/runtime/actionContainers/NodeJsActionContainerTests.scala b/tests/src/test/scala/runtime/actionContainers/NodeJsActionContainerTests.scala
index f677882..63f30fd 100644
--- a/tests/src/test/scala/runtime/actionContainers/NodeJsActionContainerTests.scala
+++ b/tests/src/test/scala/runtime/actionContainers/NodeJsActionContainerTests.scala
@@ -291,6 +291,30 @@
})
}
+ it should "support webpacked function" in {
+ val (out, err) = withNodeJsContainer { c =>
+ val code =
+ """
+ |function foo() {
+ | return { bar: true }
+ |}
+ |global.main = foo
+ """.stripMargin
+
+ c.init(initPayload(code))._1 should be(200)
+
+ val (runCode, result) = c.run(JsObject.empty)
+ runCode should be(200)
+ result should be(Some(JsObject("bar" -> JsTrue)))
+ }
+
+ checkStreams(out, err, {
+ case (o, e) =>
+ o shouldBe empty
+ e shouldBe empty
+ })
+ }
+
it should "error when requiring a non-existent package" in {
// NPM package names cannot start with a dot, and so there is no danger
// of the package below ever being valid.