mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-22 01:22:54 +00:00
feat: add tool to generate documentation for routes
This commit is contained in:
31
package-lock.json
generated
31
package-lock.json
generated
@@ -265,6 +265,12 @@
|
|||||||
"integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==",
|
"integrity": "sha512-pGF/zvYOACZ/gLGWdQH8zSwteQS1epp68yRcVLJMgUck/MjEn/FBYmPub9pXT8C1e4a8YZfHo1CKyV8q1vKUnQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/humanize-string": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/humanize-string/-/humanize-string-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-lfaNfcTSt2DLiF1V8kXMhT4rX7ggkc10wI9SqTrxFMNTIfaafXHCL5DS1q2J/i+Be3EBQyG+Ls8GSbKngvSIkw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/inquirer": {
|
"@types/inquirer": {
|
||||||
"version": "0.0.43",
|
"version": "0.0.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-0.0.43.tgz",
|
"resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-0.0.43.tgz",
|
||||||
@@ -341,6 +347,16 @@
|
|||||||
"@types/request": "*"
|
"@types/request": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/rimraf": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/glob": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/rx": {
|
"@types/rx": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "http://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz",
|
"resolved": "http://registry.npmjs.org/@types/rx/-/rx-4.1.1.tgz",
|
||||||
@@ -887,9 +903,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.18.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||||
"integrity": "sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ==",
|
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"compare-func": {
|
"compare-func": {
|
||||||
@@ -1799,6 +1815,15 @@
|
|||||||
"sshpk": "^1.7.0"
|
"sshpk": "^1.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"humanize-string": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/humanize-string/-/humanize-string-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-PH5GBkXqFxw5+4eKaKRIkD23y6vRd/IXSl7IldyJxEXpDH9SEIXRORkBtkGni/ae2P7RVOw6Wxypd2tGXhha1w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"decamelize": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"humps": {
|
"humps": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run tslint && npm run compile && npm run pack && npm run schema && npm run documentation",
|
"build": "npm run tslint && npm run compile && npm run pack && npm run schema && npm run documentation",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
||||||
"compile": "tsc",
|
"compile": "tsc && rimraf lib/cli.js lib/common.js lib/types.js",
|
||||||
"documentation": "typedoc --includeDeclarations --excludeExternals --mode modules --out docs src",
|
"documentation": "typedoc --includeDeclarations --excludeExternals --mode modules --out docs src",
|
||||||
"pack": "openstapps-pack",
|
"pack": "openstapps-pack",
|
||||||
"prepareOnly": "npm run build",
|
"prepareOnly": "npm run build",
|
||||||
@@ -46,11 +46,17 @@
|
|||||||
"@openstapps/logger": "0.0.3",
|
"@openstapps/logger": "0.0.3",
|
||||||
"@openstapps/projectmanagement": "0.0.1",
|
"@openstapps/projectmanagement": "0.0.1",
|
||||||
"@types/chai": "4.1.7",
|
"@types/chai": "4.1.7",
|
||||||
|
"@types/humanize-string": "1.0.0",
|
||||||
"@types/node": "10.12.10",
|
"@types/node": "10.12.10",
|
||||||
|
"@types/rimraf": "2.0.2",
|
||||||
|
"async-pool-native": "0.1.0",
|
||||||
"chai": "4.2.0",
|
"chai": "4.2.0",
|
||||||
|
"commander": "2.19.0",
|
||||||
"conventional-changelog-cli": "2.0.11",
|
"conventional-changelog-cli": "2.0.11",
|
||||||
|
"humanize-string": "1.0.2",
|
||||||
"mocha": "5.2.0",
|
"mocha": "5.2.0",
|
||||||
"mocha-typescript": "1.1.17",
|
"mocha-typescript": "1.1.17",
|
||||||
|
"rimraf": "2.6.2",
|
||||||
"ts-node": "7.0.1",
|
"ts-node": "7.0.1",
|
||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"typedoc": "0.13.0",
|
"typedoc": "0.13.0",
|
||||||
|
|||||||
83
src/cli.ts
Normal file
83
src/cli.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import {execSync} from 'child_process';
|
||||||
|
import * as commander from 'commander';
|
||||||
|
import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs';
|
||||||
|
import {resolve} from 'path';
|
||||||
|
import {ProjectReflection} from 'typedoc';
|
||||||
|
import {gatherRouteInformation, generateDocumentationForRoute, logger, rimrafPromisifed} from './common';
|
||||||
|
import {NodesWithMetaInformation} from './types';
|
||||||
|
|
||||||
|
commander.version(JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json')).toString()).version);
|
||||||
|
|
||||||
|
commander
|
||||||
|
.command('routes <mdPath>')
|
||||||
|
.action(async (relativeMdPath) => {
|
||||||
|
if (!existsSync(resolve('tmp'))) {
|
||||||
|
// create tmp directory
|
||||||
|
mkdirSync(resolve('tmp'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = resolve('node_modules', '.bin', 'typedoc');
|
||||||
|
const jsonPath = resolve('tmp', 'out.json');
|
||||||
|
const mdPath = resolve(relativeMdPath);
|
||||||
|
|
||||||
|
logger.info(`Using Typedoc from ${command}.`);
|
||||||
|
|
||||||
|
const result = execSync(`${command} --includeDeclarations --excludeExternals --mode modules --json ${jsonPath}`);
|
||||||
|
|
||||||
|
result.toString().split('\n').forEach((line) => {
|
||||||
|
if (line.length > 0) {
|
||||||
|
logger.info(line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const jsonContent: ProjectReflection = JSON.parse(readFileSync(jsonPath).toString());
|
||||||
|
|
||||||
|
const nodes: NodesWithMetaInformation = {};
|
||||||
|
|
||||||
|
jsonContent.children.forEach((module: any) => {
|
||||||
|
if (Array.isArray(module.children) && module.children.length > 0) {
|
||||||
|
module.children.forEach((node: any) => {
|
||||||
|
nodes[node.name] = {
|
||||||
|
module: module.name.substring(1, module.name.length - 1),
|
||||||
|
type: node.kindString,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const routes = await gatherRouteInformation(jsonContent);
|
||||||
|
|
||||||
|
let output: string = '# Routes\n\n';
|
||||||
|
|
||||||
|
routes.forEach((routeWithMetaInformation) => {
|
||||||
|
output += generateDocumentationForRoute(routeWithMetaInformation, nodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
writeFileSync(mdPath, output);
|
||||||
|
|
||||||
|
logger.ok(`Route documentation written to ${mdPath}.`);
|
||||||
|
|
||||||
|
// remove temporary files
|
||||||
|
await rimrafPromisifed(resolve('tmp'));
|
||||||
|
});
|
||||||
|
|
||||||
|
commander.parse(process.argv);
|
||||||
|
|
||||||
|
if (commander.args.length < 2) {
|
||||||
|
commander.outputHelp();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
168
src/common.ts
Normal file
168
src/common.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import {Logger} from '@openstapps/logger';
|
||||||
|
import {asyncPool} from 'async-pool-native/dist/async-pool';
|
||||||
|
import humanizeString = require('humanize-string');
|
||||||
|
import * as rimraf from 'rimraf';
|
||||||
|
import {ProjectReflection} from 'typedoc';
|
||||||
|
import {promisify} from 'util';
|
||||||
|
import {NodesWithMetaInformation, NodeWithMetaInformation, RouteWithMetaInformation} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialized logger
|
||||||
|
*/
|
||||||
|
export const logger = new Logger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Promisified rimraf
|
||||||
|
*/
|
||||||
|
export const rimrafPromisifed = promisify(rimraf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather relevant information of routes
|
||||||
|
*
|
||||||
|
* This gathers the information for all routes that implement the abstract class SCAbstractRoute.
|
||||||
|
* Furthermore it instantiates every route and adds it to the information.
|
||||||
|
*
|
||||||
|
* @param reflection Contents of the JSON representation which Typedoc generates
|
||||||
|
*/
|
||||||
|
export async function gatherRouteInformation(reflection: ProjectReflection): Promise<RouteWithMetaInformation[]> {
|
||||||
|
const routes: RouteWithMetaInformation[] = [];
|
||||||
|
|
||||||
|
await asyncPool(2, reflection.children, async (module: any) => {
|
||||||
|
if (Array.isArray(module.children) && module.children.length > 0) {
|
||||||
|
await asyncPool(2, module.children, (async (node: any) => {
|
||||||
|
if (Array.isArray(node.extendedTypes) && node.extendedTypes.length > 0) {
|
||||||
|
if (node.extendedTypes.some((extendedType: any) => {
|
||||||
|
return extendedType.name === 'SCAbstractRoute';
|
||||||
|
})) {
|
||||||
|
logger.info(`Found ${node.name} in ${module.originalName}.`);
|
||||||
|
|
||||||
|
const importedModule = await import(module.originalName);
|
||||||
|
|
||||||
|
const route = new importedModule[node.name]();
|
||||||
|
|
||||||
|
routes.push({description: node.comment, name: node.name, route});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a linked name for a node
|
||||||
|
*
|
||||||
|
* @param name Name of the node
|
||||||
|
* @param node Node itself
|
||||||
|
* @param humanize Whether to humanize the name or not
|
||||||
|
*/
|
||||||
|
export function getLinkedNameForNode(name: string, node: NodeWithMetaInformation, humanize: boolean = false): string {
|
||||||
|
let printableName = name;
|
||||||
|
|
||||||
|
if (humanize) {
|
||||||
|
printableName = humanizeString(name.substr(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
let link = `[${printableName}]`;
|
||||||
|
link += `(${getLinkForNode(name, node)})`;
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get link for a node
|
||||||
|
*
|
||||||
|
* @param name Name of the node
|
||||||
|
* @param node Node itself
|
||||||
|
*/
|
||||||
|
export function getLinkForNode(name: string, node: NodeWithMetaInformation): string {
|
||||||
|
let link = 'https://openstapps.gitlab.io/core/';
|
||||||
|
const module = node.module.toLowerCase().split('/').join('_');
|
||||||
|
|
||||||
|
if (node.type === 'Type alias') {
|
||||||
|
link += 'modules/';
|
||||||
|
link += `_${module}_`;
|
||||||
|
link += `.html#${name.toLowerCase()}`;
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = 'classes';
|
||||||
|
if (node.type !== 'Class') {
|
||||||
|
type = `${node.type.toLowerCase()}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
link += `${type}/`;
|
||||||
|
link += `_${module}_`;
|
||||||
|
link += `.${name.toLowerCase()}.html`;
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate documentation snippet for one route
|
||||||
|
*
|
||||||
|
* @param routeWithInfo A route instance with its meta information
|
||||||
|
* @param nodes
|
||||||
|
*/
|
||||||
|
export function generateDocumentationForRoute(routeWithInfo: RouteWithMetaInformation,
|
||||||
|
nodes: NodesWithMetaInformation): string {
|
||||||
|
let output = '';
|
||||||
|
|
||||||
|
const route = routeWithInfo.route;
|
||||||
|
|
||||||
|
output += `## \`${route.method} ${route.urlFragment}\``;
|
||||||
|
output += ` ${getLinkedNameForNode(routeWithInfo.name, nodes[routeWithInfo.name], true)}\n\n`;
|
||||||
|
|
||||||
|
if (typeof routeWithInfo.description.shortText === 'string') {
|
||||||
|
output += `**${routeWithInfo.description.shortText}**\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof routeWithInfo.description.text === 'string') {
|
||||||
|
output += `${routeWithInfo.description.text.replace('\n', '<br>')}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
output += `### Definition
|
||||||
|
|
||||||
|
| parameter | value |
|
||||||
|
| --- | --- |
|
||||||
|
| request | ${getLinkedNameForNode(route.requestBodyName, nodes[route.requestBodyName])} |
|
||||||
|
| response | ${getLinkedNameForNode(route.responseBodyName, nodes[route.responseBodyName])} |
|
||||||
|
| success code | ${route.statusCodeSuccess} |
|
||||||
|
| errors | ${route.errorNames.map((errorName) => {
|
||||||
|
return getLinkedNameForNode(errorName, nodes[errorName]);
|
||||||
|
}).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) => {
|
||||||
|
let type = route.obligatoryParameters![parameter];
|
||||||
|
|
||||||
|
if (typeof nodes[type] !== 'undefined') {
|
||||||
|
type = getLinkedNameForNode(type, nodes[type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
parameterTable += `<tr><td>${parameter}</td><td>${type}</td></tr>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
parameterTable += '</table>';
|
||||||
|
|
||||||
|
output += `| obligatory parameters | ${parameterTable} |`;
|
||||||
|
}
|
||||||
|
output += '\n\n';
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
@@ -41,7 +41,19 @@ export interface SCBookAvailabilityRequestByUuid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route for book availiability
|
* Route for book availability
|
||||||
|
*
|
||||||
|
* This checks if a book is available in a library.
|
||||||
|
*
|
||||||
|
* **Example**:
|
||||||
|
*
|
||||||
|
* `POST https://example.com/bookAvailability`
|
||||||
|
*
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "isbn": "978-3-16-148410-0"
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
export class SCBookAvailabilityRoute extends SCAbstractRoute {
|
export class SCBookAvailabilityRoute extends SCAbstractRoute {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
66
src/types.ts
Normal file
66
src/types.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import {SCAbstractRoute} from './core/Route';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A route instance with its relevant meta information
|
||||||
|
*/
|
||||||
|
export interface RouteWithMetaInformation {
|
||||||
|
/**
|
||||||
|
* Description of the route
|
||||||
|
*/
|
||||||
|
description: {
|
||||||
|
/**
|
||||||
|
* Short text of the description - title
|
||||||
|
*/
|
||||||
|
shortText?: string;
|
||||||
|
/**
|
||||||
|
* Text of the description
|
||||||
|
*/
|
||||||
|
text?: string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Name of the route
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Instance of the route
|
||||||
|
*/
|
||||||
|
route: SCAbstractRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node with its relevant meta information
|
||||||
|
*/
|
||||||
|
export interface NodeWithMetaInformation {
|
||||||
|
/**
|
||||||
|
* Module the node belongs to
|
||||||
|
*/
|
||||||
|
module: string;
|
||||||
|
/**
|
||||||
|
* Type of the node
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of nodes indexed by their name
|
||||||
|
*/
|
||||||
|
export interface NodesWithMetaInformation {
|
||||||
|
/**
|
||||||
|
* Index signature
|
||||||
|
*/
|
||||||
|
[k: string]: NodeWithMetaInformation;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user