/** * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2020-11-11 13:27:58 * @modify date 2021-10-19 09:15:38 * @desc [description] * */ import { ArgumentParser } from "argparse"; import { readFile } from "fs/promises"; import "reflect-metadata"; import { getLayer, layerDefaultParameters, validLayerOrMirror, validLayers, } from "../communication/getLayer.nodejs"; import { sleep } from "../helpers/async"; import { deepClone } from "../helpers/objectMethods"; import { getPackageLoader } from "../loader/getPackageLoader.browser"; import { loadFunctions, loadPackages } from "../loader/loadPackages"; import { generateLogfilePath, useLogFile } from "../logger/fileLogging"; import { getNopeLogger } from "../logger/getLogger"; import { LoggerLevel, LoggerLevels } from "../logger/nopeLogger"; import { setGlobalLoggerLevel } from "../logger/setGlobalLoggerLevel"; import { INopeINopeConnectivityTimeOptions, ValidDefaultSelectors, } from "../types/nope"; import { INopePackageLoader } from "../types/nope/nopePackageLoader.interface"; import { NOPELOGO } from "./renderNope"; export interface RunArgs { file: string; channel: validLayerOrMirror; params: string; // Flag to prevent loading the configuration skipLoadingConfig: boolean; // Level of the logger. log: LoggerLevel; // The Enable Singletons. Defaults to true singleton: boolean; // The default-selector to select the service providers defaultSelector: ValidDefaultSelectors; // The default-selector to select the service providers dispatcherLogLevel: LoggerLevel; // The default-selector to select the service providers communicationLogLevel: LoggerLevel; // Enable File-logging: logToFile: boolean; // Delay to wait for system beeing ready. delay: number; // Flag to force using the selectors. Might have a performance inpact. forceUsingSelectors: boolean; // Define the Timingsparameter timings: Partial; // Forces the Selectors forceSelectors: boolean; } export const DEFAULT_SETTINGS: RunArgs = { file: "./config/settings.json", channel: "event", skipLoadingConfig: false, params: "not-provided", log: "debug", singleton: true, dispatcherLogLevel: "info", communicationLogLevel: "info", delay: 2, forceSelectors: false, timings: {}, defaultSelector: "first", forceUsingSelectors: false, logToFile: false, }; /** * Helper Function to Read-In the Arguments used by the * cli-tool * * @return {*} */ export async function readInArgs( additionalArguments: { help: string; type: "string" | "number"; name: string | string; defaultValue?: any; }[] = [], forcedArgs: Partial = {} ): Promise { const parser = new ArgumentParser({ // version: "1.0.0", add_help: true, description: "Command Line interface, determines the available Packages.", }); for (const arg of additionalArguments) { parser.add_argument(arg.name, { help: arg.help, default: arg.defaultValue, type: arg.type, }); } parser.add_argument("-f", "--file", { help: "File containing containing the package definitions.", default: "./config/settings.json", type: "str", dest: "file", }); parser.add_argument("-c", "--channel", { help: "The Communication Channel, which should be used. Possible Values are: " + // Display all Options: Object.getOwnPropertyNames(validLayers) .map((item) => '"' + item + '"') .join(", "), default: "event", type: "str", dest: "channel", }); parser.add_argument("-p", "--params", { help: "Paramas for the Channel, to connect to. The Following Defaults are used: \n" + JSON.stringify(layerDefaultParameters, undefined, 4), default: "not-provided", type: "str", dest: "params", }); parser.add_argument("-s", "--skip-loading-config", { help: "Flag to prevent loading the elements defined in the settings.json.", action: "append", nargs: "?", dest: "skipLoadingConfig", }); parser.add_argument("--default-selector", { help: "The default-selector to select the service providers. Possible Values are: " + // Display all Options: Object.getOwnPropertyNames(ValidDefaultSelectors) .map((item) => '"' + item + '"') .join(", "), default: "first", type: "str", dest: "defaultSelector", }); parser.add_argument("--log-to-file", { help: "Log will be stored in a logfile.", action: "append", nargs: "?", dest: "logToFile", }); parser.add_argument("-l", "--log", { help: 'Specify the Logger Level. Defaults to "info". Valid values are: ' + LoggerLevels.join(", "), default: "debug", type: "str", dest: "log", }); parser.add_argument("--dispatcher-log", { help: 'Specify the Logger Level of the Dispatcher. Defaults to "info". Valid values are: ' + LoggerLevels.join(", "), default: "info", type: "str", dest: "dispatcherLogLevel", }); parser.add_argument("--force-selector", { help: "Forces to use the Selector. Otherwise a smart approach is used, which only enables them if required.", action: "append", nargs: "?", dest: "forceUsingSelectors", }); parser.add_argument("-d", "--delay", { help: 'Adds an delay, which will be waited, after the system connected. Parmeter is provided in [s]. Defaults to "2"', default: 2, type: "float", dest: "delay", }); parser.add_argument("--communication-log", { help: 'Specify the Logger Level of the Communication. Defaults to "info". Valid values are: ' + LoggerLevels.join(", "), default: "info", type: "str", dest: "communicationLogLevel", }); const args: RunArgs = parser.parse_args(); if (args.params === "not-provided") { delete args.params; } args.skipLoadingConfig = Array.isArray(args.skipLoadingConfig); args.logToFile = Array.isArray(args.logToFile); args.forceUsingSelectors = Array.isArray(args.forceUsingSelectors); return Object.assign(args, forcedArgs); } // Define the Main Function. // This function is used as cli tool. export async function runNopeBackend( _args: Partial = {} ): Promise { let opts: { params: string | number; }; // Default Settings const _defaultSettings: RunArgs = deepClone(DEFAULT_SETTINGS); const args = Object.assign(_defaultSettings, _args); try { // Try to read in the default config file. opts = JSON.parse( await readFile("./nopeconfig.json", { encoding: "utf-8", }) ); } catch (error) { opts = {} as any; } if (args.logToFile) { const fileName = generateLogfilePath("run"); useLogFile(fileName, 10); } if (LoggerLevels.includes(args.log)) { setGlobalLoggerLevel(args.log); } // Define a Logger const logger = getNopeLogger("starter"); if (!Object.getOwnPropertyNames(validLayers).includes(args.channel)) { logger.error( "Invalid Channel. Please use the following values. " + Object.getOwnPropertyNames(validLayers) .map((item) => '"' + item + '"') .join(", ") ); const error = Error( "Invalid Channel. Please use the following values. " + Object.getOwnPropertyNames(validLayers) .map((item) => '"' + item + '"') .join(", ") ); logger.error(error); throw error; } // Assign the Default Setting for the Channel. opts.params = layerDefaultParameters[args.channel]; if (args.params != "not-provided") { try { try { // We try to parse the data. opts.params = JSON.parse(args.params); } catch (e) { opts.params = JSON.parse('"' + args.params + '"'); } } catch (e) { logger.error( "Unable to parse the Parameters for the channel. Please use valid JSON!" ); logger.error(args.params[0]); logger.error(e); throw e; } } // If required load all Packages. if (!args.skipLoadingConfig) { // Try to load the Modules. try { logger.info("loading Functions"); await loadFunctions(args.file); } catch (e) { logger.error("Unable to load the Packages defined in " + args.file); logger.error("Returning without config. "); args.skipLoadingConfig = true; } } let loader: INopePackageLoader; try { loader = getPackageLoader( { communicator: getLayer( args.channel, opts.params, args.communicationLogLevel ), logger: getNopeLogger("dispatcher", args.dispatcherLogLevel), defaultSelector: args.defaultSelector, forceUsingSelectors: args.forceUsingSelectors, }, _args.singleton ); } catch (e) { getNopeLogger("cli", "info").error("failed to load the Packages", e); } // If required load all Packages. if (!args.skipLoadingConfig) { // Try to load the Modules. if (args.delay > 0) { logger.info(`Waiting ${args.delay} [s] to get all information.`); await sleep(args.delay * 1000); } try { logger.info("loading Packages"); await loadPackages(loader, args.file, args.delay); } catch (e) { logger.error("Unable to load the Packages defined in " + args.file); } } const _dispose = () => { // We should close the Process: logger.warn("received 'ctrl+c'. Shutting down the Instances"); loader.dispatcher.dispose().finally(process.exit); }; process.on("SIGINT", _dispose); process.on("SIGTERM", _dispose); process.on("exit", () => { logger.info("Completed. Goodbye"); }); return loader; } /** * Main Function. * * @export */ export async function run( additionalArguments: { help: string; type: "string" | "number"; name: string | string; defaultValue?: any; }[] = [], forcedArgs: Partial = {}, quite = false ) { // Subscribe to unhandled Reactions. process.on("unhandledRejection", (reason, p) => { console.log("Unhandled Rejection at: Promise", p, "reason:", reason); console.error(reason); // application specific logging, throwing an error, or other logic here // Forward the error throw reason; }); if (!quite) { console.log(NOPELOGO); console.log("\n\n"); } const args = await readInArgs(additionalArguments, forcedArgs); return await runNopeBackend(args); } export default run; // If requested As Main => Perform the Operation. if (require.main === module) { run(); }