UNPKG

@haystacks/async

Version:

A framework to build any number or any kind of native application or automation solution.

925 lines (889 loc) 54.8 kB
/** * @file pluginBroker.js * @module pluginBroker * @description Contains all of the lower level plugin processing functions, * and also acts as an interface for loading, unloading, reloading, registering, * unregistering plugins and plugin metaData. * @requires module:constantBroker * @requires module:dataBroker * @requires module:ruleBroker * @requires module:workflowBroker * @requires module:loggers * @requires module:data * @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants} * @requires {@link https://www.npmjs.com/package/url|url} * @requires {@link https://www.npmjs.com/package/path|path} * @author Seth Hollingsead * @date 2022/09/02 * @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved */ // Internal imports import constantBroker from './constantBroker.js'; import dataBroker from './dataBroker.js'; import ruleBroker from './ruleBroker.js'; import workflowBroker from './workflowBroker.js'; import loggers from '../executrix/loggers.js'; import D from '../structures/data.js'; // External imports import hayConst from '@haystacks/constants'; import url from 'url'; import path from 'path'; import configurator from '../executrix/configurator.js' import commandBroker from './commandBroker.js' import themeBroker from './themeBroker.js' const {bas, biz, cfg, gen, msg, sys, wrd} = hayConst; const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url)); // framework.brokers.pluginBroker. const namespacePrefix = wrd.cframework + bas.cDot + wrd.cbrokers + bas.cDot + baseFileName + bas.cDot; /** * @function loadPluginRegistry * @description Loads the plugin registry file, which specified the data with the paths were plugins should be loaded from. * @param {string} pluginRegistryPath The path to the plugin registry for the app that loaded the haystacks framework. * @return {object} The JSON data object loaded from the specified plugin registry path by the input parameter. * @author Seth Hollingsead * @date 2022/09/13 */ async function loadPluginRegistry(pluginRegistryPath) { let functionName = loadPluginRegistry.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginRegistryPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryPathIs + pluginRegistryPath); let returnData = {}; let resolvedPluginRegistryPath = path.resolve(pluginRegistryPath); // resolvedPluginRegistryPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cresolvedPluginRegistryPathIs + resolvedPluginRegistryPath); returnData = await ruleBroker.processRules([resolvedPluginRegistryPath, ''], [biz.cgetJsonData]); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function storePluginRegistryInDataStructure * @description Pushes the input plugin registry data to the D-data structure so it can be used by the rest of the framework. * @param {object} pluginRegistryData The plugin registry data that should be stored in the D-data structure. * @return {boolean} A True or False value to indicate if the data was successfully stored in the D-data structure or not. * @author Seth Hollingsead * @date 2022/09/14 */ async function storePluginRegistryInDataStructure(pluginRegistryData) { let functionName = storePluginRegistryInDataStructure.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginRegistryData is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryDataIs + JSON.stringify(pluginRegistryData)); let returnData = false; try { if (D[cfg.cpluginRegistry] === 'undefined') { D[cfg.cpluginRegistry] = {}; } D[cfg.cpluginRegistry] = pluginRegistryData; returnData = true; } catch (err) { // ERROR: There was a problem saving the registry data to the plugin registry in the d-data structure: console.log(msg.cstorePluginRegistryInDataStoreMessage01 + err); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function listAllLoadedPlugins * @description Builds a list array of the names of the plugins that are currently loaded. * @return {array<string>} A list array of the names of the plugins that are currently loaded in the Haystacks platform. * @author Seth Hollingsead * @date 2023/02/06 */ async function listAllLoadedPlugins() { let functionName = listAllLoadedPlugins.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = []; let pluginsLoadedList = D[sys.cpluginsLoaded]; // pluginsLoadedList is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginsLoadedListIs + JSON.stringify(pluginsLoadedList)); if (Array.isArray(pluginsLoadedList) === true && pluginsLoadedList.length >= 1) { // pluginsLoadedList is an array and length greater than or equal to 1 await loggers.consoleLog(namespacePrefix + functionName, msg.cunloadPluginMessage01); for (let pluginLoadedKey in pluginsLoadedList) { // pluginLoadedKey is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginLoadedKeyIs + pluginLoadedKey); let pluginLoadedEntry = pluginsLoadedList[pluginLoadedKey]; // pluginLoadedEntry is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginLoadedEntryIs + JSON.stringify(pluginLoadedEntry)); // pluginLoadedEntry name is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginLoadedEntryNameIs + pluginLoadedEntry[0]); returnData.push(pluginLoadedEntry[0]); }// End-for (let pluginLoadedKey in pluginsLoadedList) } // End-if (Array.isArray(pluginsLoadedList) === true && pluginsLoadedList >= 1) // List of loaded plugins is: await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function listPluginsInRegistry * @description Builds a list array of the names of the plugins in the plugin registry. * @return {array<string>} A list array of the names of the plugins in the plugin registry. * @author Seth Hollingsead * @date 2022/09/14 */ async function listPluginsInRegistry() { let functionName = listPluginsInRegistry.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = []; returnData = await listPluginsAttributeInRegistry(wrd.cName); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function listPluginsPathsInRegistry * @description Builds a list array of the paths of the plugins in the plugin registry. * @return {array<string>} A list array of the paths of the plugins in the plugin registry. * @author Seth Hollingsead * @date 2022/09/20 */ async function listPluginsPathsInRegistry() { let functionName = listPluginsPathsInRegistry.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = []; returnData = await listPluginsAttributeInRegistry(wrd.cPath); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function listPluginsAttributeInRegistry * @description Builds a list array of the specified attribute out of the plugin objects in the plugin registry. * @param {string} attributeName The name of the attribute that should be looked up in the plugin object, * for each of the plugin objects in the plugin registry. * @return {array<string>} A list array of the attributes from the plugins in the plugin registry. * @author Seth Hollingsead * @date 2023/01/31 */ async function listPluginsAttributeInRegistry(attributeName) { let functionName = listPluginsAttributeInRegistry.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // attributeName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeNameIs + attributeName); let returnData = []; let pluginRegistryList = D[cfg.cpluginRegistry][wrd.cplugins]; // pluginRegistryList is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryListIs + JSON.stringify(pluginRegistryList)); for (let pluginKey in pluginRegistryList) { // pluginKey is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginKeyIs + pluginKey); let pluginParentObject = pluginRegistryList[pluginKey]; // pluginParentObject is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginParentObjectIs + JSON.stringify(pluginParentObject)); let keys = Object.keys(pluginParentObject); let pluginObject = pluginParentObject[keys[0]]; // pluginObject is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginObjectIs + JSON.stringify(pluginObject)); returnData.push(pluginObject[attributeName]); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function listPluginsInRegistryPath * @description In the plugin registry there should be an entry for the path of the plugins on the local system. * This function will load that path and return a list of the sub-folders located at that path. * @return {array<string>} A list array of the names of the plugins located at the specified path on * the local system from the plugins registry data hive. * @author Seth Hollingsead * @date 2022/09/14 */ async function listPluginsInRegistryPath() { let functionName = listPluginsInRegistryPath.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = []; returnData = ruleBroker.processRules([D[cfg.cpluginRegistry][wrd.cpath], ''], [biz.cgetDirectoryList]); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function countPluginsInRegistry * @description Scans the plugin registry data hive and determines how many plugins are registered there. * @return {integer} The count of the number of plugins listed in the plugin registry data hive. * @author Seth Hollingsead * @date 2022/09/14 */ async function countPluginsInRegistry() { let functionName = countPluginsInRegistry.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = 0; let pluginRegistryList = await listPluginsInRegistry(); returnData = pluginRegistryList.length; await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function countPluginsInRegistryPath * @description Counts the number of sub-folders located at the path specified in the plugin registry data hive. * @return {integer} The count of the number of plugin sub-folders in the plugins path listed in the plugin registry data hive. * @author Seth Hollingsead * @date 2022/09/14 */ async function countPluginsInRegistryPath() { let functionName = countPluginsInRegistryPath.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = 0; let pluginFolderList = await listPluginsInRegistryPath(); returnData = pluginFolderList.length; await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function registerPlugin * @description Manually registers a plugin with the plugin registry data hive. * Allows for special case where plugins can be registered from a different path, then the path specified by the plugin registry. * Caution should be emphasized when loading plugins from a custom path location, this should be used primarily for debugging and triage use cases. * @param {string} pluginName The name of the plugin that should be registered. * @param {string} pluginPath The path to the plugin, to be added to the plugin registry. * This should be the path to the plugin/package.json file, but not including the package.json as part of the path URI. * @return {boolean} True or False to indicate if the plugin was added to the plugin registry successfully or not. * @author Seth Hollingsead * @date 2022/09/14 */ async function registerPlugin(pluginName, pluginPath) { let functionName = registerPlugin.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); // pluginPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginPathIs + pluginPath); let returnData = false; let pluginRegistrationEntry = {}; try { if (pluginName && pluginPath) { pluginRegistrationEntry = {Name: pluginName, Path: pluginPath}; // NOTE: We need to check and see if the plugin is already registered, and throw an error message if it is. // To prevent the user from being able to register the same plugin multiple times. let registeredPluginsArray = await listPluginsInRegistry(); let pluginIsRegistered = false; for (let registeredPluginNameKey in registeredPluginsArray) { let registeredPluginName = registeredPluginsArray[registeredPluginNameKey] if (registeredPluginName === pluginName) { pluginIsRegistered = true; break; } } if (pluginIsRegistered === false) { D[cfg.cpluginRegistry][wrd.cplugins].push({[pluginName]: pluginRegistrationEntry}); returnData = true; } else { // ERROR: The specified plugin is already registered. Plugin name: console.log(msg.cErrorRegisterPluginMessage02 + pluginName); } } else { if (!pluginName) { // ERROR: Plugin Name is an invalid value: console.log(msg.cErrorRegisterPluginMessage03 + pluginName); } if (!pluginPath) { // ERROR: Plugin Path is an invalid value: console.log(msg.cErrorRegisterPluginMessage04 + pluginPath); } } } catch (err) { // ERROR: Failure to register plugin: // pluginPath is: // error message: console.log(msg.cErrorRegisterPluginMessage01 + pluginName); console.log(msg.cpluginPathIs + pluginPath); console.log(msg.cerrorMessage + err); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function unregisterPlugin * @description Manually removes a plugin from the plugin registry data hive. * @param {string} pluginName The name of the plugin that should be removed from the plugin registry. * @return {boolean} True or False to indicate if the plugin was removed from the plugin registry successfully or not. * @author Seth Hollingsead * @date 2022/09/14 */ async function unregisterPlugin(pluginName) { let functionName = unregisterPlugin.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); let returnData = false; // We need to find the index of the plugin we are looking for, then we can use the array.splice method to remove it. let pluginRegistryNames = await listPluginsInRegistry(); let index = 0; try { for (let currentPluginNameKey in pluginRegistryNames) { let currentPluginName = pluginRegistryNames[currentPluginNameKey]; // currentPluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.ccurrentPluginNameIs + currentPluginName); if (currentPluginName === pluginName) { break; } index = index + 1; } let pluginObjects = D[cfg.cpluginRegistry][wrd.cplugins]; pluginObjects.splice(index, 1); let newPluginObjects = pluginObjects; D[cfg.cpluginRegistry][wrd.cplugins] = newPluginObjects; returnData = true; } catch (err) { // ERROR: Failure to unregister plugin: // error message: console.log(msg.cErrorUnRegisterPluginMessage01 + pluginName); console.log(msg.cerrorMessage + err); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function unregisterPlugins * @description Removes a list of plugins from the plugin registry data hive, by calling unregisterPlugin for each one. * @param {array<string>} pluginListArray A list array of plugin names that should be removed from the plugin registry. * @return {boolean} True or False to indicate if all the plugins were removed from the plugin registry successfully or not. * @author Seth Hollingsead * @date 2023/02/07 */ async function unregisterPlugins(pluginListArray) { let functionName = unregisterPlugins.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginListArray is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginListArrayIs + JSON.stringify(pluginListArray)); let returnData = true; let noErrorFound = true; if (Array.isArray(pluginListArray) === true && pluginListArray.length >= 1) { for (let pluginNameKey in pluginListArray) { let pluginName = pluginListArray[pluginNameKey]; // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); if (pluginName) { noErrorFound = await unregisterPlugin(pluginName); } else { // ERROR: The plugin name was not a valid name: console.log(msg.cErrorUnregisterPluginsMessage01 + pluginName); } if (noErrorFound === false) { returnData = false; } } // End-for (let pluginNameKey in pluginListArray) } // End-if (Array.isArray(pluginListArray) === true && pluginListArray.length >= 1) await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function syncPluginRegistryWithPluginRegistryPath * @description performs a synchronization procedure on the plugin registry to ensure that the contents and * plugins registered in the plugin registry match with the list of plugin folders located at the path specified in * the plugin registry as the default plugin path. The updates are made to the plugin registry data hive. * @return {boolean} True or False to indicate if the synchronization was performed successfully. * @author Seth Hollingsead * @date 2022/09/14 * @NOTE It is expected that the number of plugins loaded at any one time will not be crazy high. * This function will do a O(n^2) brute force search, * if the number of plugins needed at any one time ever grows much over 100, then this solution will need to be re-evaluated! */ async function syncPluginRegistryWithPluginRegistryPath() { let functionName = syncPluginRegistryWithPluginRegistryPath.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = false; let pluginRegistryList = await listPluginsInRegistry(); let pluginRegistryFolderList = await listPluginsInRegistryPath(); let accumulatorPluginRegistry = []; let synchronizedPluginRegistryList = []; let pluginsRootPath = await configurator.getConfigurationSetting(wrd.csystem, cfg.cpluginsRootPath); // pluginRegistryList is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryListIs + pluginRegistryList); // pluginRegistryFolderList is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryFolderListIs + pluginRegistryFolderList); // pluginsRootPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginsRootPathIs + pluginsRootPath); try { if (pluginRegistryFolderList && Array.isArray(pluginRegistryFolderList) === false) { if (pluginRegistryFolderList.includes(bas.cComa) === true) { pluginRegistryFolderList = pluginRegistryFolderList.split(bas.cComa); } } if (pluginRegistryList.length === 0 && pluginRegistryFolderList && pluginRegistryFolderList.length === 1) { // pluginRegistryList.length === 0 await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryListLengthEqualZero); synchronizedPluginRegistryList[pluginRegistryFolderList[0]] = {Name: pluginRegistryFolderList[0], Path: path.join(pluginsRootPath + bas.cForwardSlash + pluginRegistryFolderList[0] + bas.cForwardSlash)}; } else if (pluginRegistryFolderList.length !== 0) { // pluginRegistryList.length !== 0 await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryListLengthNotEqualZero); for (let folderPluginKey in pluginRegistryFolderList) { let folderPlugin = pluginRegistryFolderList[folderPluginKey]; // folderPlugin is: await loggers.consoleLog(namespacePrefix + functionName, msg.cfolderPluginIs + JSON.stringify(folderPlugin)); let folderPluginName = folderPlugin[wrd.cName]; if (folderPluginName === undefined) { folderPluginName = folderPlugin; } // folderPluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cfolderPluginNameIs + folderPluginName); // Now search the pluginRegistryList to see if it contains this same name. let foundMatchingPluginName = false; for (let registryPluginKey in pluginRegistryList) { let registryPlugin = pluginRegistryList[registryPluginKey]; // registryPlugin is: await loggers.consoleLog(namespacePrefix + functionName, msg.cregistryPluginIs + JSON.stringify(registryPlugin)); let registryPluginName = registryPlugin[wrd.cName]; // registryPluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cregistryPluginNameIs + registryPluginName); if (folderPluginName === registryPluginName) { foundMatchingPluginName = true; break; } } // End-for (let registryPlugin in pluginRegistryList) if (foundMatchingPluginName === false) { // Then no match was found, and we should therefore add this plugin entry object. // First create the object, folderPlugin is mostly likely just a string. accumulatorPluginRegistry.push({[folderPluginName]: {Name: folderPluginName, Path: path.join(pluginsRootPath + bas.cForwardSlash + pluginRegistryFolderList[folderPluginKey] + bas.cForwardSlash)}}); } // NOTE: pluginRegistryList is the accumulator here, when we are all done we need to assign that one to the output. // accumulatorPluginRegistry is: await loggers.consoleLog(namespacePrefix + functionName, msg.caccumulatorPluginRegistryIs + JSON.stringify(accumulatorPluginRegistry)); } // End-for (let folderPlugin in pluginRegistryFolderList) // Now assign the pluginRegistryList to the synchronizedPluginRgistryList, // then we will clear the plugin registry and re-assign it to the merged/synchronized/updated version of the plugin registry. synchronizedPluginRegistryList = accumulatorPluginRegistry; } // synchronizedPluginRegistryList is: await loggers.consoleLog(namespacePrefix + functionName, msg.csynchronizedPluginRegistryListIs + JSON.stringify(synchronizedPluginRegistryList)); // Now assign it back to the official plugin registry data hive on the D-data structure await unregisterAllPlugins(); D[cfg.cpluginRegistry][wrd.cplugins] = synchronizedPluginRegistryList; returnData = true; } catch (err) { // ERROR: Failure to synchronize the plugin registry with the list of plugins available from the plugins folder specified by the application in the plugins registry JSON file. // error message: await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorSyncPluginRegistryWithPluginRegistryPathMessage01); console.log(msg.cerrorMessage + err); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function unregisterAllPlugins * @description Removes all plugins from the list of registered plugins in the plugin registry. * This removes all plugins in the plugins registry data hive. * @return {boolean} True or False to indicate if the plugin registry data hive was cleared successfully or not. * @author Seth Hollingsead * @date 2022/09/14 */ async function unregisterAllPlugins() { let functionName = unregisterAllPlugins.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = false; try { // Assign it to an empty array, that should successfully wipe out all the contents. D[cfg.cpluginRegistry][wrd.cplugins] = []; returnData = true; } catch (err) { // ERROR: Could not unregister all of the plugins. // error message: console.log(msg.cErrorUnregisterAllPluginsMessage01); console.log(msg.cerrorMessage + err); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function savePluginRegistry * @description Exports the plugin registry data hive JSON object and saves it out to disk, * over-writing any existing plugin registry JSON file at the location specified by the application. * @return {boolean} True or False to indicate if the export to file was completed successfully or not. * @author Seth Hollingsead * @date 2022/09/14 */ async function savePluginRegistry() { let functionName = savePluginRegistry.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); let returnData = false; let pluginRegistry = D[cfg.cpluginRegistry] // pluginRegistry is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryIs + JSON.stringify(pluginRegistry)); try { let pluginRegistryPath = await configurator.getConfigurationSetting(wrd.csystem, cfg.cpluginRegistryPath); // pluginRegistryPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginRegistryPathIs + pluginRegistryPath); returnData = ruleBroker.processRules([pluginRegistryPath, pluginRegistry], [biz.cwriteJsonData]); } catch (err) { // ERROR: Failure to write out the plugin registry to the plugin path specified by the application: // error message: console.log(msg.cErrorSavePluginRegistryMessage01); console.log(msg.cerrorMessage + err); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function loadPluginMetaData * @description Loads the plugin meta data for the plugin at the specified path by looking * for a package.json at the specified path and loading that file, and returning it as a JSON object. * @param {string} pluginPath The path to a plugin where a package.json should be expected to be found for that plugin. * It could also be that the pluginPath just contains the name of the folder that is the plugin, * and the path should be acquired from the plugin registry path. * @return {object} The JSON data object loaded from the plugin package.json file, specified by the input parameter. * @author Seth Hollingsead * @date 2022/09/02 */ async function loadPluginMetaData(pluginPath) { let functionName = loadPluginMetaData.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginPathIs + pluginPath); let returnData = {}; let fullyQualifiedPluginPath = ''; if (pluginPath.includes(bas.cForwardSlash) !== true && pluginPath.includes(bas.cBackSlash) !== true) { // It's just a name, we need to get the first part of the path from the plugin registry path. let prefixPluginPath = await getPluginsRegistryPath(); // prefixPluginPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cprefixPluginPathIs + prefixPluginPath); if (prefixPluginPath.slice(-1) != bas.cForwardSlash && prefixPluginPath.slice(-1) != bas.cBackSlash) { let pathSeparator = ''; // eslint-disable-next-line no-undef if (process.platform === gen.cwin32) { pathSeparator = bas.cBackSlash; } else { pathSeparator = bas.cForwardSlash; } prefixPluginPath = prefixPluginPath + pathSeparator; } fullyQualifiedPluginPath = prefixPluginPath + pluginPath; } else { fullyQualifiedPluginPath = pluginPath; } let resolvedPluginPath = path.resolve(fullyQualifiedPluginPath + bas.cForwardSlash + sys.cpackageDotJson); // resolvedPluginPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cresolvedPluginPathIs + resolvedPluginPath); returnData = await ruleBroker.processRules([resolvedPluginPath, ''], [biz.cgetJsonData]); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function extractAndProcessPluginEntryPointURI * @description Extracts the entry point path and file name for a plugin from the package.json data object. * Processes the entry point path with a path.join to form a fully qualified path. * Converts the resulting fully qualified path into a path URI string for importing later. * @param {object} pluginMetaData The meta data for the given plugin loaded for the corresponding package.json. * @param {string} pluginPath The path to the plugin, used to form a fully-qualified path. * NOTE: It could also be that the pluginPath just contains the name of the folder that is the plugin, * and the path should be acquired from the plugin registry path. * @return {string} The path entry point to the plugin as a URI file path. * @author Seth Hollingsead * @date 2022/09/02 */ async function extractAndProcessPluginEntryPointURI(pluginMetaData, pluginPath) { let functionName = extractAndProcessPluginEntryPointURI.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginMetaData is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginMetaDataIs + JSON.stringify(pluginMetaData)); // pluginPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginPathIs + pluginPath); let returnData = ''; let fullyQualifiedPluginPath = ''; if (pluginMetaData && pluginPath) { if (pluginPath.includes(bas.cForwardSlash) !== true && pluginPath.includes(bas.cBackSlash) !== true) { // It's just a name, we need to get the first part of the path from the plugin registry path. let prefixPluginPath = await getPluginsRegistryPath(); // prefixPluginPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cprefixPluginPathIs + prefixPluginPath); fullyQualifiedPluginPath = prefixPluginPath + pluginPath; } else { fullyQualifiedPluginPath = pluginPath; } let pluginMainPath = pluginMetaData[wrd.cmain]; // pluginMainPath before join is: await loggers.consoleLog(namespacePrefix + functionName, msg.cextractAndProcessPluginEntryPointUriMessage01 + pluginMainPath); pluginMainPath = path.join(fullyQualifiedPluginPath, pluginMainPath); // pluginMainPath after join is: await loggers.consoleLog(namespacePrefix + functionName, msg.cextractAndProcessPluginEntryPointUriMessage02 + pluginMainPath); pluginMainPath = path.normalize(pluginMainPath); pluginMainPath = url.pathToFileURL(pluginMainPath); // pluginMainPath URI is: await loggers.consoleLog(namespacePrefix + functionName, msg.cextractAndProcessPluginEntryPointUriMessage03 + pluginMainPath); returnData = pluginMainPath; } else { // ERROR: No plugin meta data or plugin path are specified: console.log(msg.cextractAndProcessPluginEntryPointUriMessage04 + namespacePrefix + functionName); } await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function loadPlugin * @description Uses a combination of promises and a function call to * import the specified plugin file at the specified URI path. * @param {string} pluginExecutionPath The entry point for the plugin that should be loaded. * @return {object} The data that was returned and loaded from the plugin. * @author Seth Hollingsead * @date 2022/09/02 */ async function loadPlugin(pluginExecutionPath) { let functionName = loadPlugin.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginExecutionPath is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginExecutionPathIs + pluginExecutionPath); let returnData = {}; // D-command stack before loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdCommandStackBeforeLoadingIs, D[wrd.cCommands]); // D-businessRules stack before loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdBusinessRulesStackBeforeLoadingIs, D[sys.cbusinessRules]); // NOTE: We really NEED to deeply clone the D-data structure here, // because for some reason the plugin was mutating the D-data structure and removing the client application defined commands. // This should be the fastest and safest way to get Haystacks context data into the plugin, for its own data dependency loading process. // NOTE: The objectDeepClone function, should also clone functions and business rules, // however, when that data is injected into the plugins instance of Haystacks, then that instance of Haystacks over-writes the commands and functions, // and resets them loading its own business rules and commands. // This is probably how the plugin was mutating the data, which is not safe, or correct. // So in the cloned data, we don't care if the business rules or commands get over written. let dCommandClone = {}; let dBusinessRulesClone = {}; try { dCommandClone = await ruleBroker.processRules([D[wrd.cCommands], ''], [biz.cobjectDeepClone]); } catch (err1) { await loggers.consoleLog(namespacePrefix + functionName, msg.cERROR_Colon + err1.message); } try { dBusinessRulesClone = await ruleBroker.processRules([D[sys.cbusinessRules], ''], [biz.cobjectDeepClone]); } catch (err2) { await loggers.consoleLog(namespacePrefix + functionName, msg.cERROR_Colon + err2.message); } // dCommandClone stack before loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdCommandCloneStackBeforeLoadingIs, dCommandClone); // dBusinessRulesClone stack before loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdBusinessRulesCloneStackBeforeLoadingIs, dBusinessRulesClone); try { const pluginResponseData = new Promise((resolve, reject) => { const loadAsyncImport = () => { const asyncImport = async () => { return await myDynamicImport(pluginExecutionPath); }; return asyncImport().then((result) => { return result; }); }; const myDynamicImport = async (path) => { return await import(path); }; return loadAsyncImport().then(async value => { resolve(returnData = await value[wrd.cdefault].initializePlugin(D)); // dataLoaded is: loggers.consoleLog(namespacePrefix + functionName, msg.cdataLoadedIs + JSON.stringify(returnData)); }).catch (err => reject(err)); }); await Promise.all([pluginResponseData]).then((value) => { loggers.consoleLog(namespacePrefix + functionName, msg.cvalueIs + JSON.stringify(value)); }); } catch (err) { // ERROR: There was an error attempting to load the specified plugin: console.log(msg.cloadPluginErrorMessage01 + pluginExecutionPath); console.log(msg.cerrorMessage + err.message); returnData = false; } // const dDataReset = await D.setData(dCommandClone); // This didn't work either!! // dCommandClone stack after loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdCommandCloneStackAfterLoadingIs, dCommandClone); // dBusinessRulesClone stack after loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdBusinessRulesCloneStackAfterLoadingIs, dBusinessRulesClone); // D-command stack after loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdCommandStackAfterLoadingIs, D[wrd.cCommands]); // D-businessRules stack after loading is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdBusinessRulesStackAfterLoadingIs, D[sys.cbusinessRules]); // NOTE: Even if the business rules were corrupted and that might be why its not possible to use them to re-clone the data back into the D-data structure. // The system commands should not have been corrupted, unless they were corrupted by the plugins instance of haystacks over-writing the D-singleton. // Now copy all of the commands from the backup back to the original. // NOTE: I tried to do this by calling the same business rule: biz.cobjectDeepClone as above. // But that didn't work, so I'm just going to do it manually here in the code below. for (let key in dCommandClone) { if (Object.prototype.hasOwnProperty.call(dCommandClone, key)) { if (typeof dCommandClone[key] === wrd.cfunction) { D[wrd.cCommands][key] = dCommandClone[key]; } } // End-if (inputData.hasOwnProperty(key)) } // End-for (let key in inputData) // Now copy all of the business rules from the backup back to the original. // NOTE: I tried to do this by calling the same business rule: biz.cobjectDeepClone as above. // But that didn't work, so I'm just going to do it manually here in the code below. for (let key in dBusinessRulesClone) { if (Object.prototype.hasOwnProperty.call(dBusinessRulesClone, key)) { if (typeof dBusinessRulesClone[key] === wrd.cfunction) { D[sys.cbusinessRules][key] = dBusinessRulesClone[key]; } } // End-if (inputData.hasOwnProperty(key)) } // End-for (let key in inputData) // D-command stack after over-write is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdCommandStackAfterOverWriteIs, D[wrd.cCommands]); // D-businessRules stack after over-write is: // console.log(namespacePrefix + functionName + bas.cSpace + msg.cdBusinessRulesStackAfterOverWriteIs, D[sys.cbusinessRules]); // await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function integratePluginBusinessRules * @description Saves all of the plugin business rules to the D-data structure where business rules are stored and called from. * @param {string} pluginName The name of the plugin who's business rules should be integrated with the haystacks business rules. * @param {object} pluginBusinessRules The business rules specific to this current plugin. * @return {boolean} True or False to indicate if this plugins business rules are successfully integrated or not. * @author Seth Hollingsead * @date 2022/10/23 */ async function integratePluginBusinessRules(pluginName, pluginBusinessRules) { let functionName = integratePluginBusinessRules.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); // pluginBusinessRules is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginBusinessRulesIs + JSON.stringify(pluginBusinessRules)); let returnData = false; returnData = await ruleBroker.addPluginRules(pluginName, pluginBusinessRules); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function integratePluginCommands * @description Saves all of the plugin commands to the D-data structure where commands are stored and called from. * @param {string} pluginName The name of the plugin who's commands should be integrated with the haystacks commands. * @param {object} pluginCommands The commands specific to this current plugin. * @return {boolean} True or False to indicate if this plugins commands are successfully integrated or not. * @author Seth Hollingsead * @date 2022/10/23 */ async function integratePluginCommands(pluginName, pluginCommands) { let functionName = integratePluginCommands.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); // pluginCommands is: await loggers.consoleLog(namespacePrefix + functionName, msg.cplguinCommandsIs + JSON.stringify(pluginCommands)); let returnData = false; returnData = await commandBroker.addPluginCommands(pluginName, pluginCommands); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function integratePluginConfigurationData * @description Saves all of the plugin configuration data to the D-data structure where configuration data are stored. * @param {string} pluginName The name of the plugin who's configuration data should be integrated with the haystacks configuration data. * @param {object} pluginConfigurationData The JSON object that contains all of the configuration data specific to this current plugin. * @return {boolean} True or False to indicate if this plugins configuration data are successfully integrated or not. * @author Seth Hollingsead * @date 2022/10/23 */ async function integratePluginConfigurationData(pluginName, pluginConfigurationData) { let functionName = integratePluginConfigurationData.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); // pluginConfigurationData is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginConfigurationDataIs + JSON.stringify(pluginConfigurationData)); let returnData = false; returnData = await dataBroker.addPluginConfigurationData(pluginName, pluginConfigurationData); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function integratePluginCommandAliases * @description Saves all of the plugin command aliases to the D-data structure where command aliases data are stored. * @param {string} pluginName The name of the plugin who's command aliases should be integrated with the haystacks command aliases data. * @param {object} pluginCommandAliases The JSON object that contains all of the command aliases specific to this current plugin. * @return {boolean} True or False to indicate if this plugins command aliases data are successfully integrated or not. * @author Seth Hollingsead * @date 2022/10/23 */ async function integratePluginCommandAliases(pluginName, pluginCommandAliases) { let functionName = integratePluginCommandAliases.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); // pluginCommandAliases is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginCommandAliasesIs + JSON.stringify(pluginCommandAliases)); let returnData = false; returnData = await commandBroker.addPluginCommandAliases(pluginName, pluginCommandAliases); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function integratePluginWorkflows * @description Saves all of the plugin workflows to the D-data structure where workflow data are stored. * @param {string} pluginName The name of the plugin who's workflows should be integrated with the haystacks workflows data. * @param {object} pluginWorkflows The JSON object that contains all of the workflows specific to this current plugin. * @return {boolean} True or False to indicate if the plugins workflows data are successfully integrated or not. * @author Seth Hollingsead * @date 2022/10/23 */ async function integratePluginWorkflows(pluginName, pluginWorkflows) { let functionName = integratePluginWorkflows.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); // pluginWorkflows is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginWorkflowsIs + JSON.stringify(pluginWorkflows)); let returnData = false; returnData = await workflowBroker.addPluginWorkflows(pluginName, pluginWorkflows); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function integratePluginThemeData * @description Saves all of the plugin theme data to the D-data structure where theme data is stored. * @param {string} pluginName The name of the plugin who's theme data should be integrated with the haystacks theme data. * @param {object} pluginThemeData The JSON object that contains all of the theme data specific to this current plugin. * @return {boolean} True or False to indicate if the plugins theme data are successfully integrated or not. * @author Seth Hollingsead * @date 2022/10/25 */ async function integratePluginThemeData(pluginName, pluginThemeData) { let functionName = integratePluginThemeData.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); // pluginThemeData is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginThemeDataIs + JSON.stringify(pluginThemeData)); let returnData = false; returnData = await themeBroker.addThemeData(pluginThemeData, wrd.cPlugins + bas.cColon + pluginName); await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData)); await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function); return returnData; } /** * @function unloadPlugin * @description Unloads a plugin by removing all of the plugin data and meta-data from all of the * appropriate data structures in the D-data structure. * @param {string} pluginName The name of the plugin that should have all its data unloaded from the D-data structure. * @return {boolean} True or False to indicate if the plugin was unloaded successfully or not. * @author Seth Hollingsead * @date 2023/02/01 */ async function unloadPlugin(pluginName) { let functionName = unloadPlugin.name; await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function); // pluginName is: await loggers.consoleLog(namespacePrefix + functionName, msg.cpluginNameIs + pluginName); let returnData = false; let businessRulesRemovalSuccess = await ruleBroker.removePluginBusinessRules(pluginName); let commandsRemovalSuccess = await commandBroker.removePluginCommands(pluginName); let configurationDataRemovalSuccess = await dataBroker.removePluginConfigurationData(pluginName); let commandAliasesRemovalSuccess = await commandBroker.removePluginCommandAliases(pluginName); let workflowRemovalSuccess = await workflowBroker.removePluginWorkflows(pluginName); let themeDataRemovalSuccess = await themeBroker.removePluginThemeData(pluginName); let constantsValidationDataRemovalSuccess = await constantBroker.removePluginConstantsValidationData(pluginName); // Still need to remove the plugin from the