nestjs-openapi 0.1.0
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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.mjs +139 -0
- package/dist/index.d.mts +896 -0
- package/dist/index.d.ts +896 -0
- package/dist/index.mjs +297 -0
- package/dist/internal.d.mts +2 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.mjs +53 -0
- package/dist/shared/nestjs-openapi.B1bBy_tG.mjs +1529 -0
- package/dist/shared/nestjs-openapi.BYUrTaMo.d.mts +355 -0
- package/dist/shared/nestjs-openapi.BYUrTaMo.d.ts +355 -0
- package/dist/shared/nestjs-openapi.DlNMM8Zq.mjs +1831 -0
- package/package.json +112 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
export { c as categorizeBrokenRefs, d as defineConfig, f as findConfigFile, e as formatValidationResult, g as generate, b as loadAndResolveConfig, a as loadConfig, l as loadConfigFromFile, r as resolveConfig, v as validateSpec } from './shared/nestjs-openapi.DlNMM8Zq.mjs';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { Module } from '@nestjs/common';
|
|
5
|
+
export { generateAsync, generate as generateEffect } from './internal.mjs';
|
|
6
|
+
import { P as ProjectInitError, E as EntryNotFoundError } from './shared/nestjs-openapi.B1bBy_tG.mjs';
|
|
7
|
+
export { a as ConfigLoadError, C as ConfigNotFoundError, b as ConfigValidationError, I as InvalidMethodError, c as getAllControllers, q as getArrayInitializer, o as getControllerMethodInfos, e as getControllerName, d as getControllerPrefix, j as getControllerTags, h as getDecoratorName, k as getHttpDecorator, f as getHttpMethods, m as getMethodInfo, w as getModuleDecoratorArg, z as getModuleMetadata, g as getModules, s as getStringLiteralValue, u as getSymbolFromIdentifier, l as isHttpDecorator, i as isHttpMethod, v as isModuleClass, n as normalizePath, y as resolveArrayOfClasses, x as resolveClassFromExpression, r as resolveClassFromSymbol, t as transformMethod, p as transformMethods } from './shared/nestjs-openapi.B1bBy_tG.mjs';
|
|
8
|
+
import { Context, Effect, Layer } from 'effect';
|
|
9
|
+
import { Project } from 'ts-morph';
|
|
10
|
+
import 'glob';
|
|
11
|
+
import 'js-yaml';
|
|
12
|
+
import 'ts-json-schema-generator';
|
|
13
|
+
import 'node:crypto';
|
|
14
|
+
import 'node:url';
|
|
15
|
+
import 'child_process';
|
|
16
|
+
|
|
17
|
+
var __create = Object.create;
|
|
18
|
+
var __defProp = Object.defineProperty;
|
|
19
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
20
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
21
|
+
var __typeError = (msg) => {
|
|
22
|
+
throw TypeError(msg);
|
|
23
|
+
};
|
|
24
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
25
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
26
|
+
var __decoratorStart = (base) => [, , , __create(null)];
|
|
27
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
28
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
29
|
+
var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
30
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
31
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
32
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
|
|
33
|
+
return value;
|
|
34
|
+
};
|
|
35
|
+
var __decorateElement = (array, flags, name, decorators, target, extra) => {
|
|
36
|
+
var it, done, ctx, k = flags & 7, p = false;
|
|
37
|
+
var j = 0;
|
|
38
|
+
var extraInitializers = array[j] || (array[j] = []);
|
|
39
|
+
var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(target , name));
|
|
40
|
+
__name(target, name);
|
|
41
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
42
|
+
ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
|
|
43
|
+
it = (0, decorators[i])(target, ctx), done._ = 1;
|
|
44
|
+
__expectFn(it) && (target = it);
|
|
45
|
+
}
|
|
46
|
+
return __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
47
|
+
};
|
|
48
|
+
var _OpenApiModule_decorators, _init;
|
|
49
|
+
const PATH_METADATA = "path";
|
|
50
|
+
const METHOD_METADATA = "method";
|
|
51
|
+
const HEADERS_METADATA = "__headers__";
|
|
52
|
+
const CONTROLLER_WATERMARK = "__controller__";
|
|
53
|
+
const OPENAPI_MODULE_OPTIONS = Symbol("OPENAPI_MODULE_OPTIONS");
|
|
54
|
+
const OPENAPI_SPEC = Symbol("OPENAPI_SPEC");
|
|
55
|
+
function escapeHtml(text) {
|
|
56
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
57
|
+
}
|
|
58
|
+
function generateSwaggerUiHtml(title, jsonPath) {
|
|
59
|
+
return `<!DOCTYPE html>
|
|
60
|
+
<html lang="en">
|
|
61
|
+
<head>
|
|
62
|
+
<meta charset="UTF-8">
|
|
63
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
64
|
+
<title>${escapeHtml(title)}</title>
|
|
65
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
|
66
|
+
<style>
|
|
67
|
+
html { box-sizing: border-box; overflow-y: scroll; }
|
|
68
|
+
*, *:before, *:after { box-sizing: inherit; }
|
|
69
|
+
body { margin: 0; background: #fafafa; }
|
|
70
|
+
</style>
|
|
71
|
+
</head>
|
|
72
|
+
<body>
|
|
73
|
+
<div id="swagger-ui"></div>
|
|
74
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"><\/script>
|
|
75
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-standalone-preset.js"><\/script>
|
|
76
|
+
<script>
|
|
77
|
+
window.onload = function() {
|
|
78
|
+
SwaggerUIBundle({
|
|
79
|
+
url: "${escapeHtml(jsonPath)}",
|
|
80
|
+
dom_id: '#swagger-ui',
|
|
81
|
+
deepLinking: true,
|
|
82
|
+
presets: [
|
|
83
|
+
SwaggerUIBundle.presets.apis,
|
|
84
|
+
SwaggerUIStandalonePreset
|
|
85
|
+
],
|
|
86
|
+
plugins: [
|
|
87
|
+
SwaggerUIBundle.plugins.DownloadUrl
|
|
88
|
+
],
|
|
89
|
+
layout: "StandaloneLayout"
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
<\/script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>`;
|
|
95
|
+
}
|
|
96
|
+
function loadSpecFile(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
const resolvedPath = resolve(process.cwd(), filePath);
|
|
99
|
+
const content = readFileSync(resolvedPath, "utf-8");
|
|
100
|
+
return JSON.parse(content);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error instanceof Error) {
|
|
103
|
+
if ("code" in error && error.code === "ENOENT") {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`OpenAPI spec file not found: ${filePath}. Make sure to run 'nestjs-openapi generate' first.`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`Failed to load OpenAPI spec: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function resolveOptions(options) {
|
|
114
|
+
let swaggerEnabled = false;
|
|
115
|
+
let swaggerPath = "/api-docs";
|
|
116
|
+
let swaggerTitle = "";
|
|
117
|
+
if (options.swagger === true) {
|
|
118
|
+
swaggerEnabled = true;
|
|
119
|
+
} else if (options.swagger && typeof options.swagger === "object") {
|
|
120
|
+
swaggerEnabled = true;
|
|
121
|
+
swaggerPath = options.swagger.path ?? "/api-docs";
|
|
122
|
+
swaggerTitle = options.swagger.title ?? "";
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
specFile: options.specFile,
|
|
126
|
+
enabled: options.enabled ?? true,
|
|
127
|
+
jsonPath: options.jsonPath ?? "/openapi.json",
|
|
128
|
+
swagger: {
|
|
129
|
+
enabled: swaggerEnabled,
|
|
130
|
+
path: swaggerPath,
|
|
131
|
+
title: swaggerTitle
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
_OpenApiModule_decorators = [Module({})];
|
|
136
|
+
let _OpenApiModule = class _OpenApiModule {
|
|
137
|
+
/**
|
|
138
|
+
* Configure the OpenAPI module with options.
|
|
139
|
+
*
|
|
140
|
+
* @param options - Configuration options
|
|
141
|
+
* @returns Dynamic module configuration
|
|
142
|
+
*/
|
|
143
|
+
static forRoot(options) {
|
|
144
|
+
const resolvedOptions = resolveOptions(options);
|
|
145
|
+
if (!resolvedOptions.enabled) {
|
|
146
|
+
return {
|
|
147
|
+
module: _OpenApiModule,
|
|
148
|
+
providers: [],
|
|
149
|
+
controllers: [],
|
|
150
|
+
exports: []
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const spec = loadSpecFile(resolvedOptions.specFile);
|
|
154
|
+
const finalOptions = {
|
|
155
|
+
...resolvedOptions,
|
|
156
|
+
swagger: {
|
|
157
|
+
...resolvedOptions.swagger,
|
|
158
|
+
title: resolvedOptions.swagger.title || spec.info.title
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
const providers = [
|
|
162
|
+
{
|
|
163
|
+
provide: OPENAPI_MODULE_OPTIONS,
|
|
164
|
+
useValue: finalOptions
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
provide: OPENAPI_SPEC,
|
|
168
|
+
useValue: spec
|
|
169
|
+
}
|
|
170
|
+
];
|
|
171
|
+
const controllers = createOpenApiControllers(finalOptions, spec);
|
|
172
|
+
return {
|
|
173
|
+
module: _OpenApiModule,
|
|
174
|
+
providers,
|
|
175
|
+
controllers,
|
|
176
|
+
exports: [OPENAPI_MODULE_OPTIONS, OPENAPI_SPEC]
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
_init = __decoratorStart();
|
|
181
|
+
_OpenApiModule = __decorateElement(_init, 0, "OpenApiModule", _OpenApiModule_decorators, _OpenApiModule);
|
|
182
|
+
__runInitializers(_init, 1, _OpenApiModule);
|
|
183
|
+
let OpenApiModule = _OpenApiModule;
|
|
184
|
+
function createJsonController(controllerPath, spec) {
|
|
185
|
+
class JsonSpecController {
|
|
186
|
+
getSpec() {
|
|
187
|
+
return spec;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
Reflect.defineMetadata(CONTROLLER_WATERMARK, true, JsonSpecController);
|
|
191
|
+
Reflect.defineMetadata(PATH_METADATA, controllerPath, JsonSpecController);
|
|
192
|
+
const method = JsonSpecController.prototype.getSpec;
|
|
193
|
+
Reflect.defineMetadata(METHOD_METADATA, 0, method);
|
|
194
|
+
Reflect.defineMetadata(PATH_METADATA, "/", method);
|
|
195
|
+
Reflect.defineMetadata(
|
|
196
|
+
HEADERS_METADATA,
|
|
197
|
+
[{ name: "Content-Type", value: "application/json" }],
|
|
198
|
+
method
|
|
199
|
+
);
|
|
200
|
+
return JsonSpecController;
|
|
201
|
+
}
|
|
202
|
+
function createSwaggerUiController(controllerPath, title, jsonPath) {
|
|
203
|
+
class SwaggerUiController {
|
|
204
|
+
getSwaggerUi() {
|
|
205
|
+
return generateSwaggerUiHtml(title, jsonPath);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
Reflect.defineMetadata(CONTROLLER_WATERMARK, true, SwaggerUiController);
|
|
209
|
+
Reflect.defineMetadata(PATH_METADATA, controllerPath, SwaggerUiController);
|
|
210
|
+
const method = SwaggerUiController.prototype.getSwaggerUi;
|
|
211
|
+
Reflect.defineMetadata(METHOD_METADATA, 0, method);
|
|
212
|
+
Reflect.defineMetadata(PATH_METADATA, "/", method);
|
|
213
|
+
Reflect.defineMetadata(
|
|
214
|
+
HEADERS_METADATA,
|
|
215
|
+
[{ name: "Content-Type", value: "text/html" }],
|
|
216
|
+
method
|
|
217
|
+
);
|
|
218
|
+
return SwaggerUiController;
|
|
219
|
+
}
|
|
220
|
+
function createOpenApiControllers(options, spec) {
|
|
221
|
+
const controllers = [];
|
|
222
|
+
const jsonController = createJsonController(options.jsonPath, spec);
|
|
223
|
+
controllers.push(jsonController);
|
|
224
|
+
if (options.swagger.enabled) {
|
|
225
|
+
const swaggerUiController = createSwaggerUiController(
|
|
226
|
+
options.swagger.path,
|
|
227
|
+
options.swagger.title,
|
|
228
|
+
options.jsonPath
|
|
229
|
+
);
|
|
230
|
+
controllers.push(swaggerUiController);
|
|
231
|
+
}
|
|
232
|
+
return controllers;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
class ProjectService extends Context.Tag("ProjectService")() {
|
|
236
|
+
}
|
|
237
|
+
const initProject = Effect.fn("ProjectService.initProject")(function* (options) {
|
|
238
|
+
return yield* Effect.try({
|
|
239
|
+
try: () => new Project({
|
|
240
|
+
tsConfigFilePath: options.tsconfig,
|
|
241
|
+
skipAddingFilesFromTsConfig: true
|
|
242
|
+
}),
|
|
243
|
+
catch: (error) => ProjectInitError.create(options.tsconfig, error)
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
const addSourceFiles = Effect.fn("ProjectService.addSourceFiles")(function* (project, entry) {
|
|
247
|
+
return yield* Effect.try({
|
|
248
|
+
try: () => {
|
|
249
|
+
project.addSourceFilesAtPaths(entry);
|
|
250
|
+
project.resolveSourceFileDependencies();
|
|
251
|
+
},
|
|
252
|
+
catch: (error) => new ProjectInitError({
|
|
253
|
+
tsconfig: "",
|
|
254
|
+
message: `Failed to add source files: ${entry}`,
|
|
255
|
+
cause: error
|
|
256
|
+
})
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
const getEntrySourceFile = Effect.fn("ProjectService.getEntrySourceFile")(
|
|
260
|
+
function* (project, entry) {
|
|
261
|
+
const sourceFile = project.getSourceFile(entry);
|
|
262
|
+
if (!sourceFile) {
|
|
263
|
+
return yield* EntryNotFoundError.fileNotFound(entry);
|
|
264
|
+
}
|
|
265
|
+
return sourceFile;
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
const getEntryClass = Effect.fn("ProjectService.getEntryClass")(function* (sourceFile, entry, className = "AppModule") {
|
|
269
|
+
const entryClass = sourceFile.getClass(className);
|
|
270
|
+
if (!entryClass) {
|
|
271
|
+
return yield* EntryNotFoundError.classNotFound(entry, className);
|
|
272
|
+
}
|
|
273
|
+
return entryClass;
|
|
274
|
+
});
|
|
275
|
+
const makeProjectContext = Effect.fn(
|
|
276
|
+
"ProjectService.makeProjectContext"
|
|
277
|
+
)(function* (options) {
|
|
278
|
+
yield* Effect.logDebug("Initializing project").pipe(
|
|
279
|
+
Effect.annotateLogs({ entry: options.entry })
|
|
280
|
+
);
|
|
281
|
+
const project = yield* initProject(options);
|
|
282
|
+
yield* addSourceFiles(project, options.entry);
|
|
283
|
+
const entrySourceFile = yield* getEntrySourceFile(project, options.entry);
|
|
284
|
+
const entryClass = yield* getEntryClass(entrySourceFile, options.entry);
|
|
285
|
+
yield* Effect.logDebug("Project initialized successfully");
|
|
286
|
+
return {
|
|
287
|
+
project,
|
|
288
|
+
entrySourceFile,
|
|
289
|
+
entryClass
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
const ProjectServiceLive = (options) => Layer.effect(
|
|
293
|
+
ProjectService,
|
|
294
|
+
makeProjectContext(options).pipe(Effect.map((context) => ({ context })))
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
export { EntryNotFoundError, OPENAPI_MODULE_OPTIONS, OPENAPI_SPEC, OpenApiModule, ProjectInitError, ProjectService, ProjectServiceLive, generateSwaggerUiHtml, loadSpecFile, makeProjectContext, resolveOptions };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { Project } from 'ts-morph';
|
|
3
|
+
import { E as EntryNotFoundError, g as getModules, o as getControllerMethodInfos, p as transformMethods } from './shared/nestjs-openapi.B1bBy_tG.mjs';
|
|
4
|
+
|
|
5
|
+
const generate = (options) => Effect.gen(function* () {
|
|
6
|
+
yield* Effect.logInfo("Starting OpenAPI generation").pipe(
|
|
7
|
+
Effect.annotateLogs({ entry: options.entry })
|
|
8
|
+
);
|
|
9
|
+
const project = new Project({
|
|
10
|
+
tsConfigFilePath: options.tsconfig,
|
|
11
|
+
skipAddingFilesFromTsConfig: true
|
|
12
|
+
});
|
|
13
|
+
project.addSourceFilesAtPaths(options.entry);
|
|
14
|
+
project.resolveSourceFileDependencies();
|
|
15
|
+
const entrySourceFile = project.getSourceFile(options.entry);
|
|
16
|
+
if (!entrySourceFile) {
|
|
17
|
+
return yield* EntryNotFoundError.fileNotFound(options.entry);
|
|
18
|
+
}
|
|
19
|
+
const entryClass = entrySourceFile.getClass("AppModule");
|
|
20
|
+
if (!entryClass) {
|
|
21
|
+
return yield* EntryNotFoundError.classNotFound(
|
|
22
|
+
options.entry,
|
|
23
|
+
"AppModule"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
const modules = yield* getModules(entryClass);
|
|
27
|
+
const methodInfos = modules.flatMap(
|
|
28
|
+
(mod) => mod.controllers.flatMap(
|
|
29
|
+
(controller) => getControllerMethodInfos(controller)
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
yield* Effect.logInfo("Collected method infos").pipe(
|
|
33
|
+
Effect.annotateLogs({
|
|
34
|
+
modules: modules.length,
|
|
35
|
+
methods: methodInfos.length
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
const paths = transformMethods(methodInfos);
|
|
39
|
+
yield* Effect.logInfo("OpenAPI generation complete").pipe(
|
|
40
|
+
Effect.annotateLogs({
|
|
41
|
+
paths: Object.keys(paths).length
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
return paths;
|
|
45
|
+
});
|
|
46
|
+
const generateAsync = async (options) => {
|
|
47
|
+
const program = generate(options).pipe(
|
|
48
|
+
Effect.mapError((error) => new Error(error.message))
|
|
49
|
+
);
|
|
50
|
+
return Effect.runPromise(program);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export { generate, generateAsync };
|