nestjs-openapi-parser 0.0.1
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/README.md +213 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +54 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/defaults.d.ts +5 -0
- package/dist/config/defaults.js +14 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.js +24 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.js +160 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +139 -0
- package/dist/config/types.js +8 -0
- package/dist/config/types.js.map +1 -0
- package/dist/lib.d.ts +12 -0
- package/dist/lib.js +22 -0
- package/dist/lib.js.map +1 -0
- package/dist/parser/ast-index.d.ts +38 -0
- package/dist/parser/ast-index.js +124 -0
- package/dist/parser/ast-index.js.map +1 -0
- package/dist/parser/index.d.ts +20 -0
- package/dist/parser/index.js +95 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/path-builder.d.ts +87 -0
- package/dist/parser/path-builder.js +464 -0
- package/dist/parser/path-builder.js.map +1 -0
- package/dist/parser/schema-builder.d.ts +70 -0
- package/dist/parser/schema-builder.js +355 -0
- package/dist/parser/schema-builder.js.map +1 -0
- package/dist/parser/tags.d.ts +61 -0
- package/dist/parser/tags.js +163 -0
- package/dist/parser/tags.js.map +1 -0
- package/dist/types/openapi.d.ts +42 -0
- package/dist/types/openapi.js +3 -0
- package/dist/types/openapi.js.map +1 -0
- package/dist/validate.d.ts +13 -0
- package/dist/validate.js +30 -0
- package/dist/validate.js.map +1 -0
- package/docs/configuration.md +195 -0
- package/docs/library-usage.md +40 -0
- package/docs/parser.md +148 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# nestjs-openapi-parser
|
|
2
|
+
|
|
3
|
+
Generate an OpenAPI 3.x document from a NestJS project by **pure static analysis** of the TypeScript source. No app boot, no `reflect-metadata`, no runtime dependency on `@nestjs/*`. Just [ts-morph](https://ts-morph.com/) reading your `.ts` files.
|
|
4
|
+
|
|
5
|
+
Ships a CLI (`nestparser`) and a programmatic API.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
`@nestjs/swagger` is excellent but requires booting the application and sprinkling `@Api*` decorators everywhere.
|
|
10
|
+
`nestjs-openapi-parser` takes a different trade-off: it reads your existing code (controllers, DTOs, entities, JSDoc) and
|
|
11
|
+
produces a spec without running anything. That makes it cheap to plug into CI
|
|
12
|
+
and ergonomic when your codebase already follows clear conventions (TypeORM entities, `class-validator` DTOs, JSDoc descriptions).
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
- **Zero runtime coupling** — no `@nestjs/*` deps, no `reflect-metadata`, the parser doesn't import your code.
|
|
17
|
+
- **JSDoc-driven descriptions** — class, method and property comments become OpenAPI `description` fields.
|
|
18
|
+
- **Reachability-only schemas** — only classes reached from an endpoint end up in `components.schemas`; orphans don't leak.
|
|
19
|
+
- **Nest mapped types** — `PartialType / PickType / OmitType / IntersectionType` are resolved structurally.
|
|
20
|
+
- **`@Query() dto: DTO`** is expanded into individual query parameters.
|
|
21
|
+
- **`class-validator` constraints** — `@Min/@Max/@MinLength/@IsEmail/@IsUUID/@Matches/@IsInt`… become `minimum/maximum/minLength/format/pattern`…
|
|
22
|
+
- **Self-validating output** — every generated document is checked against the OpenAPI 3.x JSON Schema; an invalid spec throws instead of being emitted.
|
|
23
|
+
- **Pluggable hooks** for response envelopes, security resolution, and tag conventions.
|
|
24
|
+
- **Documentation variants via custom `@Scope` tag in JSDoc** — emit `public`, `internal`, `admin`… flavors of the same spec from a single source.
|
|
25
|
+
- **Tiny config surface** with sane defaults.
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- Node.js **>= 18**
|
|
30
|
+
- A NestJS (or NestJS-shaped) project with a `tsconfig.json`
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
One-off via `npx`:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npx nestjs-openapi-parser --project ./apps/api --out openapi.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
As a dev dependency:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npm install --save-dev nestjs-openapi-parser
|
|
44
|
+
# or
|
|
45
|
+
yarn add -D nestjs-openapi-parser
|
|
46
|
+
# or
|
|
47
|
+
pnpm add -D nestjs-openapi-parser
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or install globally:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm install -g nestjs-openapi-parser
|
|
54
|
+
nestparser --help
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
1. Drop a config file at your project root:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// nestparser.config.ts
|
|
63
|
+
import { defineConfig } from 'nestjs-openapi-parser';
|
|
64
|
+
|
|
65
|
+
export default defineConfig({
|
|
66
|
+
openapi: {
|
|
67
|
+
title: 'My API', // Default to `package.json` name
|
|
68
|
+
version: '1.0.0', // Default to `package.json` version
|
|
69
|
+
servers: [{ url: 'http://localhost:3000' }],
|
|
70
|
+
securitySchemes: {
|
|
71
|
+
bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
project: {
|
|
75
|
+
globalPrefix: 'v1',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
2. Run the CLI:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
npx nestparser --out openapi.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
That's it. You should see something like:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Project root: /path/to/your/api
|
|
90
|
+
Config file: /path/to/your/api/nestparser.config.ts
|
|
91
|
+
Wrote /path/to/your/api/openapi.json (42 routes, 58 operations, 31 schemas).
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## CLI usage
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
nestparser [options]
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
-V, --version output the version number
|
|
101
|
+
-p, --project <path> path to the NestJS project root (default: cwd)
|
|
102
|
+
-o, --out <path> path to the OpenAPI JSON output file (default: ./openapi.json)
|
|
103
|
+
-c, --config <path> path to the nestparser config file (default: auto-discover)
|
|
104
|
+
-s, --scope <list> active scopes (comma-separated; repeatable)
|
|
105
|
+
-h, --help display help
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Config file discovery
|
|
109
|
+
|
|
110
|
+
The CLI looks for the first file matching, inside `--project`:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
nestparser.config.ts
|
|
114
|
+
nestparser.config.mts
|
|
115
|
+
nestparser.config.cts
|
|
116
|
+
nestparser.config.mjs
|
|
117
|
+
nestparser.config.cjs
|
|
118
|
+
nestparser.config.js
|
|
119
|
+
nestparser.config.json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`.ts` / `.mts` / `.cts` files are loaded via [`tsx`](https://github.com/privatenumber/tsx) (registered lazily — JSON-only users don't pay for it). Use `--config <path>` to point at an explicit file.
|
|
123
|
+
|
|
124
|
+
**Config is optional.** If auto-discovery finds nothing, the parser emits a warning and falls back to a default config — `openapi.title`/`version` come from the project's `package.json` (`name`/`version`), with everything else on engine defaults (`rootDir: src`, `tsConfigFilePath: tsconfig.json`, no hooks). This lets you run against a vanilla NestJS project with zero setup.
|
|
125
|
+
|
|
126
|
+
## Library usage
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { parseNestProject, loadConfig } from 'nestjs-openapi-parser';
|
|
130
|
+
import { writeFileSync } from 'node:fs';
|
|
131
|
+
|
|
132
|
+
const projectRoot = process.cwd();
|
|
133
|
+
const { config } = await loadConfig({ projectRoot });
|
|
134
|
+
const document = await parseNestProject({ projectRoot, config });
|
|
135
|
+
|
|
136
|
+
writeFileSync('openapi.json', JSON.stringify(document, null, 2));
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
See more in [Library Usage](docs/library-usage.md).
|
|
140
|
+
|
|
141
|
+
## How to use in NestJS
|
|
142
|
+
|
|
143
|
+
Write plain NestJS — the parser reads your controllers and models as-is. No config and no
|
|
144
|
+
extra decorators are required; JSDoc comments simply carry over as descriptions.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
/** A registered user. */
|
|
148
|
+
export class User {
|
|
149
|
+
id!: string; // → { type: 'string' }, required
|
|
150
|
+
|
|
151
|
+
/** Display name. */
|
|
152
|
+
name!: string; // property JSDoc → schema description
|
|
153
|
+
|
|
154
|
+
email?: string; // optional (`?`) → omitted from `required`
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { Controller, Delete, Get, Param } from '@nestjs/common';
|
|
160
|
+
import { User } from './user.entity';
|
|
161
|
+
|
|
162
|
+
/** Manage users. */ // class JSDoc → `Users` tag description
|
|
163
|
+
@Controller('users')
|
|
164
|
+
export class UsersController {
|
|
165
|
+
/** Fetch a single user by id. */ // method JSDoc → operation description
|
|
166
|
+
@Get(':id') // `:id` → required path param; summary "Find One"
|
|
167
|
+
findOne(@Param('id') id: string): Promise<User> {
|
|
168
|
+
// return type `User` → 200 response body + `User` schema
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Permanently delete a user.
|
|
173
|
+
*
|
|
174
|
+
* @Scope admin // emitted only when built with --scope admin
|
|
175
|
+
* @Tag Admin // groups this operation under the `Admin` tag
|
|
176
|
+
* @Name Delete user // overrides the auto summary
|
|
177
|
+
*/
|
|
178
|
+
@Delete(':id')
|
|
179
|
+
remove(@Param('id') id: string): Promise<void> {}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Run `npx nestparser --out openapi.json` — that's it. See [What it parses](docs/parser.md) for
|
|
184
|
+
every supported construct (DTOs, `class-validator` constraints, enums, `@Scope`, `@Tag`, hooks…).
|
|
185
|
+
|
|
186
|
+
## What it parses
|
|
187
|
+
|
|
188
|
+
See [Parser Documentation](docs/parser.md)
|
|
189
|
+
|
|
190
|
+
## Configuration
|
|
191
|
+
|
|
192
|
+
See [Configuration Documentation](docs/configuration.md)
|
|
193
|
+
|
|
194
|
+
## Examples
|
|
195
|
+
|
|
196
|
+
A complete fixture lives under [`tests/fixtures/example-app/`](tests/fixtures/example-app/) — a self-contained NestJS app with controllers, DTOs, entities, mapped types, scopes and the envelope hook. The corresponding generated OpenAPI documents are snapshotted at [`tests/__snapshots__/`](tests/__snapshots__/) (one file per scope variant).
|
|
197
|
+
|
|
198
|
+
You can browse any snapshot in a [Scalar](https://github.com/scalar/scalar) UI:
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
yarn snapshot:serve # interactive picker (uses prompts)
|
|
202
|
+
yarn snapshot:serve openapi.admin.snap.json # basename relative to tests/__snapshots__/
|
|
203
|
+
yarn snapshot:serve /tmp/some-spec.json # any absolute path
|
|
204
|
+
SCALAR_PORT=9000 yarn snapshot:serve # override the default port (8088)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Limitations
|
|
208
|
+
|
|
209
|
+
- Decorators are matched by **local identifier name**. Aliased imports (`import { Post as HttpPost }`) won't be detected — keep them un-aliased or extend via a hook.
|
|
210
|
+
- Pipe detection in `@Param` is textual (`ParseUUIDPipe` / `ParseIntPipe` / `ParseBoolPipe`). Custom pipes fall back to the parameter type.
|
|
211
|
+
- No support yet for `@nestjs/swagger`'s `@ApiProperty(...)` runtime overrides — describe properties via JSDoc instead.
|
|
212
|
+
- Module-level filtering (e.g. emit only routes from one Nest module) is not built in — control it at the `rootDir` / `excludeSuffixes` / `@Scope` level.
|
|
213
|
+
- Route patterns OpenAPI can't represent — inline regex (`:id(\d+)`), wildcards (`*`, `:splat*`), `+`/`*` modifiers, and more than one optional param in a route — are **skipped with a warning**. Optional params (`:id?`) are supported via path splitting.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const loader_1 = require("./config/loader");
|
|
11
|
+
const parser_1 = require("./parser");
|
|
12
|
+
const tags_1 = require("./parser/tags");
|
|
13
|
+
function readPackageVersion() {
|
|
14
|
+
const pkgPath = node_path_1.default.resolve(__dirname, '..', 'package.json');
|
|
15
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
16
|
+
return pkg.version;
|
|
17
|
+
}
|
|
18
|
+
async function run(options) {
|
|
19
|
+
const projectRoot = node_path_1.default.resolve(options.project);
|
|
20
|
+
const outPath = node_path_1.default.resolve(options.out);
|
|
21
|
+
const { config, filePath } = await (0, loader_1.loadConfig)({
|
|
22
|
+
projectRoot,
|
|
23
|
+
configPath: options.config,
|
|
24
|
+
});
|
|
25
|
+
// CLI --scope overrides config.scopes when present (even empty).
|
|
26
|
+
const effectiveConfig = options.scope !== undefined ? { ...config, scopes: (0, tags_1.parseScopeList)(options.scope) } : config;
|
|
27
|
+
console.log(`Project root: ${projectRoot}`);
|
|
28
|
+
console.log(`Config file: ${filePath ?? '(none — using defaults)'}`);
|
|
29
|
+
if (effectiveConfig.scopes && effectiveConfig.scopes.length > 0) {
|
|
30
|
+
console.log(`Active scopes: ${effectiveConfig.scopes.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
const document = await (0, parser_1.parseNestProject)({ projectRoot, config: effectiveConfig });
|
|
33
|
+
(0, node_fs_1.mkdirSync)(node_path_1.default.dirname(outPath), { recursive: true });
|
|
34
|
+
(0, node_fs_1.writeFileSync)(outPath, JSON.stringify(document, null, 2));
|
|
35
|
+
const routeCount = Object.keys(document.paths).length;
|
|
36
|
+
const operationCount = Object.values(document.paths).reduce((acc, ops) => acc + Object.keys(ops).length, 0);
|
|
37
|
+
const schemaCount = Object.keys(document.components?.schemas ?? {}).length;
|
|
38
|
+
console.log(`Wrote ${outPath} (${routeCount} routes, ${operationCount} operations, ${schemaCount} schemas).`);
|
|
39
|
+
}
|
|
40
|
+
const program = new commander_1.Command();
|
|
41
|
+
program
|
|
42
|
+
.name('nestparser')
|
|
43
|
+
.description('Parse a NestJS project and emit an OpenAPI document.')
|
|
44
|
+
.version(readPackageVersion())
|
|
45
|
+
.option('-p, --project <path>', 'path to the NestJS project root', process.cwd())
|
|
46
|
+
.option('-o, --out <path>', 'path to the OpenAPI JSON output file', node_path_1.default.resolve(process.cwd(), 'openapi.json'))
|
|
47
|
+
.option('-c, --config <path>', 'path to the nestparser config file (defaults to auto-discovery in the project root)')
|
|
48
|
+
.option('-s, --scope <list>', 'active scopes (comma-separated; repeatable). Items tagged with @Scope are emitted only when at least one matches; untagged items are always emitted.', (value, prior = []) => [...prior, value])
|
|
49
|
+
.action(run);
|
|
50
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
51
|
+
console.error(err instanceof Error ? err.message : err);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_CONVENTIONS = exports.DEFAULT_PROJECT = void 0;
|
|
4
|
+
exports.DEFAULT_PROJECT = {
|
|
5
|
+
tsConfigFilePath: 'tsconfig.json',
|
|
6
|
+
rootDir: 'src',
|
|
7
|
+
globalPrefix: '',
|
|
8
|
+
excludeSuffixes: ['.spec.ts', '.test.ts', '.d.ts'],
|
|
9
|
+
};
|
|
10
|
+
exports.DEFAULT_CONVENTIONS = {
|
|
11
|
+
excludeDecorator: 'Exclude',
|
|
12
|
+
optionalDecorator: 'IsOptional',
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/config/defaults.ts"],"names":[],"mappings":";;;AAEa,QAAA,eAAe,GAExB;IACF,gBAAgB,EAAE,eAAe;IACjC,OAAO,EAAE,KAAK;IACd,YAAY,EAAE,EAAE;IAChB,eAAe,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,CAAC;CACnD,CAAC;AAEW,QAAA,mBAAmB,GAAgC;IAC9D,gBAAgB,EAAE,SAAS;IAC3B,iBAAiB,EAAE,YAAY;CAChC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
exports.loadConfig = exports.DEFAULT_CONVENTIONS = exports.DEFAULT_PROJECT = void 0;
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
var defaults_1 = require("./defaults");
|
|
20
|
+
Object.defineProperty(exports, "DEFAULT_PROJECT", { enumerable: true, get: function () { return defaults_1.DEFAULT_PROJECT; } });
|
|
21
|
+
Object.defineProperty(exports, "DEFAULT_CONVENTIONS", { enumerable: true, get: function () { return defaults_1.DEFAULT_CONVENTIONS; } });
|
|
22
|
+
var loader_1 = require("./loader");
|
|
23
|
+
Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return loader_1.loadConfig; } });
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,uCAAkE;AAAzD,2GAAA,eAAe,OAAA;AAAE,+GAAA,mBAAmB,OAAA;AAC7C,mCAAsC;AAA7B,oGAAA,UAAU,OAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NestParserConfig } from './types';
|
|
2
|
+
export interface LoadConfigOptions {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
/** Explicit path to a config file, overriding auto-discovery. */
|
|
5
|
+
configPath?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LoadedConfig {
|
|
8
|
+
config: NestParserConfig;
|
|
9
|
+
filePath: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadConfig(options: LoadConfigOptions): Promise<LoadedConfig>;
|
|
@@ -0,0 +1,160 @@
|
|
|
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.loadConfig = loadConfig;
|
|
40
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
41
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
42
|
+
const node_url_1 = require("node:url");
|
|
43
|
+
const CANDIDATE_NAMES = [
|
|
44
|
+
'nestparser.config.ts',
|
|
45
|
+
'nestparser.config.mts',
|
|
46
|
+
'nestparser.config.cts',
|
|
47
|
+
'nestparser.config.mjs',
|
|
48
|
+
'nestparser.config.cjs',
|
|
49
|
+
'nestparser.config.js',
|
|
50
|
+
'nestparser.config.json',
|
|
51
|
+
];
|
|
52
|
+
let tsxRegistered = false;
|
|
53
|
+
function ensureTsxRegistered() {
|
|
54
|
+
if (tsxRegistered)
|
|
55
|
+
return;
|
|
56
|
+
// Register tsx's CommonJS require hook so we can `require('./foo.ts')`.
|
|
57
|
+
// Imported lazily so JSON-only users don't pay for it.
|
|
58
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
59
|
+
require('tsx/cjs');
|
|
60
|
+
tsxRegistered = true;
|
|
61
|
+
}
|
|
62
|
+
function findConfigFile(projectRoot, explicit) {
|
|
63
|
+
if (explicit) {
|
|
64
|
+
const resolved = node_path_1.default.isAbsolute(explicit) ? explicit : node_path_1.default.resolve(projectRoot, explicit);
|
|
65
|
+
if (!node_fs_1.default.existsSync(resolved)) {
|
|
66
|
+
throw new Error(`Config file not found: ${resolved}`);
|
|
67
|
+
}
|
|
68
|
+
return resolved;
|
|
69
|
+
}
|
|
70
|
+
for (const name of CANDIDATE_NAMES) {
|
|
71
|
+
const candidate = node_path_1.default.join(projectRoot, name);
|
|
72
|
+
if (node_fs_1.default.existsSync(candidate))
|
|
73
|
+
return candidate;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
async function importConfig(filePath) {
|
|
78
|
+
const ext = node_path_1.default.extname(filePath);
|
|
79
|
+
if (ext === '.json') {
|
|
80
|
+
return JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf-8'));
|
|
81
|
+
}
|
|
82
|
+
if (ext === '.ts' || ext === '.mts' || ext === '.cts') {
|
|
83
|
+
ensureTsxRegistered();
|
|
84
|
+
}
|
|
85
|
+
if (ext === '.mjs' || ext === '.mts') {
|
|
86
|
+
const mod = (await Promise.resolve(`${(0, node_url_1.pathToFileURL)(filePath).href}`).then(s => __importStar(require(s))));
|
|
87
|
+
return mod.default ?? mod;
|
|
88
|
+
}
|
|
89
|
+
// .ts / .cts / .cjs / .js — CommonJS path (tsx's require hook covers .ts/.cts)
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
91
|
+
const mod = require(filePath);
|
|
92
|
+
return mod.default ?? mod;
|
|
93
|
+
}
|
|
94
|
+
function isConfigShape(value) {
|
|
95
|
+
return (typeof value === 'object' &&
|
|
96
|
+
value !== null &&
|
|
97
|
+
'openapi' in value &&
|
|
98
|
+
typeof value.openapi === 'object' &&
|
|
99
|
+
value.openapi !== null);
|
|
100
|
+
}
|
|
101
|
+
function nonEmptyString(value) {
|
|
102
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Default `openapi.title`/`version` for a project — from its `package.json`
|
|
106
|
+
* (`name`/`version`) when available, else generic fallbacks. Used both when no
|
|
107
|
+
* config file exists and to fill in either field a config file leaves out.
|
|
108
|
+
*/
|
|
109
|
+
function resolveOpenApiDefaults(projectRoot) {
|
|
110
|
+
let title = 'API';
|
|
111
|
+
let version = '1.0.0';
|
|
112
|
+
const pkgPath = node_path_1.default.join(projectRoot, 'package.json');
|
|
113
|
+
if (node_fs_1.default.existsSync(pkgPath)) {
|
|
114
|
+
try {
|
|
115
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
|
|
116
|
+
if (nonEmptyString(pkg.name))
|
|
117
|
+
title = pkg.name;
|
|
118
|
+
if (nonEmptyString(pkg.version))
|
|
119
|
+
version = pkg.version;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Malformed package.json — keep the generic fallbacks.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { title, version };
|
|
126
|
+
}
|
|
127
|
+
async function loadConfig(options) {
|
|
128
|
+
const filePath = findConfigFile(options.projectRoot, options.configPath);
|
|
129
|
+
if (!filePath) {
|
|
130
|
+
// Auto-discovery found nothing (an explicit --config miss already threw):
|
|
131
|
+
// fall back to a default config so the doc can still be generated.
|
|
132
|
+
const { title, version } = resolveOpenApiDefaults(options.projectRoot);
|
|
133
|
+
console.warn(`[nestparser] No config file found in ${options.projectRoot}; ` +
|
|
134
|
+
`using defaults (title="${title}", version="${version}"). ` +
|
|
135
|
+
`Add one of ${CANDIDATE_NAMES.join(', ')} to customize.`);
|
|
136
|
+
return { config: { openapi: { title, version } }, filePath: undefined };
|
|
137
|
+
}
|
|
138
|
+
const raw = await importConfig(filePath);
|
|
139
|
+
if (!isConfigShape(raw)) {
|
|
140
|
+
throw new Error(`Config at ${filePath} does not export a valid NestParserConfig (missing 'openapi').`);
|
|
141
|
+
}
|
|
142
|
+
// Fill in `title`/`version` from package.json (or generic defaults) when the
|
|
143
|
+
// config omits either, rather than failing — same fallback as the no-config path.
|
|
144
|
+
const missing = [];
|
|
145
|
+
if (!nonEmptyString(raw.openapi.title))
|
|
146
|
+
missing.push('title');
|
|
147
|
+
if (!nonEmptyString(raw.openapi.version))
|
|
148
|
+
missing.push('version');
|
|
149
|
+
if (missing.length) {
|
|
150
|
+
const defaults = resolveOpenApiDefaults(options.projectRoot);
|
|
151
|
+
if (!nonEmptyString(raw.openapi.title))
|
|
152
|
+
raw.openapi.title = defaults.title;
|
|
153
|
+
if (!nonEmptyString(raw.openapi.version))
|
|
154
|
+
raw.openapi.version = defaults.version;
|
|
155
|
+
console.warn(`[nestparser] Config at ${filePath} is missing ${missing.map((m) => `openapi.${m}`).join(', ')}; ` +
|
|
156
|
+
`using defaults (title="${raw.openapi.title}", version="${raw.openapi.version}").`);
|
|
157
|
+
}
|
|
158
|
+
return { config: raw, filePath };
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+GA,gCAqCC;AApJD,sDAAyB;AACzB,0DAA6B;AAC7B,uCAAyC;AAGzC,MAAM,eAAe,GAAG;IACtB,sBAAsB;IACtB,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;IACvB,uBAAuB;IACvB,sBAAsB;IACtB,wBAAwB;CACzB,CAAC;AAEF,IAAI,aAAa,GAAG,KAAK,CAAC;AAE1B,SAAS,mBAAmB;IAC1B,IAAI,aAAa;QAAE,OAAO;IAC1B,wEAAwE;IACxE,uDAAuD;IACvD,iEAAiE;IACjE,OAAO,CAAC,SAAS,CAAC,CAAC;IACnB,aAAa,GAAG,IAAI,CAAC;AACvB,CAAC;AAED,SAAS,cAAc,CAAC,WAAmB,EAAE,QAAiB;IAC5D,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,mBAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,mBAAI,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QAC5F,IAAI,CAAC,iBAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,0BAA0B,QAAQ,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,SAAS,GAAG,mBAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QAC/C,IAAI,iBAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IACjD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,QAAgB;IAC1C,MAAM,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnC,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,iBAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,GAAG,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACtD,mBAAmB,EAAE,CAAC;IACxB,CAAC;IAED,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,CAAC,yBAAa,IAAA,wBAAa,EAAC,QAAQ,CAAC,CAAC,IAAI,uCAAC,CAA0B,CAAC;QAClF,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;IAC5B,CAAC;IAED,+EAA+E;IAC/E,iEAAiE;IACjE,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAA0B,CAAC;IACvD,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;AAC5B,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,SAAS,IAAI,KAAK;QAClB,OAAQ,KAA8B,CAAC,OAAO,KAAK,QAAQ;QAC1D,KAA8B,CAAC,OAAO,KAAK,IAAI,CACjD,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,WAAmB;IACjD,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,OAAO,GAAG,OAAO,CAAC;IAEtB,MAAM,OAAO,GAAG,mBAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;IACvD,IAAI,iBAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAE,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAA4B,CAAC;YACrF,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC;YAC/C,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;QACzD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC;AAaM,KAAK,UAAU,UAAU,CAAC,OAA0B;IACzD,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACzE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,0EAA0E;QAC1E,mEAAmE;QACnE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,sBAAsB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CACV,wCAAwC,OAAO,CAAC,WAAW,IAAI;YAC7D,0BAA0B,KAAK,eAAe,OAAO,MAAM;YAC3D,cAAc,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAC3D,CAAC;QACF,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC1E,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,aAAa,QAAQ,gEAAgE,CACtF,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,kFAAkF;IAClF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAClE,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,sBAAsB,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC7D,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;QAC3E,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,GAAG,CAAC,OAAO,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;QACjF,OAAO,CAAC,IAAI,CACV,0BAA0B,QAAQ,eAAe,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAChG,0BAA0B,GAAG,CAAC,OAAO,CAAC,KAAK,eAAe,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,CACrF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { ClassDeclaration, MethodDeclaration, Type } from 'ts-morph';
|
|
2
|
+
import type { OpenApiInfo, OpenApiSchema, OpenApiSecurityRequirement, OpenApiSecurityScheme, OpenApiServer } from '../types/openapi';
|
|
3
|
+
/**
|
|
4
|
+
* Context passed to the `buildResponseSchema` hook for a single controller method.
|
|
5
|
+
* The hook decides what the final response body schema looks like — including any
|
|
6
|
+
* project-specific envelope (e.g. `{ success, data }`) or pagination wrapping.
|
|
7
|
+
*/
|
|
8
|
+
export interface ResponseSchemaContext {
|
|
9
|
+
method: MethodDeclaration;
|
|
10
|
+
/** Method return type with `Promise<T>` already unwrapped to `T`. */
|
|
11
|
+
returnType: Type;
|
|
12
|
+
/** Symbol name of `returnType`, if any (e.g. `PaginatedResponse`, `User`, `Date`). */
|
|
13
|
+
returnTypeName: string | undefined;
|
|
14
|
+
/** Generic type arguments of `returnType` (e.g. `[User]` for `PaginatedResponse<User>`). */
|
|
15
|
+
returnTypeArgs: Type[];
|
|
16
|
+
/**
|
|
17
|
+
* Lazily compute the schema for the bare return type (no envelope/wrapper).
|
|
18
|
+
* Calling this registers a `$ref` to `components.schemas` if the return type
|
|
19
|
+
* is a class — only call when you actually need it.
|
|
20
|
+
*/
|
|
21
|
+
defaultSchema: () => OpenApiSchema;
|
|
22
|
+
/** Convert any ts-morph `Type` to an OpenAPI schema fragment (registers refs). */
|
|
23
|
+
typeToSchema: (type: Type) => OpenApiSchema;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Context passed to the `resolveSecurity` hook for a single controller method.
|
|
27
|
+
* Return `undefined` to keep the default (apply every registered security scheme),
|
|
28
|
+
* `[]` to mark the endpoint public, or an explicit array of requirements.
|
|
29
|
+
*/
|
|
30
|
+
export interface SecurityContext {
|
|
31
|
+
controller: ClassDeclaration;
|
|
32
|
+
method: MethodDeclaration;
|
|
33
|
+
/** Names of all security schemes declared under `openapi.securitySchemes`. */
|
|
34
|
+
registeredSchemes: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Context passed to the `endpointSummary` hook for a single controller method.
|
|
38
|
+
* Return a string to use as the operation `summary`, or `null`/`undefined` to
|
|
39
|
+
* fall back to `defaultSummary`. A method-level `@Name` JSDoc tag overrides both.
|
|
40
|
+
*/
|
|
41
|
+
export interface EndpointSummaryContext {
|
|
42
|
+
controller: ClassDeclaration;
|
|
43
|
+
method: MethodDeclaration;
|
|
44
|
+
/** Lowercase HTTP verb (`get`, `post`, `put`, `delete`, `patch`). */
|
|
45
|
+
httpMethod: string;
|
|
46
|
+
/** The default summary: the method name humanized (PascalCase split + capitalized). */
|
|
47
|
+
defaultSummary: string;
|
|
48
|
+
}
|
|
49
|
+
export interface NestParserHooks {
|
|
50
|
+
/**
|
|
51
|
+
* Decide the schema of the response body for an endpoint. If not provided, the
|
|
52
|
+
* response body is the method's return type schema directly (no envelope).
|
|
53
|
+
*/
|
|
54
|
+
buildResponseSchema?: (ctx: ResponseSchemaContext) => OpenApiSchema;
|
|
55
|
+
/**
|
|
56
|
+
* Decide which security requirements apply to an endpoint. Default behavior:
|
|
57
|
+
* if any security schemes are registered, all of them apply (logical OR); else
|
|
58
|
+
* no security entries are emitted.
|
|
59
|
+
*/
|
|
60
|
+
resolveSecurity?: (ctx: SecurityContext) => OpenApiSecurityRequirement[] | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Override the default tag derivation (strip `Controller` suffix from class name).
|
|
63
|
+
*/
|
|
64
|
+
controllerTag?: (clazz: ClassDeclaration) => string;
|
|
65
|
+
/**
|
|
66
|
+
* Build the `summary` for an endpoint. Return `null`/`undefined` to fall back
|
|
67
|
+
* to the default (the method name humanized). A method-level `@Name` JSDoc tag
|
|
68
|
+
* overrides both this hook and the default.
|
|
69
|
+
*/
|
|
70
|
+
endpointSummary?: (ctx: EndpointSummaryContext) => string | null | undefined;
|
|
71
|
+
}
|
|
72
|
+
export interface OpenApiConfig {
|
|
73
|
+
title: string;
|
|
74
|
+
version: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
servers?: OpenApiServer[];
|
|
77
|
+
securitySchemes?: Record<string, OpenApiSecurityScheme>;
|
|
78
|
+
/** Document-level extras spliced onto `info`. */
|
|
79
|
+
info?: Partial<OpenApiInfo>;
|
|
80
|
+
}
|
|
81
|
+
export interface ProjectConfig {
|
|
82
|
+
/**
|
|
83
|
+
* Path to the project's tsconfig (relative to the project root or absolute).
|
|
84
|
+
* Defaults to `tsconfig.json`.
|
|
85
|
+
*/
|
|
86
|
+
tsConfigFilePath?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Directory containing the project's source files, relative to the project root.
|
|
89
|
+
* Defaults to `src`.
|
|
90
|
+
*/
|
|
91
|
+
rootDir?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Global route prefix mirroring `app.setGlobalPrefix(...)`. Prepended to every
|
|
94
|
+
* controller route. Defaults to empty (no prefix).
|
|
95
|
+
*/
|
|
96
|
+
globalPrefix?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Glob-ish suffixes to skip when scanning the source tree. Files matching any
|
|
99
|
+
* of these are not indexed. Defaults to `['.spec.ts', '.test.ts', '.d.ts']`.
|
|
100
|
+
*/
|
|
101
|
+
excludeSuffixes?: string[];
|
|
102
|
+
}
|
|
103
|
+
export interface ConventionsConfig {
|
|
104
|
+
/** Decorator name marking a property as excluded from serialization. Defaults to `Exclude`. */
|
|
105
|
+
excludeDecorator?: string;
|
|
106
|
+
/** Decorator name marking a property as optional. Defaults to `IsOptional`. */
|
|
107
|
+
optionalDecorator?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A class reference (concrete or abstract) — anything assignable to a class
|
|
111
|
+
* constructor. Looked up against the project's AST index by `klass.name`.
|
|
112
|
+
*/
|
|
113
|
+
export type ModelConstructor = abstract new (...args: any[]) => unknown;
|
|
114
|
+
export interface NestParserConfig {
|
|
115
|
+
openapi: OpenApiConfig;
|
|
116
|
+
project?: ProjectConfig;
|
|
117
|
+
conventions?: ConventionsConfig;
|
|
118
|
+
hooks?: NestParserHooks;
|
|
119
|
+
/**
|
|
120
|
+
* Class references to force-include in `components.schemas`, even when no
|
|
121
|
+
* endpoint reaches them. Pass the class itself (not its name) — we resolve
|
|
122
|
+
* via `klass.name` against the project's AST index. Throws at build time if
|
|
123
|
+
* any name cannot be matched, to surface typos and out-of-tree classes early.
|
|
124
|
+
*
|
|
125
|
+
* Transitive references of each entry are also pulled in (same reachability
|
|
126
|
+
* walk as if an endpoint had returned the class).
|
|
127
|
+
*/
|
|
128
|
+
additionalModels?: ModelConstructor[];
|
|
129
|
+
/**
|
|
130
|
+
* Active scopes for this build. Items annotated with `@Scope` are emitted
|
|
131
|
+
* only when their scope set intersects this list. Untagged items are always
|
|
132
|
+
* emitted. Empty/undefined means "only untagged items".
|
|
133
|
+
*
|
|
134
|
+
* The CLI flag `--scope a,b` overrides this when given.
|
|
135
|
+
*/
|
|
136
|
+
scopes?: string[];
|
|
137
|
+
}
|
|
138
|
+
/** Helper for `nestparser.config.ts` so users get full type-checking. */
|
|
139
|
+
export declare function defineConfig(config: NestParserConfig): NestParserConfig;
|