@rsdk/cli.cmd.autodoc 6.0.0-next.2 → 6.0.0-next.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +382 -0
- package/TODO.MD +24 -0
- package/dist/commands/autodoc.cmd.js +8 -10
- package/dist/commands/autodoc.cmd.js.map +1 -1
- package/dist/commands/openapi.variation.d.ts +2 -1
- package/dist/commands/openapi.variation.js +47 -25
- package/dist/commands/openapi.variation.js.map +1 -1
- package/dist/generators/file-generator.interface.d.ts +1 -1
- package/dist/generators/openapi-json/config.schema.d.ts +55 -0
- package/dist/generators/openapi-json/config.schema.js +36 -0
- package/dist/generators/openapi-json/config.schema.js.map +1 -0
- package/dist/generators/openapi-json/index.d.ts +1 -0
- package/dist/generators/openapi-json/index.js +1 -0
- package/dist/generators/openapi-json/index.js.map +1 -1
- package/dist/generators/openapi-json/openapi-json.generator.d.ts +9 -11
- package/dist/generators/openapi-json/openapi-json.generator.js +12 -89
- package/dist/generators/openapi-json/openapi-json.generator.js.map +1 -1
- package/dist/generators/openapi-json/schemas/config.schema.d.ts +55 -0
- package/dist/generators/openapi-json/schemas/config.schema.js +36 -0
- package/dist/generators/openapi-json/schemas/config.schema.js.map +1 -0
- package/dist/generators/openapi-json/schemas/index.d.ts +1 -0
- package/dist/generators/openapi-json/schemas/index.js +18 -0
- package/dist/generators/openapi-json/schemas/index.js.map +1 -0
- package/jest.config.e2e.js +1 -0
- package/jest.config.js +1 -0
- package/jest.config.unit.js +1 -0
- package/package.json +17 -29
- package/src/base/app.loader.ts +39 -0
- package/src/base/document.file.ts +33 -0
- package/src/base/index.ts +2 -0
- package/src/commands/autodoc.cmd.ts +93 -0
- package/src/commands/openapi.variation.ts +172 -0
- package/src/generators/autodoc-md/autodoc-md.generator.ts +56 -0
- package/src/generators/autodoc-md/formatters.ts +9 -0
- package/src/generators/autodoc-md/fragments/document-fragment.abstract.ts +6 -0
- package/src/generators/autodoc-md/fragments/extractor/document.extractor.ts +73 -0
- package/src/generators/autodoc-md/fragments/implementations/additional-sources.fragment.ts +41 -0
- package/src/generators/autodoc-md/fragments/implementations/app-metadata.fragment.ts +18 -0
- package/src/generators/autodoc-md/fragments/implementations/autodoc.fragment.ts +30 -0
- package/src/generators/autodoc-md/fragments/implementations/config-section.fragment.ts +148 -0
- package/src/generators/autodoc-md/fragments/implementations/header.fragment.ts +18 -0
- package/src/generators/autodoc-md/fragments/implementations/plugin.fragment.ts +21 -0
- package/src/generators/autodoc-md/fragments/implementations/transports.fragment.ts +36 -0
- package/src/generators/autodoc-md/fragments/index.ts +9 -0
- package/src/generators/autodoc-md/index.ts +1 -0
- package/src/generators/file-generator.interface.ts +22 -0
- package/src/generators/index.ts +4 -0
- package/src/generators/openapi-json/config.schema.spec.ts +265 -0
- package/src/generators/openapi-json/config.schema.ts +37 -0
- package/src/generators/openapi-json/index.ts +2 -0
- package/src/generators/openapi-json/openapi-json.generator.ts +53 -0
- package/src/generators/openapi-json/schemas/config.schema.spec.ts +265 -0
- package/src/generators/openapi-json/schemas/config.schema.ts +37 -0
- package/src/generators/openapi-json/schemas/index.ts +1 -0
- package/src/generators/rsdk-json/index.ts +1 -0
- package/src/generators/rsdk-json/rsdk-json.generator.ts +24 -0
- package/src/index.ts +1 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommandVariation,
|
|
3
|
+
IRunnable,
|
|
4
|
+
Option,
|
|
5
|
+
ValueOption,
|
|
6
|
+
} from '@rsdk/cli.common';
|
|
7
|
+
import { ArrayParser, StringParser, text } from '@rsdk/common';
|
|
8
|
+
import { FsPathParser, Path, readObj } from '@rsdk/common.node';
|
|
9
|
+
import { ILogger, LoggerFactory } from '@rsdk/logging';
|
|
10
|
+
import { Value } from '@sinclair/typebox/value';
|
|
11
|
+
|
|
12
|
+
import { AppLoader } from '../base/app.loader';
|
|
13
|
+
import { DocumentWriter } from '../base/document.file';
|
|
14
|
+
import {
|
|
15
|
+
OpenApiConfigSchema,
|
|
16
|
+
OpenApiFileGenerator,
|
|
17
|
+
OpenApiGeneratorOptions,
|
|
18
|
+
} from '../generators';
|
|
19
|
+
|
|
20
|
+
interface OpenApiConfig extends Partial<OpenApiGeneratorOptions> {
|
|
21
|
+
out?: string;
|
|
22
|
+
app?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@CommandVariation('autodoc:openapi', {
|
|
26
|
+
description: 'generate open api',
|
|
27
|
+
})
|
|
28
|
+
export class OpenApiCommand implements IRunnable {
|
|
29
|
+
private readonly logger: ILogger;
|
|
30
|
+
private readonly loader: AppLoader;
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
this.logger = LoggerFactory.create(OpenApiCommand);
|
|
34
|
+
this.loader = new AppLoader(this.logger);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async run(
|
|
38
|
+
@Option('verbose', { description: 'Verbose output', defaultValue: false })
|
|
39
|
+
verbose: boolean,
|
|
40
|
+
|
|
41
|
+
@Option('if-present', {
|
|
42
|
+
description: 'Not fail if not provided app file',
|
|
43
|
+
defaultValue: false,
|
|
44
|
+
})
|
|
45
|
+
ifPresent: boolean,
|
|
46
|
+
|
|
47
|
+
@ValueOption('out', new FsPathParser('file', { check: 'dirname' }), {
|
|
48
|
+
description: 'output file',
|
|
49
|
+
alias: 'o',
|
|
50
|
+
})
|
|
51
|
+
outPath?: string,
|
|
52
|
+
|
|
53
|
+
@ValueOption('app', new FsPathParser('file'), {
|
|
54
|
+
description: 'path to app.js file',
|
|
55
|
+
alias: 'a',
|
|
56
|
+
})
|
|
57
|
+
appPath?: string,
|
|
58
|
+
|
|
59
|
+
@ValueOption('conf', new FsPathParser('file'), {
|
|
60
|
+
description: text`
|
|
61
|
+
Path to configuration file (YAML or JSON) containing any of the command options.
|
|
62
|
+
Command line options take precedence over config file values.
|
|
63
|
+
`,
|
|
64
|
+
})
|
|
65
|
+
configPath?: string,
|
|
66
|
+
|
|
67
|
+
@ValueOption('include-zones', new ArrayParser(new StringParser()), {
|
|
68
|
+
description: text`
|
|
69
|
+
API zones to include. Only controllers with these zones will be included in the output.
|
|
70
|
+
Zones are specified as a comma-separated list.
|
|
71
|
+
`,
|
|
72
|
+
})
|
|
73
|
+
includeZones?: string[],
|
|
74
|
+
|
|
75
|
+
@ValueOption('exclude-zones', new ArrayParser(new StringParser()), {
|
|
76
|
+
description: text`
|
|
77
|
+
API zones to exclude. Only controllers without these zones will be included in the output.
|
|
78
|
+
Zones are specified as a comma-separated list.
|
|
79
|
+
`,
|
|
80
|
+
})
|
|
81
|
+
excludeZones?: string[],
|
|
82
|
+
|
|
83
|
+
@ValueOption('title', new StringParser(), {
|
|
84
|
+
description: text`
|
|
85
|
+
The title of OpenAPI document. Takes precedence over config file and app metadata.
|
|
86
|
+
`,
|
|
87
|
+
})
|
|
88
|
+
title?: string,
|
|
89
|
+
|
|
90
|
+
@ValueOption('version', new StringParser(), {
|
|
91
|
+
description: text`
|
|
92
|
+
The version of OpenAPI document. Takes precedence over config file and app metadata.
|
|
93
|
+
`,
|
|
94
|
+
})
|
|
95
|
+
version?: string,
|
|
96
|
+
|
|
97
|
+
@ValueOption('description', new StringParser(), {
|
|
98
|
+
description: text`
|
|
99
|
+
The description of OpenAPI document. Takes precedence over config file and app metadata.
|
|
100
|
+
`,
|
|
101
|
+
})
|
|
102
|
+
description?: string,
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
if (verbose) {
|
|
105
|
+
this.logger.info(`app path: ${appPath}`);
|
|
106
|
+
this.logger.info(`output file: ${outPath}`);
|
|
107
|
+
this.logger.info(`--if-present: ${ifPresent}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const config = await this.readConfig(configPath);
|
|
111
|
+
|
|
112
|
+
// Command line options take precedence over config file
|
|
113
|
+
const effectiveOutPath = outPath ?? config.out ?? 'openapi.json';
|
|
114
|
+
const effectiveAppPath = appPath ?? config.app ?? './dist/app.js';
|
|
115
|
+
|
|
116
|
+
if (verbose) {
|
|
117
|
+
this.logger.info(`effective out path: ${effectiveOutPath}`);
|
|
118
|
+
this.logger.info(`effective app path: ${effectiveAppPath}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const app = await this.loader.loadApp(effectiveAppPath, !ifPresent);
|
|
122
|
+
if (!app) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const [absolutePath] = Path.absolutize(effectiveOutPath);
|
|
127
|
+
const [dirname, filename] = Path.split(absolutePath);
|
|
128
|
+
|
|
129
|
+
const options: Partial<OpenApiGeneratorOptions> = {
|
|
130
|
+
...config,
|
|
131
|
+
...(includeZones && { includeZones }),
|
|
132
|
+
...(excludeZones && { excludeZones }),
|
|
133
|
+
...(title && { title }),
|
|
134
|
+
...(description && { description }),
|
|
135
|
+
...(version && { version }),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (verbose) {
|
|
139
|
+
this.logger.info('override options', options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const generator = new OpenApiFileGenerator(app, options);
|
|
143
|
+
const writer = new DocumentWriter(this.logger, dirname);
|
|
144
|
+
|
|
145
|
+
const document = await generator.createFile(filename);
|
|
146
|
+
|
|
147
|
+
await writer.write(document);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private async readConfig(
|
|
151
|
+
configPath: string | undefined,
|
|
152
|
+
): Promise<Partial<OpenApiConfig>> {
|
|
153
|
+
if (!configPath) {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.logger.info(`reading config file: ${configPath}`);
|
|
158
|
+
|
|
159
|
+
const [absolutePath] = Path.absolutize(configPath);
|
|
160
|
+
const config = await readObj(absolutePath);
|
|
161
|
+
|
|
162
|
+
if (!Value.Check(OpenApiConfigSchema, config)) {
|
|
163
|
+
const errors = [...Value.Errors(OpenApiConfigSchema, config)];
|
|
164
|
+
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Invalid config file: ${configPath}\n${JSON.stringify(errors, null, 2)}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return config;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DocumentNode } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import { Composite } from '@rsdk/autodoc.protocol';
|
|
3
|
+
import type { PlatformApp } from '@rsdk/core';
|
|
4
|
+
import { LoggerFactory } from '@rsdk/logging';
|
|
5
|
+
|
|
6
|
+
import { FileGenerator } from '../file-generator.interface';
|
|
7
|
+
|
|
8
|
+
import type { DocumentFragment } from './fragments';
|
|
9
|
+
import {
|
|
10
|
+
AdditionalSourcesFragment,
|
|
11
|
+
AppMetadataFragment,
|
|
12
|
+
AutodocMetadataFragment,
|
|
13
|
+
ConfigSectionFragment,
|
|
14
|
+
HeaderFragment,
|
|
15
|
+
PluginFragment,
|
|
16
|
+
TransportsFragment,
|
|
17
|
+
} from './fragments';
|
|
18
|
+
|
|
19
|
+
export class MarkdownDocsGenerator extends FileGenerator {
|
|
20
|
+
constructor(private readonly app: PlatformApp) {
|
|
21
|
+
super(LoggerFactory.create(MarkdownDocsGenerator));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
protected async getContent(): Promise<string> {
|
|
25
|
+
this.logger.info('Start generating documentation');
|
|
26
|
+
|
|
27
|
+
const structure = await this.getStructure();
|
|
28
|
+
|
|
29
|
+
const nodes = await Promise.all(
|
|
30
|
+
structure.map((item) => item.getDocumentNode()),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const metadata = await this.app.getMetadata();
|
|
34
|
+
const content = await new Composite(
|
|
35
|
+
nodes.filter((node): node is DocumentNode => node !== null),
|
|
36
|
+
'Application: ' + metadata.name,
|
|
37
|
+
).toString();
|
|
38
|
+
|
|
39
|
+
this.logger.info('Generating documentation finished');
|
|
40
|
+
return content;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async getStructure(): Promise<DocumentFragment[]> {
|
|
44
|
+
const metadata = await this.app.getMetadata();
|
|
45
|
+
|
|
46
|
+
return [
|
|
47
|
+
new HeaderFragment(this.app),
|
|
48
|
+
new AppMetadataFragment(this.app),
|
|
49
|
+
new PluginFragment(this.app.platformAppOptions.plugins),
|
|
50
|
+
new TransportsFragment(this.app.platformAppOptions.transports),
|
|
51
|
+
new AdditionalSourcesFragment(metadata.config.sources),
|
|
52
|
+
new ConfigSectionFragment(metadata),
|
|
53
|
+
new AutodocMetadataFragment(this.app),
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Abstract,
|
|
3
|
+
ClassProvider,
|
|
4
|
+
DynamicModule,
|
|
5
|
+
ExistingProvider,
|
|
6
|
+
FactoryProvider,
|
|
7
|
+
Type,
|
|
8
|
+
ValueProvider,
|
|
9
|
+
} from '@nestjs/common';
|
|
10
|
+
import type { DocumentResolver } from '@rsdk/autodoc.protocol';
|
|
11
|
+
import { AutodocMetadata } from '@rsdk/autodoc.protocol';
|
|
12
|
+
import { NestAssert, RsdkMetadataProvider } from '@rsdk/metadata';
|
|
13
|
+
import { NestDefinitionIterator } from '@rsdk/nest-tools';
|
|
14
|
+
import type { Promisable } from 'type-fest';
|
|
15
|
+
|
|
16
|
+
type DocumentResolverMap = Map<string, DocumentResolver>;
|
|
17
|
+
|
|
18
|
+
export class DocumentExtractor {
|
|
19
|
+
constructor(readonly module: Promisable<DynamicModule | Type>) {}
|
|
20
|
+
|
|
21
|
+
async getDocumentResolverMap(): Promise<DocumentResolverMap> {
|
|
22
|
+
return this.recursiveFillDocumentMap();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Рекурсивно проходит по дереву объявлений неста и добавляет все AutodocResolver в `map`
|
|
27
|
+
*/
|
|
28
|
+
private async recursiveFillDocumentMap(
|
|
29
|
+
autodocResolverMap: DocumentResolverMap = new Map(),
|
|
30
|
+
): Promise<DocumentResolverMap> {
|
|
31
|
+
const nestDefinitionIterator = new NestDefinitionIterator(this.module);
|
|
32
|
+
|
|
33
|
+
for await (const nestDef of nestDefinitionIterator.iterate()) {
|
|
34
|
+
if (!NestAssert.isObjectOrCtor(nestDef)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
for (const value of RsdkMetadataProvider.getMetadataSource(nestDef)) {
|
|
38
|
+
if (!NestAssert.isObjectOrCtor(value)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
this.fillDocumentMap(value, autodocResolverMap);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return autodocResolverMap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Извлекает autodoc-метаданные из definition и добавляет их в map
|
|
50
|
+
*/
|
|
51
|
+
private fillDocumentMap(
|
|
52
|
+
definition:
|
|
53
|
+
| DynamicModule
|
|
54
|
+
| Type<any>
|
|
55
|
+
| ClassProvider<any>
|
|
56
|
+
| ValueProvider<any>
|
|
57
|
+
| FactoryProvider<any>
|
|
58
|
+
| ExistingProvider<any>
|
|
59
|
+
| Abstract<any>
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
61
|
+
| Function,
|
|
62
|
+
extractorMap: DocumentResolverMap,
|
|
63
|
+
): void {
|
|
64
|
+
const resolvers = AutodocMetadata.getResolvers(definition);
|
|
65
|
+
|
|
66
|
+
if (!resolvers) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const resolver of resolvers) {
|
|
70
|
+
extractorMap.set(resolver.scope, resolver.resolver);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Composite } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import type { SourceMetadata } from '@rsdk/core';
|
|
3
|
+
|
|
4
|
+
import { DocumentFragment } from '../document-fragment.abstract';
|
|
5
|
+
|
|
6
|
+
export class AdditionalSourcesFragment extends DocumentFragment {
|
|
7
|
+
constructor(private sources?: SourceMetadata[]) {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getContent(): string {
|
|
12
|
+
if (!this.sources?.length) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
const fragments: string[] = [];
|
|
16
|
+
|
|
17
|
+
fragments.push('## Additional Sources', '\n');
|
|
18
|
+
|
|
19
|
+
for (const metadata of this.sources) {
|
|
20
|
+
fragments.push(
|
|
21
|
+
`### ${metadata.name}`,
|
|
22
|
+
`${metadata.name} ${metadata.description}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return fragments.join('\n');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getDocumentNode(): Composite | null {
|
|
29
|
+
if (!this.sources) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const sourceNodes = this.sources.map((metadata) => {
|
|
33
|
+
return new Composite(
|
|
34
|
+
[`${metadata.name} ${metadata.description}`],
|
|
35
|
+
metadata.name,
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return new Composite(sourceNodes, 'AdditionalSources');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DocumentNode } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import { Composite } from '@rsdk/autodoc.protocol';
|
|
3
|
+
import type { PlatformApp } from '@rsdk/core';
|
|
4
|
+
|
|
5
|
+
import type { DocumentFragment } from '../document-fragment.abstract';
|
|
6
|
+
|
|
7
|
+
export class AppMetadataFragment implements DocumentFragment {
|
|
8
|
+
constructor(private readonly app: PlatformApp) {}
|
|
9
|
+
|
|
10
|
+
async getDocumentNode(): Promise<DocumentNode[] | DocumentNode | null> {
|
|
11
|
+
const metadata = await this.app.getMetadata();
|
|
12
|
+
|
|
13
|
+
if (!metadata.description) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return new Composite(metadata.description, 'Description');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DocumentNode } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import { Composite } from '@rsdk/autodoc.protocol';
|
|
3
|
+
import type { PlatformApp } from '@rsdk/core';
|
|
4
|
+
|
|
5
|
+
import type { DocumentFragment } from '../document-fragment.abstract';
|
|
6
|
+
import { DocumentExtractor } from '../extractor/document.extractor';
|
|
7
|
+
|
|
8
|
+
export class AutodocMetadataFragment implements DocumentFragment {
|
|
9
|
+
constructor(readonly app: PlatformApp) {}
|
|
10
|
+
|
|
11
|
+
async getDocumentNode(): Promise<DocumentNode> {
|
|
12
|
+
const root = this.app.context.getRoot();
|
|
13
|
+
const resolvers = await new DocumentExtractor(
|
|
14
|
+
root,
|
|
15
|
+
).getDocumentResolverMap();
|
|
16
|
+
|
|
17
|
+
const nodes: DocumentNode[] = [];
|
|
18
|
+
|
|
19
|
+
const rsdkMetadataProvider =
|
|
20
|
+
await this.app.context.getRsdkMetadataProvider();
|
|
21
|
+
|
|
22
|
+
for (const resolver of resolvers.values()) {
|
|
23
|
+
const node = await resolver.getNode(rsdkMetadataProvider);
|
|
24
|
+
|
|
25
|
+
node && nodes.push(node);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return new Composite(nodes);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { DocumentNode } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import { Composite, Table } from '@rsdk/autodoc.protocol';
|
|
3
|
+
import type {
|
|
4
|
+
PlatformAppMetadata,
|
|
5
|
+
SerializablePropertyMetadata,
|
|
6
|
+
SerializableSectionMetadata,
|
|
7
|
+
} from '@rsdk/core';
|
|
8
|
+
import { get, set } from 'lodash';
|
|
9
|
+
|
|
10
|
+
import { MdFormatter } from '../../formatters';
|
|
11
|
+
import { DocumentFragment } from '../document-fragment.abstract';
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Этот тип представляет собой древовидную структуру, где каждый ключ может
|
|
15
|
+
* быть либо массивом SerializableSectionMetadata,
|
|
16
|
+
* либо другим GroupedSectionTree, что позволяет создавать вложенные секции в конфигурации.
|
|
17
|
+
*/
|
|
18
|
+
type GroupedSectionTree = {
|
|
19
|
+
[key in string]: SerializableSectionMetadata[] | GroupedSectionTree;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const APPLICATION_SECTION_NAME = 'Application';
|
|
23
|
+
|
|
24
|
+
export class ConfigSectionFragment extends DocumentFragment {
|
|
25
|
+
constructor(private metadata: PlatformAppMetadata) {
|
|
26
|
+
super();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private static getSectionNode(
|
|
30
|
+
section: SerializableSectionMetadata,
|
|
31
|
+
): Composite {
|
|
32
|
+
const content: (DocumentNode | string)[] = [];
|
|
33
|
+
|
|
34
|
+
if (section.description) {
|
|
35
|
+
content.push(section.description);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
content.push(
|
|
39
|
+
new Table(
|
|
40
|
+
section.properties.map((property) => ({
|
|
41
|
+
Name: property.expectedInEnv
|
|
42
|
+
? MdFormatter.bold(property.key)
|
|
43
|
+
: property.key,
|
|
44
|
+
Type: property.parser.type,
|
|
45
|
+
'Default value': `${property.defaultValue}`,
|
|
46
|
+
Description: property.description,
|
|
47
|
+
})),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const nodeTitle = section.name
|
|
52
|
+
? `${section.name} (${MdFormatter.italic(section.constructorName)})`
|
|
53
|
+
: section.constructorName;
|
|
54
|
+
|
|
55
|
+
return new Composite(content, nodeTitle);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private static getSectionNodeTree(
|
|
59
|
+
groupedSectionTree: GroupedSectionTree,
|
|
60
|
+
): Composite[] {
|
|
61
|
+
const sectionNodes: Composite[] = [];
|
|
62
|
+
|
|
63
|
+
for (const [key, value] of Object.entries(groupedSectionTree)) {
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
sectionNodes.push(
|
|
66
|
+
new Composite(
|
|
67
|
+
value.map((section) =>
|
|
68
|
+
ConfigSectionFragment.getSectionNode(section),
|
|
69
|
+
),
|
|
70
|
+
key,
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
sectionNodes.push(new Composite(this.getSectionNodeTree(value), key));
|
|
76
|
+
}
|
|
77
|
+
return sectionNodes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getDocumentNode(): Composite | null {
|
|
81
|
+
const {
|
|
82
|
+
config: { properties },
|
|
83
|
+
} = this.metadata;
|
|
84
|
+
|
|
85
|
+
const title = 'Configuration properties';
|
|
86
|
+
|
|
87
|
+
const printProperty = (meta: SerializablePropertyMetadata): Composite => {
|
|
88
|
+
const { key, defaultValue, description, parser, expectedInEnv } = meta;
|
|
89
|
+
const fragments: string[] = [];
|
|
90
|
+
|
|
91
|
+
fragments.push(
|
|
92
|
+
`- ${key} ${description}`,
|
|
93
|
+
`> Parser: **${parser.type}** ${parser.description}`,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if ('defaultValue' in meta) {
|
|
97
|
+
fragments.push(`> Default value: ${defaultValue}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (expectedInEnv) {
|
|
101
|
+
fragments.push(`> Should be in environment!`);
|
|
102
|
+
}
|
|
103
|
+
fragments.push('');
|
|
104
|
+
return new Composite(fragments.join('\n'));
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const sectionsNode = this.getSectionsNode();
|
|
108
|
+
|
|
109
|
+
const content = [sectionsNode];
|
|
110
|
+
|
|
111
|
+
if (properties.length > 0) {
|
|
112
|
+
const title = `Unbound properties`;
|
|
113
|
+
const propertyNodeContent: DocumentNode[] = [];
|
|
114
|
+
const propertiesNode = new Composite(propertyNodeContent, title);
|
|
115
|
+
|
|
116
|
+
for (const propMeta of properties) {
|
|
117
|
+
const propertyNode = printProperty(propMeta);
|
|
118
|
+
|
|
119
|
+
propertyNodeContent.push(propertyNode);
|
|
120
|
+
}
|
|
121
|
+
content.push(propertiesNode);
|
|
122
|
+
}
|
|
123
|
+
return new Composite(content, title);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private getSectionsNode(): Composite {
|
|
127
|
+
const groupByTags: { [key in string]: SerializableSectionMetadata[] } = {};
|
|
128
|
+
|
|
129
|
+
for (const section of this.metadata.config.sections) {
|
|
130
|
+
if (section.tags?.length) {
|
|
131
|
+
const existed = get(groupByTags, section.tags.join('.'), [section]);
|
|
132
|
+
|
|
133
|
+
set(groupByTags, section.tags.join('.'), existed);
|
|
134
|
+
} else {
|
|
135
|
+
groupByTags[APPLICATION_SECTION_NAME] ??= <
|
|
136
|
+
SerializableSectionMetadata[]
|
|
137
|
+
>[];
|
|
138
|
+
|
|
139
|
+
groupByTags[APPLICATION_SECTION_NAME].push(section);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const content: DocumentNode[] =
|
|
144
|
+
ConfigSectionFragment.getSectionNodeTree(groupByTags);
|
|
145
|
+
|
|
146
|
+
return new Composite(content, 'Sections');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DocumentNode } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import { Composite } from '@rsdk/autodoc.protocol';
|
|
3
|
+
import type { PlatformApp } from '@rsdk/core';
|
|
4
|
+
|
|
5
|
+
import type { DocumentFragment } from '../document-fragment.abstract';
|
|
6
|
+
|
|
7
|
+
export class HeaderFragment implements DocumentFragment {
|
|
8
|
+
constructor(readonly app: PlatformApp) {}
|
|
9
|
+
|
|
10
|
+
async getDocumentNode(): Promise<DocumentNode[] | DocumentNode | null> {
|
|
11
|
+
const metadata = await this.app.getMetadata();
|
|
12
|
+
const appVersion = metadata.version;
|
|
13
|
+
|
|
14
|
+
const headerFragments = [`version: ${appVersion}`];
|
|
15
|
+
|
|
16
|
+
return new Composite(headerFragments);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DocumentNode } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import { Composite, List } from '@rsdk/autodoc.protocol';
|
|
3
|
+
import type { PlatformAppPlugin } from '@rsdk/core';
|
|
4
|
+
|
|
5
|
+
import { DocumentFragment } from '../document-fragment.abstract';
|
|
6
|
+
|
|
7
|
+
export class PluginFragment extends DocumentFragment {
|
|
8
|
+
constructor(private plugins?: PlatformAppPlugin[]) {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getDocumentNode(): DocumentNode | null {
|
|
13
|
+
if (!this.plugins?.length) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return new Composite(
|
|
17
|
+
new List(this.plugins?.map((plugin) => plugin.constructor.name)),
|
|
18
|
+
'Plugins',
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Composite, List } from '@rsdk/autodoc.protocol';
|
|
2
|
+
import type { ITransport } from '@rsdk/core';
|
|
3
|
+
|
|
4
|
+
import { DocumentFragment } from '../document-fragment.abstract';
|
|
5
|
+
|
|
6
|
+
export class TransportsFragment extends DocumentFragment {
|
|
7
|
+
constructor(private transports?: ITransport[]) {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getContent(): string {
|
|
12
|
+
if (!this.transports?.length) {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
const fragments: string[] = [];
|
|
16
|
+
|
|
17
|
+
fragments.push('## Transports', '\n');
|
|
18
|
+
for (const transport of this.transports) {
|
|
19
|
+
fragments.push('- ' + transport.getProtocol());
|
|
20
|
+
}
|
|
21
|
+
return fragments.join('\n');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getDocumentNode(): Composite | null {
|
|
25
|
+
if (!this.transports?.length) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const transportNode = new Composite(
|
|
30
|
+
new List(this.transports.map((tr) => tr.getProtocol())),
|
|
31
|
+
'Transports',
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return transportNode;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './implementations/config-section.fragment';
|
|
2
|
+
export * from './implementations/app-metadata.fragment';
|
|
3
|
+
export * from './implementations/config-section.fragment';
|
|
4
|
+
export * from './implementations/transports.fragment';
|
|
5
|
+
export * from './implementations/autodoc.fragment';
|
|
6
|
+
export * from './implementations/plugin.fragment';
|
|
7
|
+
export * from './implementations/additional-sources.fragment';
|
|
8
|
+
export * from './implementations/header.fragment';
|
|
9
|
+
export * from './document-fragment.abstract';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './autodoc-md.generator';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ILogger } from '@rsdk/logging';
|
|
2
|
+
|
|
3
|
+
import type { DocumentFile } from '../base';
|
|
4
|
+
|
|
5
|
+
export abstract class FileGenerator {
|
|
6
|
+
constructor(protected readonly logger: ILogger) {}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Принимает имя файла и возвращает абстракцию файла
|
|
10
|
+
* @param filename - Имя файла
|
|
11
|
+
* @returns {DocumentFile} Объект файла
|
|
12
|
+
*/
|
|
13
|
+
async createFile(filename: string): Promise<DocumentFile> {
|
|
14
|
+
this.logger.info(`started generating ${filename}`);
|
|
15
|
+
const content = await this.getContent();
|
|
16
|
+
|
|
17
|
+
this.logger.info(`finished generating ${filename}`);
|
|
18
|
+
return { name: filename, content };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected abstract getContent(): Promise<string>;
|
|
22
|
+
}
|