rsi.cli
Version:
The swiss army knife for RSI related tasks.
256 lines (246 loc) • 13 kB
text/typescript
import { Logger } from "./utils/Logger";
import commandLineArgs from 'command-line-args';
import { printHelp, getVersion, service } from "./";
import * as vfs from "vinyl-fs";
import * as path from "path";
import { existsSync } from "fs";
import * as watch from "watch";
import * as inquirer from "inquirer";
import fs from "fs";
import { SERVICE_NAME_REGEX } from "./utils/regex";
let bundleDepenencies = {};
/* first - parse the main command */
const mainOptions = commandLineArgs([{ name: 'command', defaultOption: true }, { name: 'sourceFolder', type: String }], { stopAtFirstUnknown: true })
const argv = mainOptions._unknown || [];
/* second - parse the command options */
if (mainOptions.command == 'bundle' || mainOptions.command == 'service') {
const serviceCommands = commandLineArgs([{ name: 'command', defaultOption: true }], { argv, stopAtFirstUnknown: true });
const serviceArgv = serviceCommands._unknown || [];
switch (serviceCommands.command) {
case 'init':
optioninitialise(serviceArgv);
break;
case 'validate':
optionValidate(serviceArgv, mainOptions.command === 'bundle');
break;
case 'release':
optionRelease();
break;
case 'render':
optionRender(serviceArgv, mainOptions.command === 'bundle');
break;
case 'markdown':
case 'document':
optiondocument(serviceArgv, mainOptions.command === 'bundle', serviceCommands.command === "document");
break;
default:
service.printHelp();
break;
}
} else {
interface IOptions { version: Boolean }
const Definitions = [{ name: 'version', alias: 'v', type: Boolean }];
try {
const options: IOptions = commandLineArgs(Definitions, { argv }) as IOptions;
(options.version) ? console.log(getVersion()) : printHelp();
} catch (error) {
printHelp();
}
}
// initialises a new repository
function optioninitialise(serviceArgv) {
const serviceInitOpts = commandLineArgs([
{ name: 'author', alias: 'a', type: String },
{ name: 'description', alias: 'd', type: String },
{ name: 'email', alias: 'e', type: String },
{ name: 'name', alias: 'n', type: String },
{ name: 'output', alias: 'o', type: String, defaultValue: "./" },
{ name: 'version', alias: 'v', type: String },
], { argv: serviceArgv });
const questions = [
{ type: 'input', name: 'name', message: 'What is the service name?', default: serviceInitOpts.name, validate: (inp: string) => {
if (!inp) return false;
const isEmail = inp.match(/^\w+$/);
return (isEmail !== null) || "not a valid name, only characters and numbers allowed";
}
},
{ type: 'input', name: 'version', message: 'What is the service version?', default: serviceInitOpts.version || "0.0.1" },
{ type: 'input', name: 'description', message: 'Give a short desciption of your service?', default: serviceInitOpts.description || "some service" },
{ type: 'input', name: 'author', message: 'Who authored the service?', default: serviceInitOpts.author || process.env.LOGNAME },
{ type: 'input', name: 'email', message: 'What is the authors email?', default: serviceInitOpts.email, validate: (inp: string) => {
if (!inp) return false;
const isEmail = inp.match(/^\w+@\w+\.\w+$/);
return (isEmail !== null) || "not a valid email address";
}
},
{ type: 'input', name: 'output', message: 'output directory', default: serviceInitOpts.output || "./myNewService" }
];
if (!(serviceInitOpts.author && serviceInitOpts.email && serviceInitOpts.name && serviceInitOpts.output)) Logger.info("Shortcut usage: $ rsi service init --name <servicename> --author \"<username>\" --email <email> --output <outputfolder>\n\r\n\r");
inquirer.prompt(questions as inquirer.Questions).then((answers) => {
console.log(answers)
service.init(answers as service.serviceMeta).pipe(vfs.dest(answers.output));
});
}
//Renders documentation(markdown and html) based on schema
function optiondocument(serviceArgv, bundle, html) {
const serviceDocumentOpts = commandLineArgs([
{ name: 'sourceFolder', alias: 's', type: String },
{ name: 'output', alias: 'o', type: String },
{ name: 'watch', alias: 'w', type: Boolean }
], { argv: serviceArgv });
let pathsObj = {};
let packageFile = JSON.parse(fs.readFileSync(path.join(serviceDocumentOpts.sourceFolder, "./package.json"), 'utf-8'));
let packageInfo = { name: packageFile.name, version: packageFile.version, description: packageFile.description };
if (serviceDocumentOpts.sourceFolder && serviceDocumentOpts.output) {
if (bundle) {
let temp = createBundleObj(serviceDocumentOpts.sourceFolder);
pathsObj['changelog'] = ((existsSync(path.join(serviceDocumentOpts.sourceFolder, "./changelog.md"))) ? path.join(serviceDocumentOpts.sourceFolder, "./changelog.md") : undefined);
for (let def in temp) pathsObj[def] = { serviceDefinition: temp[def].definition, package: temp[def].package, changelog: temp[def].changelog };
} else {
pathsObj[packageInfo.name] = {
serviceDefinition: ((existsSync(path.join(serviceDocumentOpts.sourceFolder, "./src/serviceDefinition.json"))) ? path.join(serviceDocumentOpts.sourceFolder, "./src/serviceDefinition.json") : path.join(serviceDocumentOpts.sourceFolder, "./src/schema.json")),
package: path.join(serviceDocumentOpts.sourceFolder, "./package.json"),
changelog: ((existsSync(path.join(serviceDocumentOpts.sourceFolder, "./changelog.md"))) ? path.join(serviceDocumentOpts.sourceFolder, "./changelog.md") : undefined)
}
}
service.renderDoc(pathsObj, packageInfo, html).then(data => {
data.pipe(vfs.dest(serviceDocumentOpts.output));
Logger.success("Documented scuccessfully")
});//.catch(err => Logger.error("Documentation failed:", JSON.stringify(err, undefined, 2)));
for (let x in pathsObj) {
// check if we are watching
if (serviceDocumentOpts.watch) {
watch.createMonitor(path.join(serviceDocumentOpts.sourceFolder, "./"), (monitor) => {
monitor.files[pathsObj[x].serviceDefinition, pathsObj[x].changelog, pathsObj[x].package]
Logger.info(`Watching ${serviceDocumentOpts.sourceFolder}`);
monitor.on("changed", (where) => {
// Handle file changes
Logger.info(`Change detected in ${where}.`);
service.renderDoc(pathsObj, packageInfo, html).then(data => {
data.pipe(vfs.dest(serviceDocumentOpts.output));
Logger.success("Documented scuccessfully");
}).catch(err => Logger.error("Documentation failed:", JSON.stringify(err, undefined, 2)));
});
})
}
}
} else {
Logger.error("One or more parameter missing");
Logger.info("Usage: $ rsi service document --sourceFolder <pathToServiceFolder> --output <pathToOutputFolder>");
}
}
//Prepare service for release
function optionRelease() {
Logger.error("`release` command currently not supported");
}
//Render UML from schema
function optionRender(serviceArgv, bundle) {
const serviceRenderOpts = commandLineArgs([
{ name: 'sourceFolder', alias: 's', type: String },
{ name: 'output', alias: 'o', type: String },
{ name: 'watch', alias: 'w', type: Boolean }
], { argv: serviceArgv });
let pathObjs = {};
if (serviceRenderOpts.sourceFolder && serviceRenderOpts.output) {
if (bundle) {
let temp = createBundleObj(serviceRenderOpts.sourceFolder)
for (let def in temp) pathObjs[def] = temp[def].definition;
} else {
pathObjs[JSON.parse(fs.readFileSync(path.join(serviceRenderOpts.sourceFolder, "./package.json"), 'utf-8')).name] = ((existsSync(path.join(serviceRenderOpts.sourceFolder, "./src/serviceDefinition.json"))) ? path.join(serviceRenderOpts.sourceFolder, "./src/serviceDefinition.json") : path.join(serviceRenderOpts.sourceFolder, "./src/schema.json"));
}
// const paths = { schema: path.join(serviceRenderOpts.sourceFolder, "./src/schema.json") }
// render once
// service.parseSchemas([paths.schema]).then(payload => {
service.parseSchemas(pathObjs).then(payload => {
service.render(payload).then(data => {
data.pipe(vfs.dest(serviceRenderOpts.output)).on("data", (data) => Logger.success(`Rendered plantuml to ${path.join(serviceRenderOpts.output, data.basename)}.`));
}).catch(err => Logger.error("Rendering failed:", JSON.stringify(err, undefined, 2)));
});
for (let x in pathObjs) {
// check if we are watching
if (serviceRenderOpts.watch) {
watch.createMonitor(path.join(serviceRenderOpts.sourceFolder, "./"), (monitor) => {
monitor.files[pathObjs[x].serviceDefinition]
Logger.info(`Watching ${serviceRenderOpts.sourceFolder}`);
monitor.on("changed", (where) => {
// Handle file changes
Logger.info(`Change detected in ${where}.`);
service.parseSchemas([pathObjs[x].serviceDefinition]).then(payload => {
service.render(payload).then(data => {
data.pipe(vfs.dest(serviceRenderOpts.output)).on("data", (data) => Logger.success(`Rendered plantuml to ${path.join(serviceRenderOpts.output, data.basename)}.`));
}).catch(err => Logger.error("Rendering failed:", JSON.stringify(err, undefined, 2)));
});
});
})
}
}
} else {
Logger.error("One or more parameter missing");
Logger.info("Usage:\n$ rsi service render --sourceFolder <pathToServiceFolder> --output <pathToOutputFolder>");
}
}
//Validates the uploaded Service Definitions
function optionValidate(serviceArgv, bundle) {
const serviceValidateOpts = commandLineArgs([
{ name: 'sourceFolder', alias: 's', type: String },
{ name: 'watch', alias: 'w', type: Boolean }
], { argv: serviceArgv });
let pathObjs = {};
if (serviceValidateOpts.sourceFolder) {
if (bundle) {
let temp = createBundleObj(serviceValidateOpts.sourceFolder)
for (let def in temp) pathObjs[def] = temp[def].definition;
} else {
pathObjs['CurrentService'] = ((existsSync(path.join(serviceValidateOpts.sourceFolder, "./src/serviceDefinition.json"))) ? path.join(serviceValidateOpts.sourceFolder, "./src/serviceDefinition.json") : path.join(serviceValidateOpts.sourceFolder, "./src/schema.json"));
}
for (let url in pathObjs) {
service.validate(pathObjs[url]).then(data => Logger.success("Service Definition valid - " + url)).catch(err => Logger.error("Validation failed:", JSON.stringify(err, undefined, 2)));
// check if we are watching
if (serviceValidateOpts.watch) {
watch.createMonitor(path.join(serviceValidateOpts.sourceFolder, "./"), (monitor) => {
monitor.files[pathObjs[url]]
Logger.info(`Watching ${serviceValidateOpts.sourceFolder}`);
monitor.on("changed", (where) => {
// Handle file changes
Logger.info(`Change detected in ${where}.`);
service.validate(pathObjs[url]).then(data => Logger.success("Service Definition valid - " + url)).catch(err => Logger.error("Validation failed:", JSON.stringify(err, undefined, 2)));
});
})
}
}
} else {
Logger.error("One or more parameter missing");
console.log("Usage: $ rsi service validate --sourceFolder <pathToServiceFolder>");
}
}
//Gets the path ways to the schema and package files for the dependencies
function createBundleObj(paths) {
const dependencies = JSON.parse(fs.readFileSync(path.join(paths, "./package.json"), 'utf-8')).dependencies;
for (let def of filterDependencies(dependencies)) {
bundleDepenencies[def] = {
// definition: JSON.parse(fs.readFileSync(path.join(serviceValidateOpts.sourceFolder, '/node_modules/' + def + '/src/schema.json'), "utf-8")),
// package: JSON.parse(fs.readFileSync(path.join(serviceValidateOpts.sourceFolder, '/node_modules/' + def + '/package.json'), "utf-8"))
definition: ((existsSync(path.join(paths, `/node_modules/${def}/src/serviceDefinition.json`))) ? path.join(paths, `/node_modules/${def}/src/serviceDefinition.json`) : path.join(paths, `/node_modules/${def}/src/schema.json`)),
package: path.join(paths, `/node_modules/${def}/package.json`),
// changelog: path.join(paths, `/node_modules/${def}/CHANGELOG.md`)
changelog: ((existsSync(path.join(paths, `/node_modules/${def}/CHANGELOG.md`))) ? path.join(paths, `/node_modules/${def}/CHANGELOG.md`) : undefined)
};
}
return bundleDepenencies;
}
//Checks dependencies are rsi
export function filterDependencies(obj) {
if (obj !== undefined) {
const keys = Object.keys(obj).filter(dep => {
return dep.match(SERVICE_NAME_REGEX);
});
if (keys.length === 0) {
throw new Error('no RSI or VIWI services found in dependencies!');
} else {
return keys;
}
} else {
throw new Error('undefined argument!');
}
}