/* * 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 {existsSync, mkdir, PathLike, readFile, unlink, writeFile} from 'fs'; import * as glob from 'glob'; import {Schema as JSONSchema, ValidationError} from 'jsonschema'; import {platform} from 'os'; import {join, sep} from 'path'; import {Definition} from 'ts-json-schema-generator'; import {Application, ProjectReflection} from 'typedoc'; import {promisify} from 'util'; export const logger = new Logger(); export const globPromisified = promisify(glob); export const mkdirPromisified = promisify(mkdir); export const readFilePromisified = promisify(readFile); export const writeFilePromisified = promisify(writeFile); export const unlinkPromisified = promisify(unlink); /** * 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: { errorNames: Error[]; method: string; obligatoryParameters: { [k: string]: string; } requestBodyName: string; responseBodyName: string; statusCodeSuccess: number; urlFragment: string; }; } /** * 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; } /** * A schema with definitions */ interface SchemaWithDefinitions extends JSONSchema { definitions: { [name: string]: Definition }; } /** * An expectable error */ export type ExpectableValidationError = ValidationError & { expected: boolean }; /** * A map of files and their expectable validation errors */ export interface ExpectableValidationErrors { [fileName: string]: ExpectableValidationError[]; } /** * Get a project reflection from a path * * @param srcPath Path to get reflection from */ export function getProjectReflection(srcPath: PathLike): ProjectReflection { logger.info(`Generating project reflection for ${srcPath.toString()}.`); const tsconfigPath = getTsconfigPath(srcPath.toString()); // initialize new Typedoc application const app = new Application({ excludeExternals: true, includeDeclarations: true, module: 'commonjs', tsconfig: join(tsconfigPath, 'tsconfig.json'), }); let inputFilePath = srcPath; if (inputFilePath === tsconfigPath) { inputFilePath = join(tsconfigPath, 'src'); } // get input files const inputFiles = app.expandInputFiles([inputFilePath.toString()]); // get project reflection from input files const result = app.convert(inputFiles); if (typeof result === 'undefined') { throw new Error('Project reflection could not be generated.'); } return result; } /** * Check if a schema has definitions * * @param schema Schema to check */ export function isSchemaWithDefinitions(schema: JSONSchema): schema is SchemaWithDefinitions { return typeof schema.definitions !== 'undefined'; } /** * Get path that contains a tsconfig.json * * @param startPath Path from where to start searching "upwards" */ 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] : '/'; // repeat until a tsconfig.json is found while (!existsSync(join(tsconfigPath, 'tsconfig.json'))) { if (tsconfigPath === root) { throw new Error(`Reached file system root ${root} while searching for 'tsconfig.json' in ${startPath}!`); } // pop last directory const tsconfigPathParts = tsconfigPath.split(sep); tsconfigPathParts.pop(); tsconfigPath = tsconfigPathParts.join(sep); } logger.info(`Using 'tsconfig.json' from ${tsconfigPath}.`); return tsconfigPath; }