@rsdk/cli.cmd.autodoc 6.0.0-next.1 → 6.0.0-next.10
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 +15 -27
- 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,55 @@
|
|
|
1
|
+
import type { Static } from '@sinclair/typebox';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration schema for OpenAPI document generation.
|
|
4
|
+
* @example
|
|
5
|
+
* {
|
|
6
|
+
* "out": "openapi.json",
|
|
7
|
+
* "app": "./dist/app.js",
|
|
8
|
+
* "title": "My API",
|
|
9
|
+
* "version": "1.0.0",
|
|
10
|
+
* "description": "API Description",
|
|
11
|
+
* "includeZones": ["public"],
|
|
12
|
+
* "excludeZones": ["internal"],
|
|
13
|
+
* "security": {
|
|
14
|
+
* "jwt": {
|
|
15
|
+
* "type": "http",
|
|
16
|
+
* "scheme": "bearer",
|
|
17
|
+
* "bearerFormat": "JWT",
|
|
18
|
+
* "name": "JWT Auth"
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
export declare const OpenApiConfigSchema: import("@sinclair/typebox").TIntersect<[import("@sinclair/typebox").TObject<{
|
|
24
|
+
title: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
25
|
+
version: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
26
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
27
|
+
includeZones: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
28
|
+
excludeZones: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
29
|
+
security: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
30
|
+
type: import("@sinclair/typebox").TLiteral<"http">;
|
|
31
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
32
|
+
scheme: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"basic">, import("@sinclair/typebox").TLiteral<"bearer">]>;
|
|
33
|
+
bearerFormat: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
34
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
35
|
+
type: import("@sinclair/typebox").TLiteral<"apiKey">;
|
|
36
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
37
|
+
name: import("@sinclair/typebox").TString;
|
|
38
|
+
in: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"query">, import("@sinclair/typebox").TLiteral<"header">, import("@sinclair/typebox").TLiteral<"cookie">]>;
|
|
39
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
40
|
+
type: import("@sinclair/typebox").TLiteral<"oauth2">;
|
|
41
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
42
|
+
flows: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
43
|
+
scopes: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>;
|
|
44
|
+
authorizationUrl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
45
|
+
tokenUrl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
46
|
+
refreshUrl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
47
|
+
}>>;
|
|
48
|
+
}>]>>>;
|
|
49
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
50
|
+
/** Output file path for the generated OpenAPI document */
|
|
51
|
+
out: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
52
|
+
/** Path to the application file (usually `dist/app.js`) */
|
|
53
|
+
app: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
54
|
+
}>]>;
|
|
55
|
+
export type OpenApiConfigSchema = Static<typeof OpenApiConfigSchema>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenApiConfigSchema = void 0;
|
|
4
|
+
const http_openapi_1 = require("@rsdk/http.openapi");
|
|
5
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
6
|
+
/**
|
|
7
|
+
* Configuration schema for OpenAPI document generation.
|
|
8
|
+
* @example
|
|
9
|
+
* {
|
|
10
|
+
* "out": "openapi.json",
|
|
11
|
+
* "app": "./dist/app.js",
|
|
12
|
+
* "title": "My API",
|
|
13
|
+
* "version": "1.0.0",
|
|
14
|
+
* "description": "API Description",
|
|
15
|
+
* "includeZones": ["public"],
|
|
16
|
+
* "excludeZones": ["internal"],
|
|
17
|
+
* "security": {
|
|
18
|
+
* "jwt": {
|
|
19
|
+
* "type": "http",
|
|
20
|
+
* "scheme": "bearer",
|
|
21
|
+
* "bearerFormat": "JWT",
|
|
22
|
+
* "name": "JWT Auth"
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
exports.OpenApiConfigSchema = typebox_1.Type.Intersect([
|
|
28
|
+
typebox_1.Type.Partial(http_openapi_1.OpenApiOptions),
|
|
29
|
+
typebox_1.Type.Object({
|
|
30
|
+
/** Output file path for the generated OpenAPI document */
|
|
31
|
+
out: typebox_1.Type.Optional(typebox_1.Type.String()),
|
|
32
|
+
/** Path to the application file (usually `dist/app.js`) */
|
|
33
|
+
app: typebox_1.Type.Optional(typebox_1.Type.String()),
|
|
34
|
+
}),
|
|
35
|
+
]);
|
|
36
|
+
//# sourceMappingURL=config.schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.schema.js","sourceRoot":"","sources":["../../../src/generators/openapi-json/config.schema.ts"],"names":[],"mappings":";;;AAAA,qDAAoD;AAEpD,+CAAyC;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACU,QAAA,mBAAmB,GAAG,cAAI,CAAC,SAAS,CAAC;IAChD,cAAI,CAAC,OAAO,CAAC,6BAAc,CAAC;IAC5B,cAAI,CAAC,MAAM,CAAC;QACV,0DAA0D;QAC1D,GAAG,EAAE,cAAI,CAAC,QAAQ,CAAC,cAAI,CAAC,MAAM,EAAE,CAAC;QAEjC,2DAA2D;QAC3D,GAAG,EAAE,cAAI,CAAC,QAAQ,CAAC,cAAI,CAAC,MAAM,EAAE,CAAC;KAClC,CAAC;CACH,CAAC,CAAC"}
|
|
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./openapi-json.generator"), exports);
|
|
18
|
+
__exportStar(require("./config.schema"), exports);
|
|
18
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/generators/openapi-json/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2DAAyC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/generators/openapi-json/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2DAAyC;AACzC,kDAAgC"}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { PlatformApp } from '@rsdk/core';
|
|
1
|
+
import type { PlatformApp } from '@rsdk/core';
|
|
2
2
|
import { FileGenerator } from '../file-generator.interface';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
private readonly app;
|
|
3
|
+
import type { OpenApiConfigSchema } from './config.schema';
|
|
4
|
+
/**
|
|
5
|
+
* Here we don't need `out` and `app`,
|
|
6
|
+
* but `title`, `version` and `description` are required.
|
|
7
|
+
*/
|
|
8
|
+
export type OpenApiGeneratorOptions = Omit<OpenApiConfigSchema, 'out' | 'app'> & Required<Pick<OpenApiConfigSchema, 'title' | 'version' | 'description'>>;
|
|
9
|
+
export declare class OpenApiFileGenerator extends FileGenerator {
|
|
10
|
+
private readonly context;
|
|
12
11
|
private readonly options;
|
|
13
12
|
constructor(app: PlatformApp, options?: Partial<OpenApiGeneratorOptions>);
|
|
14
13
|
protected getContent(): Promise<string>;
|
|
15
|
-
private clearController;
|
|
16
14
|
private resolveOptions;
|
|
17
15
|
}
|
|
@@ -1,104 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
10
|
-
const
|
|
11
|
-
const core_1 = require("@nestjs/core");
|
|
12
|
-
const swagger_1 = require("@nestjs/swagger");
|
|
13
|
-
const core_2 = require("@rsdk/core");
|
|
3
|
+
exports.OpenApiFileGenerator = void 0;
|
|
4
|
+
const http_openapi_1 = require("@rsdk/http.openapi");
|
|
14
5
|
const logging_1 = require("@rsdk/logging");
|
|
15
|
-
const nest_tools_1 = require("@rsdk/nest-tools");
|
|
16
|
-
const zones_1 = require("@rsdk/zones");
|
|
17
6
|
const file_generator_interface_1 = require("../file-generator.interface");
|
|
18
|
-
class
|
|
19
|
-
|
|
7
|
+
class OpenApiFileGenerator extends file_generator_interface_1.FileGenerator {
|
|
8
|
+
context;
|
|
20
9
|
options;
|
|
21
10
|
constructor(app, options = {}) {
|
|
22
|
-
super(logging_1.LoggerFactory.create(
|
|
23
|
-
this.
|
|
11
|
+
super(logging_1.LoggerFactory.create(OpenApiFileGenerator));
|
|
12
|
+
this.context = app.context;
|
|
24
13
|
this.options = options;
|
|
25
14
|
}
|
|
26
15
|
async getContent() {
|
|
27
|
-
const transports = this.app.context.options.transports;
|
|
28
|
-
if (!transports?.length) {
|
|
29
|
-
throw new Error('transports not found');
|
|
30
|
-
}
|
|
31
|
-
const httpTransport = transports?.find?.(core_2.isHttpTransport);
|
|
32
|
-
if (!httpTransport) {
|
|
33
|
-
throw new Error('http transport not found');
|
|
34
|
-
}
|
|
35
16
|
this.logger.info('Generate openapi starting');
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const controllers = [];
|
|
40
|
-
const { includeZones, excludeZones } = options;
|
|
41
|
-
/**
|
|
42
|
-
* TODO: А может надо на основании чего-то другого, кроме тегов ещё?
|
|
43
|
-
* Просто теги могут быть с пробелами и с другими приколами... как вариант
|
|
44
|
-
* можно на основании имён контроллеров?
|
|
45
|
-
*/
|
|
46
|
-
this.logger.info(`includeZones: ${includeZones}`);
|
|
47
|
-
this.logger.info(`excludeZones: ${excludeZones}`);
|
|
48
|
-
for await (const controller of iterator) {
|
|
49
|
-
const zone = zones_1.Zone.getForController(controller);
|
|
50
|
-
this.logger.info(`${controller.name} zone: ${zone || 'none'}`);
|
|
51
|
-
/**
|
|
52
|
-
* Исключаем контроллеры, c зоной из excludeZones
|
|
53
|
-
*/
|
|
54
|
-
if (excludeZones && excludeZones.includes(zone)) {
|
|
55
|
-
this.logger.info(`${controller.name} excluded`);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Включаем только те контроллеры, которые имеют хотя бы один из тегов из includeTags
|
|
60
|
-
*/
|
|
61
|
-
if (includeZones && !includeZones.includes(zone)) {
|
|
62
|
-
this.logger.info(`${controller.name} skipped`);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
this.logger.info(`${controller.name} included`);
|
|
66
|
-
controllers.push(this.clearController(controller));
|
|
67
|
-
}
|
|
68
|
-
let OpenApiModule = class OpenApiModule {
|
|
69
|
-
};
|
|
70
|
-
OpenApiModule = __decorate([
|
|
71
|
-
(0, common_1.Module)({ controllers })
|
|
72
|
-
], OpenApiModule);
|
|
73
|
-
const createOptions = await this.app.context.getNestFactoryCreateOptions();
|
|
74
|
-
const app = await core_1.NestFactory.create(OpenApiModule, ...createOptions);
|
|
75
|
-
const { title, version, description } = options;
|
|
76
|
-
const builder = new swagger_1.DocumentBuilder()
|
|
77
|
-
.setTitle(title)
|
|
78
|
-
.setVersion(version)
|
|
79
|
-
.setDescription(description);
|
|
80
|
-
const openApiConfig = builder.build();
|
|
81
|
-
const document = swagger_1.SwaggerModule.createDocument(app, openApiConfig);
|
|
17
|
+
const options = await this.resolveOptions(this.context, this.options);
|
|
18
|
+
const generator = new http_openapi_1.OpenApiGenerator(this.context, options);
|
|
19
|
+
const document = await generator.generate();
|
|
82
20
|
this.logger.info('Generate openapi finished');
|
|
83
|
-
await app.close();
|
|
84
21
|
return JSON.stringify(document, null, 2);
|
|
85
22
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
const keysForRedefine = ['design:paramtypes', 'self:paramtypes'];
|
|
90
|
-
for (const forRedefineMetadataKey of keysForRedefine) {
|
|
91
|
-
Reflect.defineMetadata(forRedefineMetadataKey, [], ClearedController);
|
|
92
|
-
}
|
|
93
|
-
// Влияет на `operationId` в генерации
|
|
94
|
-
Object.defineProperty(ClearedController, 'name', {
|
|
95
|
-
value: controller.name,
|
|
96
|
-
writable: false,
|
|
97
|
-
});
|
|
98
|
-
return ClearedController;
|
|
99
|
-
}
|
|
100
|
-
async resolveOptions(app, passed) {
|
|
101
|
-
const appMetadata = await app.context.getMetadata();
|
|
23
|
+
async resolveOptions(context, passed) {
|
|
24
|
+
const appMetadata = await context.getMetadata();
|
|
102
25
|
const defaults = {
|
|
103
26
|
title: appMetadata.name,
|
|
104
27
|
version: appMetadata.version,
|
|
@@ -107,5 +30,5 @@ class OpenapiGenerator extends file_generator_interface_1.FileGenerator {
|
|
|
107
30
|
return { ...defaults, ...passed };
|
|
108
31
|
}
|
|
109
32
|
}
|
|
110
|
-
exports.
|
|
33
|
+
exports.OpenApiFileGenerator = OpenApiFileGenerator;
|
|
111
34
|
//# sourceMappingURL=openapi-json.generator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi-json.generator.js","sourceRoot":"","sources":["../../../src/generators/openapi-json/openapi-json.generator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"openapi-json.generator.js","sourceRoot":"","sources":["../../../src/generators/openapi-json/openapi-json.generator.ts"],"names":[],"mappings":";;;AACA,qDAAsD;AACtD,2CAA8C;AAE9C,0EAA4D;AAW5D,MAAa,oBAAqB,SAAQ,wCAAa;IACpC,OAAO,CAAkB;IACzB,OAAO,CAAmC;IAE3D,YACE,GAAgB,EAChB,UAA4C,EAAE;QAE9C,KAAK,CAAC,uBAAa,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAES,KAAK,CAAC,UAAU;QACxB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAE9C,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,IAAI,+BAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,CAAC;QAE5C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,CAAC;IAEO,KAAK,CAAC,cAAc,CAC1B,OAAwB,EACxB,MAAwC;QAExC,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;QAChD,MAAM,QAAQ,GAA4B;YACxC,KAAK,EAAE,WAAW,CAAC,IAAI;YACvB,OAAO,EAAE,WAAW,CAAC,OAAO;YAC5B,WAAW,EAAE,WAAW,CAAC,WAAW;SACrC,CAAC;QAEF,OAAO,EAAE,GAAG,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC;IACpC,CAAC;CACF;AArCD,oDAqCC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Static } from '@sinclair/typebox';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration schema for OpenAPI document generation.
|
|
4
|
+
* @example
|
|
5
|
+
* {
|
|
6
|
+
* "out": "openapi.json",
|
|
7
|
+
* "app": "./dist/app.js",
|
|
8
|
+
* "title": "My API",
|
|
9
|
+
* "version": "1.0.0",
|
|
10
|
+
* "description": "API Description",
|
|
11
|
+
* "includeZones": ["public"],
|
|
12
|
+
* "excludeZones": ["internal"],
|
|
13
|
+
* "security": {
|
|
14
|
+
* "jwt": {
|
|
15
|
+
* "type": "http",
|
|
16
|
+
* "scheme": "bearer",
|
|
17
|
+
* "bearerFormat": "JWT",
|
|
18
|
+
* "name": "JWT Auth"
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
export declare const OpenApiConfigSchema: import("@sinclair/typebox").TIntersect<[import("@sinclair/typebox").TObject<{
|
|
24
|
+
title: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
25
|
+
version: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
26
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
27
|
+
includeZones: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
28
|
+
excludeZones: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
29
|
+
security: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
30
|
+
type: import("@sinclair/typebox").TLiteral<"http">;
|
|
31
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
32
|
+
scheme: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"basic">, import("@sinclair/typebox").TLiteral<"bearer">]>;
|
|
33
|
+
bearerFormat: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
34
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
35
|
+
type: import("@sinclair/typebox").TLiteral<"apiKey">;
|
|
36
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
37
|
+
name: import("@sinclair/typebox").TString;
|
|
38
|
+
in: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"query">, import("@sinclair/typebox").TLiteral<"header">, import("@sinclair/typebox").TLiteral<"cookie">]>;
|
|
39
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
40
|
+
type: import("@sinclair/typebox").TLiteral<"oauth2">;
|
|
41
|
+
description: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
42
|
+
flows: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
|
|
43
|
+
scopes: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>;
|
|
44
|
+
authorizationUrl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
45
|
+
tokenUrl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
46
|
+
refreshUrl: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
47
|
+
}>>;
|
|
48
|
+
}>]>>>;
|
|
49
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
50
|
+
/** Output file path for the generated OpenAPI document */
|
|
51
|
+
out: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
52
|
+
/** Path to the application file (usually `dist/app.js`) */
|
|
53
|
+
app: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
54
|
+
}>]>;
|
|
55
|
+
export type OpenApiConfigSchema = Static<typeof OpenApiConfigSchema>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenApiConfigSchema = void 0;
|
|
4
|
+
const http_openapi_1 = require("@rsdk/http.openapi");
|
|
5
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
6
|
+
/**
|
|
7
|
+
* Configuration schema for OpenAPI document generation.
|
|
8
|
+
* @example
|
|
9
|
+
* {
|
|
10
|
+
* "out": "openapi.json",
|
|
11
|
+
* "app": "./dist/app.js",
|
|
12
|
+
* "title": "My API",
|
|
13
|
+
* "version": "1.0.0",
|
|
14
|
+
* "description": "API Description",
|
|
15
|
+
* "includeZones": ["public"],
|
|
16
|
+
* "excludeZones": ["internal"],
|
|
17
|
+
* "security": {
|
|
18
|
+
* "jwt": {
|
|
19
|
+
* "type": "http",
|
|
20
|
+
* "scheme": "bearer",
|
|
21
|
+
* "bearerFormat": "JWT",
|
|
22
|
+
* "name": "JWT Auth"
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
exports.OpenApiConfigSchema = typebox_1.Type.Intersect([
|
|
28
|
+
typebox_1.Type.Partial(http_openapi_1.OpenApiOptions),
|
|
29
|
+
typebox_1.Type.Object({
|
|
30
|
+
/** Output file path for the generated OpenAPI document */
|
|
31
|
+
out: typebox_1.Type.Optional(typebox_1.Type.String()),
|
|
32
|
+
/** Path to the application file (usually `dist/app.js`) */
|
|
33
|
+
app: typebox_1.Type.Optional(typebox_1.Type.String()),
|
|
34
|
+
}),
|
|
35
|
+
]);
|
|
36
|
+
//# sourceMappingURL=config.schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.schema.js","sourceRoot":"","sources":["../../../../src/generators/openapi-json/schemas/config.schema.ts"],"names":[],"mappings":";;;AAAA,qDAAoD;AAEpD,+CAAyC;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACU,QAAA,mBAAmB,GAAG,cAAI,CAAC,SAAS,CAAC;IAChD,cAAI,CAAC,OAAO,CAAC,6BAAc,CAAC;IAC5B,cAAI,CAAC,MAAM,CAAC;QACV,0DAA0D;QAC1D,GAAG,EAAE,cAAI,CAAC,QAAQ,CAAC,cAAI,CAAC,MAAM,EAAE,CAAC;QAEjC,2DAA2D;QAC3D,GAAG,EAAE,cAAI,CAAC,QAAQ,CAAC,cAAI,CAAC,MAAM,EAAE,CAAC;KAClC,CAAC;CACH,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './config.schema';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./config.schema"), exports);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/generators/openapi-json/schemas/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('@rsdk/jest/jest.config.e2e');
|
package/jest.config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('@rsdk/jest/jest.config');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('@rsdk/jest/jest.config.unit');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsdk/cli.cmd.autodoc",
|
|
3
|
-
"version": "6.0.0-next.
|
|
3
|
+
"version": "6.0.0-next.10",
|
|
4
4
|
"description": "autodoc your app!",
|
|
5
5
|
"homepage": "https://github.com/R-Vision/rsdk",
|
|
6
6
|
"license": "Apache License 2.0",
|
|
@@ -8,35 +8,23 @@
|
|
|
8
8
|
"access": "public"
|
|
9
9
|
},
|
|
10
10
|
"main": "dist/index.js",
|
|
11
|
-
"
|
|
12
|
-
"dist"
|
|
13
|
-
],
|
|
14
|
-
"repository": {
|
|
15
|
-
"type": "git",
|
|
16
|
-
"url": "https://github.com/R-Vision/rsdk"
|
|
17
|
-
},
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
18
12
|
"dependencies": {
|
|
19
|
-
"commander": "^12.1.0",
|
|
20
|
-
"lodash": "^4.17.21",
|
|
21
|
-
"log-update": "4.0.0",
|
|
22
|
-
"markdown-table": "2.0.0",
|
|
23
|
-
"pino-pretty": "^10.3.1"
|
|
24
|
-
},
|
|
25
|
-
"peerDependencies": {
|
|
26
13
|
"@nestjs/common": "^10.0.0",
|
|
27
14
|
"@nestjs/core": "^10.0.0",
|
|
28
15
|
"@nestjs/swagger": "^7.0.0 || ^8.0.0",
|
|
29
|
-
"@
|
|
30
|
-
"@rsdk/
|
|
31
|
-
"@rsdk/
|
|
32
|
-
"@rsdk/common": "
|
|
33
|
-
"@rsdk/
|
|
34
|
-
"@rsdk/
|
|
35
|
-
"@rsdk/logging": "
|
|
36
|
-
"@rsdk/
|
|
37
|
-
"@rsdk/
|
|
38
|
-
"@
|
|
39
|
-
"
|
|
16
|
+
"@rsdk/cli.common": "6.0.0-next.10",
|
|
17
|
+
"@rsdk/cli.core": "6.0.0-next.10",
|
|
18
|
+
"@rsdk/common": "6.0.0-next.10",
|
|
19
|
+
"@rsdk/common.node": "6.0.0-next.10",
|
|
20
|
+
"@rsdk/core": "6.0.0-next.10",
|
|
21
|
+
"@rsdk/http.openapi": "6.0.0-next.10",
|
|
22
|
+
"@rsdk/logging": "6.0.0-next.10",
|
|
23
|
+
"@rsdk/nest-tools": "6.0.0-next.10",
|
|
24
|
+
"@rsdk/zones": "6.0.0-next.10",
|
|
25
|
+
"@sinclair/typebox": "^0.34.9",
|
|
26
|
+
"lodash": "^4.17.21",
|
|
27
|
+
"yaml": "^2.6.1"
|
|
40
28
|
},
|
|
41
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "26c1a04480e6209098be6636afbde7fd357da61a"
|
|
42
30
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Assert } from '@rsdk/common';
|
|
2
|
+
import { Path } from '@rsdk/common.node';
|
|
3
|
+
import { PlatformApp } from '@rsdk/core';
|
|
4
|
+
import type { ILogger } from '@rsdk/logging';
|
|
5
|
+
|
|
6
|
+
export class AppLoader {
|
|
7
|
+
constructor(private readonly logger: ILogger) {}
|
|
8
|
+
|
|
9
|
+
async loadApp(
|
|
10
|
+
fullpath: string,
|
|
11
|
+
required: boolean,
|
|
12
|
+
): Promise<PlatformApp | undefined> {
|
|
13
|
+
let app: PlatformApp | undefined;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const [absolute] = Path.absolutize(fullpath);
|
|
17
|
+
const loaded = await import(absolute);
|
|
18
|
+
|
|
19
|
+
app = loaded.app as PlatformApp;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
Assert.isError(error);
|
|
22
|
+
this.logger.warn(`module by path ${fullpath} not found`);
|
|
23
|
+
|
|
24
|
+
if (!required) {
|
|
25
|
+
this.logger.info(`found --if-present, finished with exit code 0`);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.logger.error(`Error on import ${fullpath}`, error);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (app instanceof PlatformApp) {
|
|
34
|
+
return app;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new Error('app is not instance of PlatformApp');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ILogger } from '@rsdk/logging';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TODO: Может быть удобная абстракция для работы с файлами.
|
|
8
|
+
* Ещё в старых загашниках надо NetworkFile поискать
|
|
9
|
+
*/
|
|
10
|
+
export type DocumentFile = {
|
|
11
|
+
name: string;
|
|
12
|
+
content: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class DocumentWriter {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly logger: ILogger,
|
|
18
|
+
private readonly basepath: string,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
async write(file: DocumentFile): Promise<string> {
|
|
22
|
+
const exists = existsSync(this.basepath);
|
|
23
|
+
if (!exists) {
|
|
24
|
+
await mkdir(this.basepath, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
const fullpath = path.join(this.basepath, file.name);
|
|
27
|
+
|
|
28
|
+
this.logger.info(`writing file ${file.name}...`);
|
|
29
|
+
await writeFile(fullpath, file.content);
|
|
30
|
+
this.logger.info(`file ${file.name} created!`);
|
|
31
|
+
return fullpath;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Command, IRunnable, Option, ValueOption } from '@rsdk/cli.common';
|
|
2
|
+
import { FsPathParser, Path } from '@rsdk/common.node';
|
|
3
|
+
import { ILogger, LoggerFactory } from '@rsdk/logging';
|
|
4
|
+
import { API_ZONE_INTERNAL } from '@rsdk/zones';
|
|
5
|
+
|
|
6
|
+
import { type DocumentFile, DocumentWriter } from '../base';
|
|
7
|
+
import { AppLoader } from '../base';
|
|
8
|
+
import {
|
|
9
|
+
MarkdownDocsGenerator,
|
|
10
|
+
OpenApiFileGenerator,
|
|
11
|
+
RsdkFileGenerator,
|
|
12
|
+
} from '../generators';
|
|
13
|
+
|
|
14
|
+
import { OpenApiCommand } from './openapi.variation';
|
|
15
|
+
|
|
16
|
+
@Command('autodoc', {
|
|
17
|
+
description: 'autodoc your app',
|
|
18
|
+
variations: [OpenApiCommand],
|
|
19
|
+
})
|
|
20
|
+
export class AutodocCommand implements IRunnable {
|
|
21
|
+
private readonly logger: ILogger;
|
|
22
|
+
private readonly loader: AppLoader;
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
this.logger = LoggerFactory.create(AutodocCommand);
|
|
26
|
+
this.loader = new AppLoader(this.logger);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async run(
|
|
30
|
+
@Option('verbose', { description: 'Verbose output' })
|
|
31
|
+
verbose: boolean,
|
|
32
|
+
|
|
33
|
+
@Option('if-present', { description: 'Not fail if not provided app file' })
|
|
34
|
+
ifPresent: boolean,
|
|
35
|
+
|
|
36
|
+
@Option('open-api', {
|
|
37
|
+
description: 'generate open api (internal routes are excluded)',
|
|
38
|
+
})
|
|
39
|
+
openApi: boolean,
|
|
40
|
+
|
|
41
|
+
@ValueOption('out', new FsPathParser('dir', { check: 'dirname' }), {
|
|
42
|
+
description: 'output directory',
|
|
43
|
+
defaultValue: 'autodoc',
|
|
44
|
+
alias: 'o',
|
|
45
|
+
})
|
|
46
|
+
outPath: string,
|
|
47
|
+
|
|
48
|
+
@ValueOption('app', new FsPathParser('file'), {
|
|
49
|
+
description: 'path to app.js file',
|
|
50
|
+
defaultValue: './dist/app.js',
|
|
51
|
+
alias: 'a',
|
|
52
|
+
})
|
|
53
|
+
appPath: string,
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
if (verbose) {
|
|
56
|
+
this.logger.info(`app path: ${appPath}`);
|
|
57
|
+
this.logger.info(`output directory path: ${outPath}`);
|
|
58
|
+
this.logger.info(`--if-present: ${ifPresent}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const app = await this.loader.loadApp(appPath, !ifPresent);
|
|
62
|
+
if (!app) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const documents: DocumentFile[] = [
|
|
67
|
+
await new RsdkFileGenerator(app).createFile('rsdk.json'),
|
|
68
|
+
await new MarkdownDocsGenerator(app).createFile('autodoc.md'),
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
if (openApi) {
|
|
72
|
+
const generator = new OpenApiFileGenerator(app, {
|
|
73
|
+
excludeZones: [API_ZONE_INTERNAL],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const openApiDocument = await generator.createFile('openapi.json');
|
|
77
|
+
|
|
78
|
+
if (openApiDocument) {
|
|
79
|
+
documents.push(openApiDocument);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const [absolutePath] = Path.absolutize(outPath);
|
|
84
|
+
|
|
85
|
+
const writer = new DocumentWriter(this.logger, absolutePath);
|
|
86
|
+
|
|
87
|
+
await Promise.all(
|
|
88
|
+
documents.map(async (doc) => {
|
|
89
|
+
await writer.write(doc);
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|