diff --git a/src/models.ts b/src/models.ts index 2df2aebd..9b51eb00 100644 --- a/src/models.ts +++ b/src/models.ts @@ -87,7 +87,7 @@ export type YfmTocIncluders = YfmTocIncluder[]; export type YfmTocIncluder = YfmTocIncluderName | YfmTocIncluderObject; -export const includersNames = ['sourcedocs'] as const; +export const includersNames = ['sourcedocs', 'openapi'] as const; export type YfmTocIncluderName = typeof includersNames[number]; diff --git a/src/services/includers/batteries/index.ts b/src/services/includers/batteries/index.ts index c9795bbd..625df3d9 100644 --- a/src/services/includers/batteries/index.ts +++ b/src/services/includers/batteries/index.ts @@ -1,2 +1,2 @@ export * as sourcedocs from './sourcedocs'; -// export * as openapi from './openapi'; +export * as openapi from './openapi'; diff --git a/src/services/includers/batteries/openapi/index.ts b/src/services/includers/batteries/openapi/index.ts index 36300fcc..c233b66f 100644 --- a/src/services/includers/batteries/openapi/index.ts +++ b/src/services/includers/batteries/openapi/index.ts @@ -1,100 +1,71 @@ -/* eslint-disable no-shadow */ -import {join} from 'path'; +import {resolve, join, dirname} from 'path'; +import {mkdir, writeFile} from 'fs/promises'; import parser from '@apidevtools/swagger-parser'; +import {dump} from 'js-yaml'; import parsers from './parsers'; import generators from './generators'; -import { - YfmToc, - IncluderFnParams, - IncluderFnOutput, - IncluderFnOutputElement, -} from '../../../../models'; +import {IncluderFunctionParams, YfmToc} from '../../../../models'; import {Endpoint, Info} from './types'; -async function generateContent(params: IncluderFnParams): IncluderFnOutput { - const { - include: {path}, - root, - } = params; +const name = 'openapi'; - const includePath = fixpath(path); +class OpenApiIncluderError extends Error { + path: string; - let data; + constructor(message: string, path: string) { + super(message); - try { - data = await parser.validate(join(root, path), {validate: {spec: true}}); - } catch (e) { - throw Error('includer openapi: failed to parse specification'); + this.name = 'OpenApiIncluderError'; + this.path = path; } +} - const results = []; +async function includerFunction(params: IncluderFunctionParams) { + const {readBasePath, writeBasePath, tocPath, passedParams: {input, leadingPage}} = params; - const info: Info = parsers.info(data); - const spec = parsers.paths(data, parsers.tags(data)); + const tocDirPath = dirname(tocPath); - const main: string = generators.main(info, spec); + const contentPath = resolve(process.cwd(), readBasePath, input); - results.push({ - path: join(root, includePath, 'index.md'), - content: main, - }); - - spec.tags.forEach((tag, id) => { - const path = join(root, includePath, id, 'index.md'); - const content = generators.section(tag); - - results.push({path, content}); + const leadingPageName = leadingPage?.name ?? 'Overview'; - const {endpoints} = tag; - if (!endpoints) { return; } - - endpoints.forEach((endpoint) => { - results.push(handleEndpointIncluder(endpoint, join(root, includePath, id))); - }); - }); + let data; - for (const endpoint of spec.endpoints) { - results.push(handleEndpointIncluder(endpoint, join(root, includePath))); + try { + data = await parser.validate(contentPath, {validate: {spec: true}}); + } catch (err) { + if (err instanceof Error) { + throw new OpenApiIncluderError(err.toString(), tocPath); + } } - return results; -} - -async function generateTocs(params: IncluderFnParams): IncluderFnOutput { - const { - name, - include: {path}, - root, - } = params; - - let data; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const writePath = join(writeBasePath, tocDirPath, params.item.include!.path); try { - data = await parser.validate(join(root, path), {validate: {spec: true}}); - - } catch (e) { - throw Error('includer openapi: failed to parse specification'); + await mkdir(writePath, {recursive: true}); + await generateToc(data, writePath, leadingPageName); + await generateContent(data, writePath); + } catch (err) { + if (err instanceof Error) { + throw new OpenApiIncluderError(err.toString(), tocPath); + } } +} - const includePath = fixpath(path); - - const result = { - path: join(root, includePath, 'toc.yaml'), - content: { - name, - href: 'index.yaml', - items: [ +async function generateToc(data: any, writePath: string, leadingPageName: string): Promise { + const toc = { + name, + items: [ { - hidden: true, - name: 'index', + name: leadingPageName, href: 'index.md', } as unknown as YfmToc, - ], - } as YfmToc, - }; + ], + } as YfmToc; const {tags, endpoints} = parsers.paths(data, parsers.tags(data)); @@ -104,8 +75,7 @@ async function generateTocs(params: IncluderFnParams): IncluderFnOutput { const section: YfmToc = { name, items: [{ - hidden: true, - name: 'index', + name: leadingPageName, href: join(id, 'index.md'), } as unknown as YfmToc], } as YfmToc; @@ -116,49 +86,55 @@ async function generateTocs(params: IncluderFnParams): IncluderFnOutput { section.items.push(handleEndpointRender(endpoint, id)); }); - result.content.items.push(section); + toc.items.push(section); }); for (const endpoint of endpoints) { - result.content.items.push(handleEndpointRender(endpoint)); + toc.items.push(handleEndpointRender(endpoint)); } - return [result]; + await mkdir(dirname(writePath), {recursive: true}); + await writeFile(join(writePath, 'toc.yaml'), dump(toc)); } -async function generateLeadingPages( - params: IncluderFnParams, -): IncluderFnOutput { - const { - name, - include: {path}, - root, - } = params; - - const includePath = fixpath(path); - - return [ - { - path: join(root, includePath, 'index.yaml'), - content: { - title: name, - links: [ - {title: 'index', href: 'index.md'}, - ], - }, - }, - ]; -} +async function generateContent(data: any, writePath: string): Promise { + const results = []; -async function generatePath(params: IncluderFnParams): Promise { - return join(fixpath(params.include.path), 'toc.yaml'); -} + const info: Info = parsers.info(data); + const spec = parsers.paths(data, parsers.tags(data)); -function fixpath(path: string) { - return path.replace(/\.[^.]+$/gmu, ''); + const main: string = generators.main(info, spec); + + results.push({ + path: join(writePath, 'index.md'), + content: main, + }); + + spec.tags.forEach((tag, id) => { + const path = join(writePath, id, 'index.md'); + const content = generators.section(tag); + + results.push({path, content}); + + const {endpoints} = tag; + if (!endpoints) { return; } + + endpoints.forEach((endpoint) => { + results.push(handleEndpointIncluder(endpoint, join(writePath, id))); + }); + }); + + for (const endpoint of spec.endpoints) { + results.push(handleEndpointIncluder(endpoint, join(writePath))); + } + + for (const {path, content} of results) { + await mkdir(dirname(path), {recursive: true}); + await writeFile(path, content); + } } -function handleEndpointIncluder(endpoint: Endpoint, pathPrefix: string): IncluderFnOutputElement { +function handleEndpointIncluder(endpoint: Endpoint, pathPrefix: string) { const path = join(pathPrefix, mdPath(endpoint)); const content = generators.endpoint(endpoint); @@ -184,12 +160,4 @@ export function mdPath(e: Endpoint): string { return `${e.id}.md`; } -const name = 'openapi'; - -export { - name, - generateTocs, - generateContent, - generateLeadingPages, - generatePath, -}; +export {name, includerFunction}; diff --git a/src/services/includers/index.ts b/src/services/includers/index.ts index 75aa4411..2d16cc6f 100644 --- a/src/services/includers/index.ts +++ b/src/services/includers/index.ts @@ -5,7 +5,7 @@ import {isObject} from 'lodash'; import {ArgvService} from '../index'; import {IncludeMode} from '../../constants'; import {logger} from '../../utils/logger'; -import {sourcedocs} from './batteries'; +import {sourcedocs, openapi} from './batteries'; import type { Includer, @@ -51,7 +51,7 @@ class IncludersError extends Error { function init(custom: Includer[] = []) { if (includersMap) { return; } - includersMap = {sourcedocs}; + includersMap = {sourcedocs, openapi}; for (const includer of custom) { includersMap[includer.name] = includer;