/* * Copyright (C) 2018-2019 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ import {Logger} from '@openstapps/logger'; import * as commander from 'commander'; import {existsSync, readFileSync, writeFileSync} from 'fs'; import {join, resolve} from 'path'; import { getProjectReflection, mkdirPromisified, readFilePromisified, toArray, } from './common'; import {pack} from './pack'; import { gatherRouteInformation, generateDocumentationForRoute, getNodeMetaInformationMap, } from './routes'; import {Converter, getValidatableTypesFromReflection} from './schema'; import {createDiagram, createDiagramFromString} from './uml/createDiagram'; import {readDefinitions} from './uml/readDefinitions'; import {UMLConfig} from './uml/umlConfig'; import {validateFiles, writeReport} from './validate'; // handle unhandled promise rejections process.on('unhandledRejection', async (error: Error) => { await Logger.error(error.message); Logger.info(error.stack); process.exit(1); }); commander .version(JSON.parse( readFileSync(resolve(__dirname, '..', 'package.json')) .toString(), ).version); commander .command('routes ') .action(async (relativeSrcPath, relativeMdPath) => { // get absolute paths const srcPath = resolve(relativeSrcPath); const mdPath = resolve(relativeMdPath); // get project reflection const projectReflection = getProjectReflection(srcPath); // get information about routes const routes = await gatherRouteInformation(projectReflection); // initialize markdown output let output = '# Routes\n\n'; // generate documentation for all routes routes.forEach((routeWithMetaInformation) => { output += generateDocumentationForRoute( routeWithMetaInformation, getNodeMetaInformationMap(projectReflection), ); }); // write documentation to file writeFileSync(mdPath, output); Logger.ok(`Route documentation written to ${mdPath}.`); }); commander .command('schema ') .action(async (relativeSrcPath, relativeSchemaPath) => { // get absolute paths const srcPath = resolve(relativeSrcPath); const schemaPath = resolve(relativeSchemaPath); // initialize new core converter const coreConverter = new Converter(srcPath); // get project reflection const projectReflection = getProjectReflection(srcPath); // get validatable types const validatableTypes = getValidatableTypesFromReflection( projectReflection, ); Logger.info(`Found ${validatableTypes.length} type(s) to generate schemas for.`); await mkdirPromisified(schemaPath, { recursive: true, }); Logger.info(`Trying to find a package.json for ${srcPath}.`); let path = srcPath; // TODO: this check should be less ugly! --- What is this doing anyway? // tslint:disable-next-line:no-magic-numbers while (!existsSync(join(path, 'package.json')) && path.length > 5) { path = resolve(path, '..'); } const corePackageJsonPath = join(path, 'package.json'); Logger.info(`Using ${corePackageJsonPath} to determine version for schemas.`); const buffer = await readFilePromisified(corePackageJsonPath); const corePackageJson = JSON.parse(buffer.toString()); const coreVersion = corePackageJson.version; Logger.log(`Using ${coreVersion} as version for schemas.`); // generate and write JSONSchema files for validatable types validatableTypes.forEach((type) => { const schema = coreConverter.getSchema(type, coreVersion); // tslint:disable-next-line:no-magic-numbers const stringifiedSchema = JSON.stringify(schema, null, 2); const file = join(schemaPath, `${type}.json`); // write schema to file writeFileSync(file, stringifiedSchema); Logger.info(`Generated schema for ${type} and saved to ${file}.`); }); Logger.ok(`Generated schemas for ${validatableTypes.length} type(s).`); }); commander .command('validate [reportPath]') .action(async (relativeSchemaPath, relativeTestPath, relativeReportPath) => { // get absolute paths const schemaPath = resolve(relativeSchemaPath); const testPath = resolve(relativeTestPath); const errorsPerFile = await validateFiles(schemaPath, testPath); let unexpected = false; for (const file in errorsPerFile) { if (!errorsPerFile.hasOwnProperty(file)) { continue; } unexpected = unexpected || errorsPerFile[file].some((error) => !error.expected); } if (typeof relativeReportPath !== 'undefined') { const reportPath = resolve(relativeReportPath); await writeReport(reportPath, errorsPerFile); } if (!unexpected) { Logger.ok('Successfully finished validation.'); } else { await Logger.error('Unexpected errors occurred during validation'); process.exit(1); } }); commander.command('pack').action(async () => { await pack(); }); commander .command('plantuml ') .option( '--definitions ', 'Shows these specific definitions (class or enum)', toArray, ) .option('--showAssociations', 'Shows associations of classes') .option( '--showInheritance', 'Shows extensions and implementations of classes', ) .option('--showEnumValues', 'Show enum values') .option('--showProperties', 'Show attributes') .option( '--showInheritedProperties', 'Shows inherited attributes, needs --showProperties', ) .option( '--showOptionalProperties', 'Shows optional attributes and relations, needs --showProperties', ) .option( '--excludeExternals', 'Exclude external definitions', ) .action(async (relativeSrcPath, plantumlserver, options) => { const plantUmlConfig: UMLConfig = { definitions: typeof options.definitions !== 'undefined' ? options.definitions : [], showAssociations: typeof options.showAssociations !== 'undefined' ? options.showAssociations : false, showEnumValues: typeof options.showEnumValues !== 'undefined' ? options.showEnumValues : false, showInheritance: typeof options.showInheritance !== 'undefined' ? options.showInheritance : false, showInheritedProperties: typeof options.showInheritedProperties !== 'undefined' ? options.showInheritedProperties : false, showOptionalProperties: typeof options.showOptionalProperties !== 'undefined' ? options.showOptionalProperties : false, showProperties: typeof options.showProperties !== 'undefined' ? options.showEnumValues : false, }; Logger.log(`PlantUML options: ${JSON.stringify(plantUmlConfig)}`); const srcPath = resolve(relativeSrcPath); const projectReflection = getProjectReflection(srcPath, !options.excludeExternals ? false : true); const definitions = readDefinitions(projectReflection); await createDiagram(definitions, plantUmlConfig, plantumlserver); }); commander .command('plantuml-file ') .action(async (file: string, plantumlserver: string) => { const fileContent = readFileSync(resolve(file)).toString(); await createDiagramFromString(fileContent, plantumlserver); }); commander.parse(process.argv); if (commander.args.length < 1) { commander.outputHelp(); process.exit(1); }