nope/lib/cli/createService.ts
2021-05-21 19:37:32 +02:00

441 lines
11 KiB
TypeScript

/**
* @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 { validLayers } from "../communication/getLayer";
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(["--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.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;
}
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;
}
let result: any = {};
if (modeSelection.length > 0) {
result = await inquirer.prompt(modeSelection);
}
result = Object.assign({}, result, argsToUse);
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:
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: "01-install.bat",
render: handlebars.compile(`set DIR=%~dp0
cd "%DIR%"
cd ..
cd ..
cd ..
node {{{pathToFolder}}}\\service.js -m install
`)
},
{
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,
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<any>[] = [];
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));
}