mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-10 03:32:52 +00:00
refactor: adjust code to new configuration
This commit is contained in:
27
src/cli.ts
27
src/cli.ts
@@ -23,14 +23,17 @@ import {Converter, getValidatableTypesFromReflection} from './schema';
|
||||
import {validateFiles, writeReport} from './validate';
|
||||
|
||||
// handle unhandled promise rejections
|
||||
process.on('unhandledRejection', (error: Error) => {
|
||||
Logger.error(error.message);
|
||||
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);
|
||||
.version(JSON.parse(
|
||||
readFileSync(resolve(__dirname, '..', 'package.json'))
|
||||
.toString(),
|
||||
).version);
|
||||
|
||||
commander
|
||||
.command('routes <srcPath> <mdPath>')
|
||||
@@ -46,7 +49,7 @@ commander
|
||||
const routes = await gatherRouteInformation(projectReflection);
|
||||
|
||||
// initialize markdown output
|
||||
let output: string = '# Routes\n\n';
|
||||
let output = '# Routes\n\n';
|
||||
|
||||
// generate documentation for all routes
|
||||
routes.forEach((routeWithMetaInformation) => {
|
||||
@@ -84,7 +87,8 @@ commander
|
||||
Logger.info(`Trying to find a package.json for ${srcPath}.`);
|
||||
|
||||
let path = srcPath;
|
||||
// TODO: this check should be less ugly!
|
||||
// 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, '..');
|
||||
}
|
||||
@@ -103,9 +107,10 @@ commander
|
||||
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');
|
||||
const file = join(schemaPath, `${type}.json`);
|
||||
|
||||
// write schema to file
|
||||
writeFileSync(file, stringifiedSchema);
|
||||
@@ -126,9 +131,13 @@ commander
|
||||
const errorsPerFile = await validateFiles(schemaPath, testPath);
|
||||
|
||||
let unexpected = false;
|
||||
Object.keys(errorsPerFile).forEach((file) => {
|
||||
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);
|
||||
@@ -138,7 +147,7 @@ commander
|
||||
if (!unexpected) {
|
||||
Logger.ok('Successfully finished validation.');
|
||||
} else {
|
||||
Logger.error('Unexpected errors occurred during validation');
|
||||
await Logger.error('Unexpected errors occurred during validation');
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -53,14 +53,35 @@ export interface RouteWithMetaInformation {
|
||||
* Instance of the route
|
||||
*/
|
||||
route: {
|
||||
/**
|
||||
* Error names of a route
|
||||
*/
|
||||
errorNames: Error[];
|
||||
/**
|
||||
* Method of the route
|
||||
*/
|
||||
method: string;
|
||||
/**
|
||||
* Obligatory parameters of the route
|
||||
*/
|
||||
obligatoryParameters: {
|
||||
[k: string]: string;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Name of the request body
|
||||
*/
|
||||
requestBodyName: string;
|
||||
/**
|
||||
* Name of the response body
|
||||
*/
|
||||
responseBodyName: string;
|
||||
/**
|
||||
* Status code on success
|
||||
*/
|
||||
statusCodeSuccess: number;
|
||||
/**
|
||||
* URL fragment
|
||||
*/
|
||||
urlFragment: string;
|
||||
};
|
||||
}
|
||||
@@ -93,13 +114,21 @@ export interface NodesWithMetaInformation {
|
||||
* A schema with definitions
|
||||
*/
|
||||
interface SchemaWithDefinitions extends JSONSchema {
|
||||
definitions: { [name: string]: Definition };
|
||||
/**
|
||||
* Definitions of the schema
|
||||
*/
|
||||
definitions: { [name: string]: Definition; };
|
||||
}
|
||||
|
||||
/**
|
||||
* An expectable error
|
||||
*/
|
||||
export type ExpectableValidationError = ValidationError & { expected: boolean };
|
||||
export interface ExpectableValidationError extends ValidationError {
|
||||
/**
|
||||
* Whether or not the error is expected
|
||||
*/
|
||||
expected: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of files and their expectable validation errors
|
||||
@@ -162,7 +191,9 @@ export function getTsconfigPath(startPath: string): string {
|
||||
let tsconfigPath = startPath;
|
||||
|
||||
// see https://stackoverflow.com/questions/9652043/identifying-the-file-system-root-with-node-js
|
||||
const root = (platform() === 'win32') ? process.cwd().split(sep)[0] : '/';
|
||||
const root = (platform() === 'win32') ? process
|
||||
.cwd()
|
||||
.split(sep)[0] : '/';
|
||||
|
||||
// repeat until a tsconfig.json is found
|
||||
while (!existsSync(join(tsconfigPath, 'tsconfig.json'))) {
|
||||
|
||||
137
src/pack.ts
137
src/pack.ts
@@ -72,18 +72,15 @@ async function packCliJs(): Promise<void> {
|
||||
|
||||
let internalRequire: string | null = null;
|
||||
|
||||
const adjustedContent = '#!/usr/bin/env node\n\n' + content
|
||||
const adjustedContent = content
|
||||
.split('\n')
|
||||
.map((line, lineNumber) => {
|
||||
|
||||
// check for exports (cli.js is not allowed to export anything)
|
||||
if (Array.isArray(line.match(/^\s*((exports)|(module\.exports))/))) {
|
||||
throw new Error(
|
||||
'Line ' +
|
||||
lineNumber +
|
||||
' in cli.js has a reference to the exports object. cli.js is not for exporting. Line was: "'
|
||||
+ line
|
||||
+ '"',
|
||||
`Line '${lineNumber}' in 'cli.js' exports something. cli.js is not for exporting. Line was:
|
||||
${line}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -92,7 +89,9 @@ async function packCliJs(): Promise<void> {
|
||||
const match = line.match(/^(\s*)(const|var) ([a-z0-9_]*) = require\(("[^"]+"|'[^']+')\);$/i);
|
||||
|
||||
if (match !== null) {
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
const importedName = match[3];
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
const moduleName = match[4].substring(1, match[4].length - 1);
|
||||
|
||||
// if it begins with '.' and not ends with json
|
||||
@@ -100,19 +99,23 @@ async function packCliJs(): Promise<void> {
|
||||
|
||||
// is the first internal require
|
||||
if (internalRequire !== null) {
|
||||
return 'const ' + importedName + ' = ' + internalRequire + ';';
|
||||
return `const ${importedName} = ${internalRequire};`;
|
||||
}
|
||||
|
||||
// only the first import needs a require
|
||||
internalRequire = importedName;
|
||||
return 'const ' + importedName + ' = require("./index");';
|
||||
|
||||
return `const ${importedName} = require("./index");`;
|
||||
}
|
||||
}
|
||||
|
||||
return line;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return await writeFilePromisified(path, adjustedContent);
|
||||
return writeFilePromisified(path, `#!/usr/bin/env node
|
||||
|
||||
${adjustedContent}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,11 +130,11 @@ async function getAllTypeDefinitions(): Promise<string[]> {
|
||||
],
|
||||
});
|
||||
|
||||
const promises = fileNames.map((fileName) => {
|
||||
const promises = fileNames.map(async (fileName) => {
|
||||
return readFilePromisified(fileName, 'utf8');
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +150,7 @@ async function packTypeDefinitions(): Promise<void> {
|
||||
const typeDefinitions = await getAllTypeDefinitions();
|
||||
|
||||
// pack TypeScript definition files
|
||||
const imports: { [k: string]: string[] } = {};
|
||||
const imports: { [k: string]: string[]; } = {};
|
||||
|
||||
const referenceLines: string[] = [];
|
||||
|
||||
@@ -163,6 +166,7 @@ async function packTypeDefinitions(): Promise<void> {
|
||||
|
||||
if (line.indexOf('/// <reference') === 0) {
|
||||
referenceLines.push(line);
|
||||
|
||||
return '// moved referenced line';
|
||||
}
|
||||
|
||||
@@ -170,13 +174,15 @@ async function packTypeDefinitions(): Promise<void> {
|
||||
const match = line.match(/^import {([^}].*)} from '([^'].*)';$/);
|
||||
|
||||
if (match !== null) {
|
||||
// extract module
|
||||
// tslint:disable-next-line:no-magic-numbers - extract module
|
||||
const module = match[2];
|
||||
|
||||
// extract imported objects
|
||||
const importedObjects = match[1].split(',').map((object) => {
|
||||
return object.trim();
|
||||
});
|
||||
const importedObjects = match[1]
|
||||
.split(',')
|
||||
.map((object) => {
|
||||
return object.trim();
|
||||
});
|
||||
|
||||
// add list of already imported objects for module
|
||||
if (typeof imports[module] === 'undefined') {
|
||||
@@ -195,27 +201,31 @@ async function packTypeDefinitions(): Promise<void> {
|
||||
// replace import line
|
||||
if (objectsToImport.length === 0) {
|
||||
return '// extraneous removed import';
|
||||
} else {
|
||||
return 'import { ' + objectsToImport.join(', ') + ' } from \'' + module + '\';';
|
||||
}
|
||||
|
||||
return `import {${objectsToImport.join(', ')}} from '${module}';`;
|
||||
}
|
||||
|
||||
return line;
|
||||
})
|
||||
// filter lines which contain "local" imports
|
||||
.filter((line) => {
|
||||
return !line.match(/^import .* from '\./);
|
||||
return line.match(/^import .* from '\./) === null;
|
||||
})
|
||||
// concat all lines separated by new lines
|
||||
.join('\n');
|
||||
|
||||
if (allDefinitions.length > 0) {
|
||||
if (referenceLines.length > 0) {
|
||||
allDefinitions = referenceLines.join('\n') + '\n\n' + allDefinitions;
|
||||
allDefinitions = `${referenceLines.join('\n')}
|
||||
|
||||
${allDefinitions}`;
|
||||
}
|
||||
|
||||
// write packed TypeScript definition files
|
||||
return await writeFilePromisified(path, PACK_IDENTIFIER + '\n\n' + allDefinitions);
|
||||
return writeFilePromisified(path, `${PACK_IDENTIFIER}
|
||||
|
||||
${allDefinitions}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,17 +243,21 @@ async function getAllJavaScriptModules(): Promise<JavaScriptModule[]> {
|
||||
|
||||
const promises = fileNames.map(async (fileName) => {
|
||||
const fileContent = await readFilePromisified(fileName, 'utf8');
|
||||
const directory = dirname(fileName).replace(new RegExp('^' + join(cwd(), 'lib')), '');
|
||||
const directory = dirname(fileName)
|
||||
.replace(new RegExp(`^${join(cwd(), 'lib')}`), '');
|
||||
|
||||
return {
|
||||
content: '(function() {\n' + fileContent + '\n})();\n',
|
||||
content: `(function() {
|
||||
${fileContent}
|
||||
})();
|
||||
`,
|
||||
dependencies: getAllInternalDependencies(fileContent),
|
||||
directory: directory,
|
||||
name: basename(fileName, '.js'),
|
||||
};
|
||||
});
|
||||
|
||||
return await Promise.all(promises);
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,13 +285,16 @@ async function packJavaScriptFiles(): Promise<void> {
|
||||
|
||||
// replace lines with internal requires
|
||||
if (match !== null) {
|
||||
// match[6] or match[8] contain the modulePath
|
||||
// tslint:disable-next-line:no-magic-numbers - match[6] or match[8] contain the modulePath
|
||||
if (typeof match[6] === 'undefined') {
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
match[6] = match[8];
|
||||
}
|
||||
|
||||
const whiteSpace = match[1] ? match[1] : '';
|
||||
const whiteSpace = (typeof match[1] === 'string' && match[1].length > 0) ? match[1] : '';
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
const importedName = match[3];
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
const modulePath = match[6];
|
||||
|
||||
// leave line unchanged if it is a "global" import
|
||||
@@ -286,22 +303,23 @@ async function packJavaScriptFiles(): Promise<void> {
|
||||
}
|
||||
|
||||
// replace internal requires with `module.exports`
|
||||
if (existsSync(join(cwd(), 'lib', module.directory, modulePath + '.js'))) {
|
||||
return whiteSpace + 'const ' + importedName + ' = module.exports;';
|
||||
if (existsSync(join(cwd(), 'lib', module.directory, `${modulePath}.js`))) {
|
||||
return `${whiteSpace}const ${importedName} = module.exports;`;
|
||||
}
|
||||
|
||||
if (existsSync(join(cwd(), 'src', module.directory, modulePath))) {
|
||||
return whiteSpace + 'const ' + importedName + ' = require(\'../src/' + modulePath + '\');';
|
||||
return `${whiteSpace} const ${importedName} = require(../src/${modulePath});`;
|
||||
}
|
||||
|
||||
Logger.warn('Import ' + importedName + ' could not be found in module.directory ' + modulePath);
|
||||
Logger.warn(`Import ${importedName} could not be found in module.directory ${modulePath}.`);
|
||||
}
|
||||
|
||||
return line;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return '// Module: ' + module.name + '\n' + module.content;
|
||||
return `// Module: ${module.name}
|
||||
${module.content}`;
|
||||
})
|
||||
// concat them separated by new lines
|
||||
.join('\n\n\n\n\n')
|
||||
@@ -332,10 +350,15 @@ async function packJavaScriptFiles(): Promise<void> {
|
||||
|
||||
if (wholeCode.length > 0) {
|
||||
// add meta lines to the file
|
||||
wholeCode = '"use strict";\nObject.defineProperty(exports, "__esModule", { value: true });\n\n' + wholeCode;
|
||||
wholeCode = `"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
||||
${wholeCode}`;
|
||||
|
||||
// write packed JavaScript files
|
||||
return await writeFilePromisified(path, PACK_IDENTIFIER + '\n\n' + wholeCode);
|
||||
return writeFilePromisified(path, `${PACK_IDENTIFIER}
|
||||
|
||||
${wholeCode}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,8 +374,9 @@ async function deleteFileIfExistingAndPacked(path: string): Promise<void> {
|
||||
|
||||
// check if packed by this script
|
||||
if (content.indexOf(PACK_IDENTIFIER) === 0) {
|
||||
Logger.log('Found `' + path + '` which is packed by this script. Deleting it...');
|
||||
return await unlinkPromisified(path);
|
||||
Logger.log(`Found '${path}' which is packed by this script. Deleting it...`);
|
||||
|
||||
return unlinkPromisified(path);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
@@ -372,23 +396,26 @@ function getAllInternalDependencies(moduleContent: string): string[] {
|
||||
moduleContent.match(/^\s*(const|var) [a-z0-9_]* = require\("([^"]+)"\)|require\('([^']+)'\);$/gmi);
|
||||
|
||||
if (Array.isArray(requireLines)) {
|
||||
return requireLines.map((requireLine) => {
|
||||
const matches = requireLine.match(/require\("([^"]+)"\)|require\('([^']+)'\);$/i);
|
||||
return requireLines
|
||||
.map((requireLine) => {
|
||||
const matches = requireLine.match(/require\("([^"]+)"\)|require\('([^']+)'\);$/i);
|
||||
|
||||
// previously matched require line does not contain a require?!
|
||||
if (matches === null) {
|
||||
throw new Error();
|
||||
}
|
||||
// previously matched require line does not contain a require?!
|
||||
if (matches === null) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
// return only the moduleName
|
||||
return matches[1];
|
||||
}).filter((moduleName) => {
|
||||
// filter out internal modules beginning with './' and not ending with '.json'
|
||||
return /^[.]{1,2}\/(?!.*\.json$).*$/i.test(moduleName);
|
||||
}).map((internalModuleName) => {
|
||||
// cut './' from the name
|
||||
return internalModuleName.substring(2);
|
||||
});
|
||||
// return only the moduleName
|
||||
return matches[1];
|
||||
})
|
||||
.filter((moduleName) => {
|
||||
// filter out internal modules beginning with './' and not ending with '.json'
|
||||
return /^[.]{1,2}\/(?!.*\.json$).*$/i.test(moduleName);
|
||||
})
|
||||
.map((internalModuleName) => {
|
||||
// cut './' from the name
|
||||
return internalModuleName.substring('./'.length);
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -417,11 +444,13 @@ function topologicalSort(modules: JavaScriptModule[]): JavaScriptModule[] {
|
||||
});
|
||||
|
||||
// sort graph and return as an array of sorted modules
|
||||
return topoSort.array(nodes, edges).map((moduleName: string) => {
|
||||
return modules.find((module) => {
|
||||
return module.name === moduleName;
|
||||
return topoSort
|
||||
.array(nodes, edges)
|
||||
.map((moduleName: string) => {
|
||||
return modules.find((module) => {
|
||||
return module.name === moduleName;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,5 +17,8 @@
|
||||
* @validatable
|
||||
*/
|
||||
export interface Foo {
|
||||
/**
|
||||
* Dummy parameter
|
||||
*/
|
||||
lorem: 'ipsum';
|
||||
}
|
||||
@@ -14,7 +14,9 @@
|
||||
*/
|
||||
import {asyncPool} from '@krlwlfrt/async-pool';
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import {basename, dirname, join} from 'path';
|
||||
import {ProjectReflection} from 'typedoc';
|
||||
import {Type} from 'typedoc/dist/lib/models';
|
||||
import {NodesWithMetaInformation, NodeWithMetaInformation, RouteWithMetaInformation} from './common';
|
||||
|
||||
/**
|
||||
@@ -32,17 +34,20 @@ export async function gatherRouteInformation(reflection: ProjectReflection): Pro
|
||||
throw new Error('Project reflection doesn\'t contain any modules.');
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
await asyncPool(2, reflection.children, async (module) => {
|
||||
if (Array.isArray(module.children) && module.children.length > 0) {
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
await asyncPool(2, module.children, (async (node) => {
|
||||
if (Array.isArray(node.extendedTypes) && node.extendedTypes.length > 0) {
|
||||
if (node.extendedTypes.some((extendedType) => {
|
||||
return (extendedType as any).name === 'SCAbstractRoute';
|
||||
// tslint:disable-next-line:completed-docs
|
||||
return (extendedType as (Type & { name: string; })).name === 'SCAbstractRoute';
|
||||
})) {
|
||||
Logger.info(`Found ${node.name} in ${module.originalName}.`);
|
||||
|
||||
if (module.originalName.match(/\.d\.ts$/)) {
|
||||
module.originalName = module.originalName.substr(0, module.originalName.length - 5);
|
||||
if (Array.isArray(module.originalName.match(/\.d\.ts$/))) {
|
||||
module.originalName = join(dirname(module.originalName), basename(module.originalName, '.d.ts'));
|
||||
Logger.info(`Using compiled version of module in ${module.originalName}.`);
|
||||
}
|
||||
|
||||
@@ -71,17 +76,18 @@ export async function gatherRouteInformation(reflection: ProjectReflection): Pro
|
||||
* @param node Node itself
|
||||
* @param humanize Whether to humanize the name or not
|
||||
*/
|
||||
export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation, humanize: boolean = false): string {
|
||||
export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation, humanize = false): string {
|
||||
const humanizeString = require('humanize-string');
|
||||
|
||||
let printableName = name;
|
||||
|
||||
if (humanize) {
|
||||
printableName = humanizeString(name.substr(2));
|
||||
printableName = humanizeString(name.substr('SC'.length));
|
||||
}
|
||||
|
||||
let link = `[${printableName}]`;
|
||||
link += `(${getLinkForNode(name, node)})`;
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -93,12 +99,16 @@ export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation
|
||||
*/
|
||||
export function getLinkForNode(name: string, node: NodeWithMetaInformation): string {
|
||||
let link = 'https://openstapps.gitlab.io/core/';
|
||||
const module = node.module.toLowerCase().split('/').join('_');
|
||||
const module = node.module
|
||||
.toLowerCase()
|
||||
.split('/')
|
||||
.join('_');
|
||||
|
||||
if (node.type === 'Type alias') {
|
||||
link += 'modules/';
|
||||
link += `_${module}_`;
|
||||
link += `.html#${name.toLowerCase()}`;
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -110,6 +120,7 @@ export function getLinkForNode(name: string, node: NodeWithMetaInformation): str
|
||||
link += `${type}/`;
|
||||
link += `_${module}_`;
|
||||
link += `.${name.toLowerCase()}.html`;
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -117,7 +128,7 @@ export function getLinkForNode(name: string, node: NodeWithMetaInformation): str
|
||||
* Generate documentation snippet for one route
|
||||
*
|
||||
* @param routeWithInfo A route instance with its meta information
|
||||
* @param nodes
|
||||
* @param nodes Nodes with meta information
|
||||
*/
|
||||
export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInformation,
|
||||
nodes: NodesWithMetaInformation): string {
|
||||
@@ -143,14 +154,20 @@ export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInform
|
||||
| request | ${getLinkedNameForNode(route.requestBodyName, nodes[route.requestBodyName])} |
|
||||
| response | ${getLinkedNameForNode(route.responseBodyName, nodes[route.responseBodyName])} |
|
||||
| success code | ${route.statusCodeSuccess} |
|
||||
| errors | ${route.errorNames.map((error) => {
|
||||
return getLinkedNameForNode(error.name, nodes[error.name]);
|
||||
}).join('<br>')} |
|
||||
| errors | ${route.errorNames
|
||||
.map((error) => {
|
||||
return getLinkedNameForNode(error.name, nodes[error.name]);
|
||||
})
|
||||
.join('<br>')} |
|
||||
`;
|
||||
if (typeof route.obligatoryParameters === 'object' && Object.keys(route.obligatoryParameters).length > 0) {
|
||||
let parameterTable = '<table><tr><th>parameter</th><th>type</th></tr>';
|
||||
|
||||
Object.keys(route.obligatoryParameters).forEach((parameter) => {
|
||||
for (const parameter in route.obligatoryParameters) {
|
||||
if (!route.obligatoryParameters.hasOwnProperty(parameter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let type = route.obligatoryParameters![parameter];
|
||||
|
||||
if (typeof nodes[type] !== 'undefined') {
|
||||
@@ -158,7 +175,7 @@ export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInform
|
||||
}
|
||||
|
||||
parameterTable += `<tr><td>${parameter}</td><td>${type}</td></tr>`;
|
||||
});
|
||||
}
|
||||
|
||||
parameterTable += '</table>';
|
||||
|
||||
@@ -182,14 +199,14 @@ export function getNodeMetaInformationMap(projectReflection: ProjectReflection):
|
||||
}
|
||||
|
||||
// iterate over modules
|
||||
projectReflection.children.forEach((module: any) => {
|
||||
projectReflection.children.forEach((module) => {
|
||||
if (Array.isArray(module.children) && module.children.length > 0) {
|
||||
// iterate over types
|
||||
module.children.forEach((node: any) => {
|
||||
module.children.forEach((node) => {
|
||||
// add node with module and type
|
||||
nodes[node.name] = {
|
||||
module: module.name.substring(1, module.name.length - 1),
|
||||
type: node.kindString,
|
||||
type: node.kindString!,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import * as ajv from 'ajv';
|
||||
import * as Ajv from 'ajv';
|
||||
import {Schema as JSONSchema} from 'jsonschema';
|
||||
import {join} from 'path';
|
||||
import {DEFAULT_CONFIG, SchemaGenerator} from 'ts-json-schema-generator';
|
||||
import {DEFAULT_CONFIG, Definition, SchemaGenerator} from 'ts-json-schema-generator';
|
||||
import {createFormatter} from 'ts-json-schema-generator/dist/factory/formatter';
|
||||
import {createParser} from 'ts-json-schema-generator/dist/factory/parser';
|
||||
import {createProgram} from 'ts-json-schema-generator/dist/factory/program';
|
||||
@@ -28,8 +28,15 @@ import {getTsconfigPath, isSchemaWithDefinitions} from './common';
|
||||
* Converts TypeScript source files to JSON schema files
|
||||
*/
|
||||
export class Converter {
|
||||
private generator: SchemaGenerator;
|
||||
private schemaValidator: ajv.Ajv;
|
||||
/**
|
||||
* Generator instance
|
||||
*/
|
||||
private readonly generator: SchemaGenerator;
|
||||
|
||||
/**
|
||||
* Schema validator instance
|
||||
*/
|
||||
private readonly schemaValidator: Ajv.Ajv;
|
||||
|
||||
/**
|
||||
* Create a new converter
|
||||
@@ -58,24 +65,24 @@ export class Converter {
|
||||
createFormatter(config),
|
||||
);
|
||||
|
||||
// create ajv instance
|
||||
this.schemaValidator = new ajv();
|
||||
// create Ajv instance
|
||||
this.schemaValidator = new Ajv();
|
||||
this.schemaValidator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema for specific StAppsCore type
|
||||
*
|
||||
* @param {string} type Type to get the schema for
|
||||
* @param {string} version Version to set for the schema
|
||||
* @returns {Schema} Generated schema
|
||||
* @param type Type to get the schema for
|
||||
* @param version Version to set for the schema
|
||||
* @returns Generated schema
|
||||
*/
|
||||
getSchema(type: string, version: string): JSONSchema {
|
||||
// generate schema for this file/type
|
||||
const schema: JSONSchema = this.generator.createSchema(type);
|
||||
|
||||
// set id of schema
|
||||
schema.id = 'https://core.stapps.tu-berlin.de/v' + version + '/lib/schema/' + type + '.json';
|
||||
schema.id = `https://core.stapps.tu-berlin.de/v${version}/lib/schema/${type}.json`;
|
||||
|
||||
if (isSchemaWithDefinitions(schema)) {
|
||||
const selfReference = {
|
||||
@@ -88,7 +95,10 @@ export class Converter {
|
||||
delete selfReference.id;
|
||||
|
||||
// add self reference to definitions
|
||||
schema.definitions['SC' + type] = Object.assign({}, selfReference as any);
|
||||
schema.definitions[`SC${type}`] = {
|
||||
...{},
|
||||
...selfReference as unknown as Definition,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.schemaValidator.validateSchema(schema)) {
|
||||
@@ -119,7 +129,7 @@ export function getValidatableTypesFromReflection(projectReflection: ProjectRefl
|
||||
// check if type has annotation @validatable
|
||||
if (typeof type.comment === 'object'
|
||||
&& Array.isArray(type.comment.tags)
|
||||
&& type.comment.tags.find((tag) => tag.tagName === 'validatable')) {
|
||||
&& type.comment.tags.findIndex((tag) => tag.tagName === 'validatable') >= 0) {
|
||||
// add type to list
|
||||
validatableTypes.push(type.name);
|
||||
}
|
||||
|
||||
@@ -18,12 +18,7 @@ import {PathLike} from 'fs';
|
||||
import {Schema, Validator as JSONSchemaValidator, ValidatorResult} from 'jsonschema';
|
||||
import * as mustache from 'mustache';
|
||||
import {basename, join, resolve} from 'path';
|
||||
import {
|
||||
ExpectableValidationErrors,
|
||||
globPromisified,
|
||||
readFilePromisified,
|
||||
writeFilePromisified,
|
||||
} from './common';
|
||||
import {ExpectableValidationErrors, globPromisified, readFilePromisified, writeFilePromisified} from './common';
|
||||
|
||||
/**
|
||||
* StAppsCore validator
|
||||
@@ -32,7 +27,7 @@ export class Validator {
|
||||
/**
|
||||
* Map of schema names to schemas
|
||||
*/
|
||||
private readonly schemas: { [type: string]: Schema } = {};
|
||||
private readonly schemas: { [type: string]: Schema; } = {};
|
||||
|
||||
/**
|
||||
* JSONSchema validator instance
|
||||
@@ -60,7 +55,7 @@ export class Validator {
|
||||
|
||||
Logger.log(`Adding schemas from ${schemaDir} to validator.`);
|
||||
|
||||
// Iterate over schema files
|
||||
// tslint:disable-next-line:no-magic-numbers - iterate over schema files
|
||||
await asyncPool(2, schemaFiles, async (file) => {
|
||||
// read schema file
|
||||
const buffer = await readFilePromisified(file);
|
||||
@@ -84,7 +79,7 @@ export class Validator {
|
||||
* @param instance Instance to validate
|
||||
* @param schema Name of schema to validate instance against or the schema itself
|
||||
*/
|
||||
public validate(instance: any, schema: string | Schema): ValidatorResult {
|
||||
public validate(instance: unknown, schema: string | Schema): ValidatorResult {
|
||||
if (typeof schema === 'string') {
|
||||
// if you want to access a schema that is contained in the validator object
|
||||
if (typeof this.schemas[schema] !== 'object') {
|
||||
@@ -92,10 +87,10 @@ export class Validator {
|
||||
}
|
||||
|
||||
return this.validator.validate(instance, this.schemas[schema]);
|
||||
} else {
|
||||
// if you have a schema and want to validate it directly
|
||||
return this.validator.validate(instance, schema);
|
||||
}
|
||||
|
||||
// if you have a schema and want to validate it directly
|
||||
return this.validator.validate(instance, schema);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +117,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
// map of errors per file
|
||||
const errors: ExpectableValidationErrors = {};
|
||||
|
||||
// iterate over files to test
|
||||
// tslint:disable-next-line:no-magic-numbers - iterate over files to test
|
||||
await asyncPool(2, testFiles, async (testFile) => {
|
||||
const testFileName = basename(testFile);
|
||||
|
||||
@@ -145,7 +140,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
errors[testFileName] = [];
|
||||
|
||||
// iterate over errors
|
||||
result.errors.forEach((error) => {
|
||||
for (const error of result.errors) {
|
||||
// get idx of expected error
|
||||
const errorIdx = expectedErrors.indexOf(error.name);
|
||||
let expected = false;
|
||||
@@ -156,7 +151,7 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
expected = true;
|
||||
} else {
|
||||
unexpectedErrors++;
|
||||
Logger.error(`Unexpected error ${error.name} in ${testFile}`);
|
||||
await Logger.error(`Unexpected error ${error.name} in ${testFile}`);
|
||||
}
|
||||
|
||||
// add error to list of errors
|
||||
@@ -164,12 +159,13 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
...error,
|
||||
expected,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedErrors.length > 0) {
|
||||
expectedErrors.forEach((error) => {
|
||||
Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
|
||||
for (const error of expectedErrors) {
|
||||
await Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
|
||||
|
||||
errors[testFileName].push({
|
||||
argument: false,
|
||||
expected: false,
|
||||
@@ -177,9 +173,9 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
|
||||
message: `expected error ${error} did not occur`,
|
||||
name: `expected ${error}`,
|
||||
property: 'unknown',
|
||||
schema: undefined as any,
|
||||
schema: 'undefined',
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (unexpectedErrors === 0) {
|
||||
Logger.info(`Successfully validated ${testFile}.`);
|
||||
}
|
||||
@@ -203,15 +199,21 @@ export async function writeReport(reportPath: PathLike, errors: ExpectableValida
|
||||
|
||||
let output = '';
|
||||
|
||||
Object.keys(errors).forEach((fileName) => {
|
||||
for (const fileName in errors) {
|
||||
if (!errors.hasOwnProperty(fileName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let fileOutput = '';
|
||||
|
||||
errors[fileName].forEach((error, idx) => {
|
||||
fileOutput += mustache.render(errorTemplate, {
|
||||
idx: idx + 1,
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
instance: JSON.stringify(error.instance, null, 2),
|
||||
message: error.message,
|
||||
property: error.property,
|
||||
// tslint:disable-next-line:no-magic-numbers
|
||||
schema: JSON.stringify(error.schema, null, 2),
|
||||
status: (error.expected) ? 'alert-success' : 'alert-danger',
|
||||
});
|
||||
@@ -221,7 +223,7 @@ export async function writeReport(reportPath: PathLike, errors: ExpectableValida
|
||||
errors: fileOutput,
|
||||
testFile: fileName,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
buffer = await readFilePromisified(resolve(__dirname, '..', 'resources', 'report.html.mustache'));
|
||||
const reportTemplate = buffer.toString();
|
||||
|
||||
@@ -23,7 +23,7 @@ process.on('unhandledRejection', (err) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@suite(timeout(10000), slow(5000))
|
||||
@suite(timeout(20000), slow(10000))
|
||||
export class CommonSpec {
|
||||
@test
|
||||
async getTsconfigPath() {
|
||||
|
||||
@@ -23,7 +23,7 @@ process.on('unhandledRejection', (err) => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@suite(timeout(15000), slow(5000))
|
||||
@suite(timeout(20000), slow(10000))
|
||||
export class SchemaSpec {
|
||||
@test
|
||||
async getSchema() {
|
||||
@@ -39,6 +39,7 @@ export class SchemaSpec {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
lorem: {
|
||||
description: 'Dummy parameter',
|
||||
enum: [
|
||||
'ipsum',
|
||||
],
|
||||
@@ -54,6 +55,7 @@ export class SchemaSpec {
|
||||
id: 'https://core.stapps.tu-berlin.de/v0.0.1/lib/schema/Foo.json',
|
||||
properties: {
|
||||
lorem: {
|
||||
description: 'Dummy parameter',
|
||||
enum: [
|
||||
'ipsum',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user