From 2de1e3f36d650337a043f71a5e869647a4d34aec Mon Sep 17 00:00:00 2001 From: Kristopher Maschi Date: Wed, 2 Apr 2025 14:29:57 -0400 Subject: [PATCH 1/2] feat(localdev): Added support for invoking local module functions in hooks --- src/handleBeforeAllHooks.js | 17 +++++- src/utils.js | 105 ++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/src/handleBeforeAllHooks.js b/src/handleBeforeAllHooks.js index 2d5c2b2..74f7761 100644 --- a/src/handleBeforeAllHooks.js +++ b/src/handleBeforeAllHooks.js @@ -11,27 +11,40 @@ governing permissions and limitations under the License. */ const { + importFn, + isModuleFn, isRemoteFn, invokeLocalFunction, + invokeLocalModuleFunction, invokeRemoteFunction, - importFn, } = require("./utils"); const handleBeforeAllHooks = (fnBuildConfig) => async (fnExecConfig) => { try { const { memoizedFns, baseDir, logger, beforeAll } = fnBuildConfig; const { payload, updateContext } = fnExecConfig; - let beforeAllFn = null; + let beforeAllFn; if (!memoizedFns.beforeAll) { if (isRemoteFn(beforeAll.composer)) { + // Invoke remote endpoint beforeAllFn = await invokeRemoteFunction(beforeAll.composer, { baseDir, importFn, logger, blocking: beforeAll.blocking, }); + } else if (isModuleFn(beforeAll)) { + // Invoke function from imported module. This handles bundled scenarios such as local development where the + // module needs to be known statically at build time. + beforeAllFn = await invokeLocalModuleFunction(beforeAll.module, beforeAll.fn, { + baseDir, + importFn, + logger, + blocking: beforeAll.blocking, + }); } else { + // Invoke local function at runtime beforeAllFn = await invokeLocalFunction(beforeAll.composer, { baseDir, importFn, diff --git a/src/utils.js b/src/utils.js index 7364e5b..4d11b3a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -172,6 +172,100 @@ const invokeRemoteFunction = async (url, metaConfig) => (data) => { } }; +/** + * Invoke local module function. Used in situations where the module needs to be imported at build time such as in + * bundled use cases like local development. + * @param mod Imported/required JavaScript module. + * @param functionName Function name. + * @param metaConfig Meta configuration + * @returns {Promise>} + */ +const invokeLocalModuleFunction = async (mod, functionName, metaConfig) => { + const { logger, blocking } = metaConfig; + + const exportName = functionName || 'default' + + let composerFn = null; + + try { + composerFn = mod[exportName] || (mod.default && mod.default[exportName]) || mod.default || mod; + } catch (err) { + logger.error("error while invoking local function %s", composerFn); + logger.error(err); + + return Promise.reject({ + status: "ERROR", + message: + err.message || `Unable to invoke local function ${composerFn}`, + }); + } + + return (data) => { + return new Promise((resolve, reject) => { + try { + if (!composerFn) { + reject({ + status: "ERROR", + message: `Unable to invoke local function ${composerFn}`, + }); + } + + logger.debug("Invoking local fn %o", composerFn); + + const result = composerFn(data); + + if (blocking) { + if (result instanceof Promise) { + timedPromise(result) + .then((res) => { + if (res.status.toUpperCase() === "SUCCESS") { + resolve(res); + } else { + reject(res); + } + }) + .catch((error) => { + logger.error( + "error while invoking local function %o", + composerFn + ); + logger.error(error); + + reject({ + status: "ERROR", + message: + error.message || + `Error while invoking local function ${composerFn}`, + }); + }); + } else { + if (result.status.toUpperCase() === "SUCCESS") { + resolve(result); + } else { + reject(result); + } + } + } else { + resolve({ + status: "SUCCESS", + message: "Local function invoked successfully", + }); + } + } catch (error) { + logger.error("error while invoking local function %o", composerFn); + logger.error(error); + + reject({ + status: "ERROR", + message: + error.message || + `Error while invoking local function ${composerFn}`, + }); + } + }); + }; +}; + const invokeLocalFunction = async (composerFnPath, metaConfig) => { const { baseDir, logger, importFn, blocking } = metaConfig; @@ -267,9 +361,20 @@ const isRemoteFn = (composer) => { return urlRegex.test(composer); }; +/** + * Whether beforeAll has module reference. + * @param beforeAll + * @returns {boolean} + */ +const isModuleFn = (beforeAll) => { + return !!(beforeAll.module && beforeAll.fn); +} + module.exports = { invokeRemoteFunction, + invokeLocalModuleFunction, invokeLocalFunction, isRemoteFn, + isModuleFn, importFn, }; From f2549420b184ebcedfeabd2b03c46fedfc9b4676 Mon Sep 17 00:00:00 2001 From: Kristopher Maschi Date: Wed, 2 Apr 2025 14:30:41 -0400 Subject: [PATCH 2/2] feat(localdev): Bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1cd64b8..ec84164 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/plugin-hooks", - "version": "0.3.3", + "version": "0.3.4", "publishConfig": { "access": "public" },