/** * @author Martin Karkowski * @email m.karkowski@zema.de * @create date 2021-01-08 15:16:51 * @modify date 2021-02-09 10:18:08 * @desc [description] */ import { ArgumentParser } from "argparse"; import { readFileSync } from "fs"; import * as handlebars from "handlebars"; import * as inquirer from "inquirer"; import { camelCase } from "lodash"; import { type } from "os"; import { join } from "path"; import { layerDefaultParameters, validLayers, } from "../communication/getLayer.nodejs"; import { createFile } from "../helpers/fileMethods"; import { IConfigFile } from "../loader/loadPackages"; import { getNopeLogger } from "../logger/getLogger"; import { LoggerLevels } from "../logger/nopeLogger"; // Define the Main Function. // This function is used as cli tool. export async function createService( additionalArguments: { help: string; type: "string" | "number"; name: string | string; defaultValue?: any; }[] = [] ) { // Flag, to determine the OS const runningInLinux = type() === "Linux"; const parser = new ArgumentParser({ version: "1.0.0", addHelp: true, description: "Command Line interface, which enables creating services.", }); for (const arg of additionalArguments) { parser.addArgument(arg.name, { help: arg.help, defaultValue: arg.defaultValue, type: arg.type, }); } parser.addArgument(["-f", "--file"], { help: "File containing containing the package definitions.", defaultValue: "./config/settings.json", type: "string", dest: "file", }); parser.addArgument(["-o", "--outputDir"], { help: "File containing containing the package definitions.", defaultValue: "./services/", type: "string", dest: "outputDir", }); parser.addArgument(["--noFuncs"], { help: "Prevents using Functions", action: "storeTrue", dest: "noFuncs", }); parser.addArgument(["--runInstances"], { help: "Run instances provided in a packages.", action: "storeTrue", dest: "runInstances", }); parser.addArgument(["--runEveryService"], { help: "Run instances provided in a packages.", action: "storeTrue", dest: "runEveryService", }); parser.addArgument(["--runExtra"], { help: "Runs every instance in an extra Service", action: "storeTrue", dest: "runExtra", }); parser.addArgument(["--layer"], { help: "Defaultly selects layer to use.", defaultValue: "not-provided", type: "string", dest: "layer", }); parser.addArgument(["--params"], { help: "Paramas for the Channel, to connect to. The Following Defaults are used: \n" + JSON.stringify(layerDefaultParameters, undefined, 4), defaultValue: "not-provided", type: "string", dest: "params", }); parser.addArgument(["--log"], { help: 'Specify the Logger Level of the Services. Defaults to "info". Valid values are: ' + LoggerLevels.join(", "), defaultValue: "not-provided", type: "string", dest: "logLevel", }); const args = parser.parseArgs(); // Define a Logger const logger = getNopeLogger("Setup-CLI"); try { logger.info("Reading config File"); const data: IConfigFile = JSON.parse( readFileSync(args.file).toString("utf-8") ); const config: IConfigFile = { functions: [], packages: [], }; const modeSelection = []; const argsToUse: any = {}; if (!args.noFuncs) { modeSelection.push({ type: "confirm", name: "useFunction", message: "Do you want to share Functions", default: false, }); } else { argsToUse.useFunction = false; } if (!args.runInstances) { modeSelection.push({ type: "confirm", name: "detailPackages", message: "Do you want to run instances provided in a package?", default: true, }); } else { argsToUse.detailPackages = false; } if (!args.runEveryService) { modeSelection.push({ type: "confirm", name: "detailInstances", message: "Do you want to detail, which instance should be hosted as Service?", default: true, }); } else { argsToUse.detailInstances = false; } if (!args.runExtra) { modeSelection.push({ type: "confirm", name: "runExtra", message: "Do you want to run every selected option as single Service?", default: true, }); } else { argsToUse.runExtra = true; } if (args.logLevel === "not-provided") { modeSelection.push({ type: "list", name: "logLevel", message: "Select the Level of the Logger, to use.", choices: LoggerLevels, }); } else { argsToUse.logLevel = args.logLevel; } if (args.layer === "not-provided") { modeSelection.push({ type: "list", name: "layer", message: "Select the Communication Layer to use", choices: Object.getOwnPropertyNames(validLayers), }); } else { argsToUse.layer = args.layer; } let result: any = {}; if (modeSelection.length > 0) { result = await inquirer.prompt(modeSelection); } result = Object.assign({}, result, argsToUse); let params: string = null; if (result.layer !== "event" && args.params === "not-provided") { const resultOfQuestion = ( await inquirer.prompt({ type: "confirm", name: "result", message: "Do you want to add custom Layer Parameters", default: true, }) ).result; if (resultOfQuestion) { while (params == null) { params = ( await inquirer.prompt({ type: "input", name: "result", message: "Please enter the parameters.", default: true, }) ).result; } } else { params = layerDefaultParameters[args.channel]; } } else { try { try { // We try to parse the data. params = JSON.parse(args.params); } catch (e) { 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); return; } } if (result.useFunction) { config.functions = ( await inquirer.prompt([ { type: "checkbox", name: "functions", message: "Select/Deselect Functions, that should be hosted", choices: data.functions.map((func) => { return { name: func.path, value: func, checked: false, }; }), }, ]) ).functions; } // Defaultly assign the default-Packages. config.packages = data.packages; if (result.detailPackages) { // Determine the Packages which should be hosted. config.packages = ( await inquirer.prompt([ { type: "checkbox", name: "packages", message: "Select/Deselect Packages, that should be hosted:", choices: data.packages.map((p) => { return { name: p.nameOfPackage, value: p, checked: true, }; }), }, ]) ).packages; } // Now, that we know, which package we should use, let us "detail" the instances we should use. if (result.detailInstances) { // Iterate over the Pacakges for (const p of config.packages) { // Determine the Instances to keep: if (p.defaultInstances.length > 0) { p.defaultInstances = ( await inquirer.prompt([ { type: "checkbox", name: "instances", message: "Select/Deselect Instances, that should be hosted:", choices: p.defaultInstances.map((instance) => { return { name: instance.options.identifier, value: instance, checked: true, }; }), }, ]) ).instances; } } } const serviceTemplates = runningInLinux ? [ { name: "nopeService.service", render: handlebars.compile( readFileSync( join( __dirname, "..", "..", "..", "lib", "templates", "linux.service.handlebars" ) ).toString("utf-8") ), }, ] : [ { name: "index.js", render: handlebars.compile( readFileSync( join( __dirname, "..", "..", "..", "lib", "templates", "index.js.handlebars" ) ).toString("utf-8") ), }, { name: "service.js", render: handlebars.compile( readFileSync( join( __dirname, "..", "..", "..", "lib", "templates", "service.js.handlebars" ) ).toString("utf-8") ), }, { name: "00-install.bat", render: handlebars.compile(`set DIR=%~dp0 cd "%DIR%" cd .. cd .. cd .. node {{{pathToFolder}}}\\service.js -m install `), }, { name: "01-start.bat", render: handlebars.compile(`set DIR=%~dp0 cd "%DIR%" cd .. cd .. cd .. node {{{pathToFolder}}}\\service.js -m start `), }, { name: "02-restart.bat", render: handlebars.compile(`set DIR=%~dp0 cd "%DIR%" cd .. cd .. cd .. node {{{pathToFolder}}}\\service.js -m restart `), }, { name: "03-uninstall.bat", render: handlebars.compile(`set DIR=%~dp0 cd "%DIR%" cd .. cd .. cd .. node {{{pathToFolder}}}\\service.js -m uninstall`), }, { name: "00-manual_starter.bat", render: handlebars.compile(`set DIR=%~dp0 cd "%DIR%" cd .. cd .. cd .. node {{{pathToFolder}}}\\index.js`), }, ]; const storeConfig = async ( config: IConfigFile, name: string, path: string ) => { // Create the Settings File: await createFile( join(path, "settings.json"), JSON.stringify(config, undefined, 4) ); // Write the Templates for (const template of serviceTemplates) { await createFile( join(path, template.name), template.render({ name, path, params, layer: result.layer, pathToFile: join(path, template.name), pathToFolder: join(path), logLevel: result.logLevel, }) ); } logger.info("Create Service at", path); }; if (result.runExtra) { const promises: Promise[] = []; for (const _package of config.packages) { for (const _instance of _package.defaultInstances) { const autostart = {}; if (_package.autostart[_instance.options.identifier]) { autostart[_instance.options.identifier] = _package.autostart[_instance.options.identifier]; } const _config: IConfigFile = { functions: [], packages: [ { autostart, defaultInstances: [_instance], nameOfPackage: _package.nameOfPackage, path: _package.path, }, ], }; promises.push( storeConfig( _config, camelCase(_package.nameOfPackage), join( args.outputDir, camelCase(_package.nameOfPackage), camelCase(_instance.options.identifier) ) ) ); } } await Promise.all(promises); } else { // Create a Syntetic Package, which will be created. await storeConfig( config, "NopeService", join(args.outputDir, "combinedService") ); } } catch (error) { logger.error("Failed generating the Config"); logger.error(error); } } // If requested As Main => Perform the Operation. if (require.main === module) { createService().catch((e) => console.error(e)); }