@visulima/jsdoc-open-api 1.0.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/CHANGELOG.md +19 -0
- package/LICENSE.md +0 -0
- package/README.md +226 -0
- package/bin/index.js +188 -0
- package/dist/index.d.ts +303 -0
- package/dist/index.js +633 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +633 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +135 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## @visulima/jsdoc-open-api [1.0.1](https://github.com/visulima/visulima/compare/@visulima/jsdoc-open-api@1.0.0...@visulima/jsdoc-open-api@1.0.1) (2022-10-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **jsdoc-open-api:** make package public ([f266edf](https://github.com/visulima/visulima/commit/f266edf64fb21f2510d9ab15e27e48909d593a58))
|
|
7
|
+
|
|
8
|
+
## @visulima/jsdoc-open-api 1.0.0 (2022-10-26)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **jsdoc-open-api:** added connect as new package for swagger parsing ([a641712](https://github.com/visulima/visulima/commit/a641712ab27616d38250fdd86a6cdc5c865ddd80))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Dependencies
|
|
18
|
+
|
|
19
|
+
* **@visulima/readdir:** upgraded to 1.1.0
|
package/LICENSE.md
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h3>Visulima jsdoc-open-api</h3>
|
|
3
|
+
<p>
|
|
4
|
+
|
|
5
|
+
Visulima jsdoc-open-api parser and generator is a forked version of [openapi-comment-parser](https://github.com/bee-travels/openapi-comment-parser) and [swagger-jsdoc](https://github.com/Surnet/swagger-jsdoc) its built on top of [swagger](https://swagger.io/) and [JSDoc](https://jsdoc.app/), for speed and minimal runtime overhead.
|
|
6
|
+
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<br />
|
|
11
|
+
|
|
12
|
+
<div align="center">
|
|
13
|
+
|
|
14
|
+
[![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
<div align="center">
|
|
21
|
+
<p>
|
|
22
|
+
<sup>
|
|
23
|
+
Daniel Bannert's open source work is supported by the community on <a href="https://github.com/sponsors/prisis">GitHub Sponsors</a>
|
|
24
|
+
</sup>
|
|
25
|
+
</p>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npm install @visulima/jsdoc-open-api
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
yarn add @visulima/jsdoc-open-api
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pnpm add @visulima/jsdoc-open-api
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Choose the syntax you want to use for your OpenAPI definitions:
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
CLI:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# This generate the .openapirc.js config file, this command is only needed on the first run
|
|
54
|
+
jsdoc-open-api init
|
|
55
|
+
|
|
56
|
+
# This will generate the swagger.json file
|
|
57
|
+
jsdoc-open-api generate src/
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## OpenApi yaml syntax
|
|
61
|
+
|
|
62
|
+
The library will take the contents of @openapi (or @swagger):
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
/**
|
|
66
|
+
* @openapi
|
|
67
|
+
* /:
|
|
68
|
+
* get:
|
|
69
|
+
* description: Welcome to swagger-jsdoc!
|
|
70
|
+
* responses:
|
|
71
|
+
* 200:
|
|
72
|
+
* description: Returns a mysterious string.
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## OpenApi short syntax
|
|
78
|
+
|
|
79
|
+
### Basic structure
|
|
80
|
+
You can write OpenAPI definitions in JSDoc comments or YAML files.
|
|
81
|
+
In this guide, we use only JSDoc comments examples. However, YAML files work equally as well.
|
|
82
|
+
|
|
83
|
+
Each comment defines individual endpoints (paths) in your API, and the HTTP methods (operations) supported by these endpoints.
|
|
84
|
+
For example, `GET /users` can be described as:
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
/**
|
|
88
|
+
* GET /users
|
|
89
|
+
* @summary Returns a list of users.
|
|
90
|
+
* @description Optional extended description in CommonMark or HTML.
|
|
91
|
+
* @response 200 - A JSON array of user names
|
|
92
|
+
* @responseContent {string[]} 200.application/json
|
|
93
|
+
*/
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Parameters
|
|
97
|
+
Operations can have parameters passed via URL path (`/users/{userId}`), query string (`/users?role=admin`),
|
|
98
|
+
headers (`X-CustomHeader: Value`) or cookies (`Cookie: debug=0`).
|
|
99
|
+
You can define the parameter data types, format, whether they are required or optional, and other details:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
/**
|
|
103
|
+
* GET /users/{userId}
|
|
104
|
+
* @summary Returns a user by ID.
|
|
105
|
+
* @pathParam {int64} userId - Parameter description in CommonMark or HTML.
|
|
106
|
+
* @response 200 - OK
|
|
107
|
+
*/
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For more information, see [Describing Parameters](/docs/short/describing-parameters.md).
|
|
111
|
+
|
|
112
|
+
#### Request body
|
|
113
|
+
If an operation sends a request body, use the `bodyContent` keyword to describe the body content and media type.
|
|
114
|
+
Use `bodyRequired` to indicate that a request body is required.
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
/**
|
|
118
|
+
* POST /users
|
|
119
|
+
* @summary Creates a user.
|
|
120
|
+
* @bodyContent {User} application/json
|
|
121
|
+
* @bodyRequired
|
|
122
|
+
* @response 201 - Created
|
|
123
|
+
*/
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For more information, see [Describing Request Body](/docs/short/describing-request-body.md).
|
|
127
|
+
|
|
128
|
+
#### Responses
|
|
129
|
+
For each operation, you can define possible status codes, such as 200 OK or 404 Not Found, and the response body content.
|
|
130
|
+
You can also provide example responses for different content types:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
/**
|
|
134
|
+
* GET /users/{userId}
|
|
135
|
+
* @summary Returns a user by ID.
|
|
136
|
+
* @pathParam {int64} userId - The ID of the user to return.
|
|
137
|
+
* @response 200 - A user object.
|
|
138
|
+
* @responseContent {User} 200.application/json
|
|
139
|
+
* @response 400 - The specified user ID is invalid (not a number).
|
|
140
|
+
* @response 404 - A user with the specified ID was not found.
|
|
141
|
+
* @response default - Unexpected error
|
|
142
|
+
*/
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
For more information, see [Describing Responses](/docs/short/describing-responses.md).
|
|
146
|
+
|
|
147
|
+
#### Input and output models
|
|
148
|
+
You can create global `components/schemas` section lets you define common data structures used in your API.
|
|
149
|
+
They can be referenced by name whenever a schema is required – in parameters, request bodies, and response bodies.
|
|
150
|
+
For example, this JSON object:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"id": 4,
|
|
155
|
+
"name": "Arthur Dent"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
can be represented as:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
components:
|
|
163
|
+
schemas:
|
|
164
|
+
User:
|
|
165
|
+
properties:
|
|
166
|
+
id:
|
|
167
|
+
type: integer
|
|
168
|
+
name:
|
|
169
|
+
type: string
|
|
170
|
+
# Both properties are required
|
|
171
|
+
required:
|
|
172
|
+
- id
|
|
173
|
+
- name
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
and then referenced in the request body schema and response body schema as follows:
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
/**
|
|
180
|
+
* GET /users/{userId}
|
|
181
|
+
* @summary Returns a user by ID.
|
|
182
|
+
* @pathParam {integer} userId
|
|
183
|
+
* @response 200 - OK
|
|
184
|
+
* @responseContent {User} 200.application/json
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* POST /users
|
|
189
|
+
* @summary Creates a new user.
|
|
190
|
+
* @bodyContent {User} application/json
|
|
191
|
+
* @bodyRequired
|
|
192
|
+
* @response 201 - Created
|
|
193
|
+
*/
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Authentication
|
|
197
|
+
The `securitySchemes` and `security` keywords are used to describe the authentication methods used in your API.
|
|
198
|
+
```yaml
|
|
199
|
+
components:
|
|
200
|
+
securitySchemes:
|
|
201
|
+
BasicAuth:
|
|
202
|
+
type: http
|
|
203
|
+
scheme: basic
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
/**
|
|
208
|
+
* GET /users
|
|
209
|
+
* @security BasicAuth
|
|
210
|
+
*/
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Supported authentication methods are:
|
|
214
|
+
- HTTP authentication: Basic, Bearer, and so on.
|
|
215
|
+
- API key as a header or query parameter or in cookies
|
|
216
|
+
- OAuth 2
|
|
217
|
+
- OpenID Connect Discovery
|
|
218
|
+
|
|
219
|
+
For more information, see [Authentication](/docs/short/authentication.md).
|
|
220
|
+
|
|
221
|
+
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
|
|
222
|
+
[typescript-url]: "typescript"
|
|
223
|
+
[license-image]: https://img.shields.io/npm/l/@visulima/jsdoc-open-api?color=blueviolet&style=for-the-badge
|
|
224
|
+
[license-url]: LICENSE.md "license"
|
|
225
|
+
[npm-image]: https://img.shields.io/npm/v/@visulima/jsdoc-open-api/latest.svg?style=for-the-badge&logo=npm
|
|
226
|
+
[npm-url]: https://www.npmjs.com/package/@visulima/jsdoc-open-api/v/latest "npm"
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
7
|
+
const { Command } = require("commander");
|
|
8
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
9
|
+
const SwaggerParser = require("@apidevtools/swagger-parser");
|
|
10
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
11
|
+
const { collect } = require("@visulima/readdir");
|
|
12
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
13
|
+
const cliProgress = require("cli-progress");
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
jsDocumentCommentsToOpenApi, parseFile, SpecBuilder, swaggerJsDocumentCommentsToOpenApi,
|
|
17
|
+
// eslint-disable-next-line unicorn/prefer-module
|
|
18
|
+
} = require("../dist/index.js");
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line unicorn/prefer-module,no-underscore-dangle
|
|
21
|
+
const package_ = require("../package.json");
|
|
22
|
+
|
|
23
|
+
const program = new Command();
|
|
24
|
+
const defaultConfigName = ".openapirc.js";
|
|
25
|
+
|
|
26
|
+
program.name("@visulima/jsdoc-open-api").description("CLI to to generate OpenAPI (Swagger) documentation from JSDoc's").version(package_.version);
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command("init")
|
|
30
|
+
.description("Inits a pre-configured @visulima/jsdoc-open-api config file.")
|
|
31
|
+
.action(() => {
|
|
32
|
+
if (fs.existsSync(defaultConfigName)) {
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.error("Config file already exists");
|
|
35
|
+
// eslint-disable-next-line no-undef
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fs.writeFileSync(
|
|
40
|
+
defaultConfigName,
|
|
41
|
+
`module.exports = {
|
|
42
|
+
exclude: [
|
|
43
|
+
'coverage/**',
|
|
44
|
+
'.github/**',
|
|
45
|
+
'packages/*/test{,s}/**',
|
|
46
|
+
'**/*.d.ts',
|
|
47
|
+
'test{,s}/**',
|
|
48
|
+
'test{,-*}.{js,cjs,mjs,ts,tsx,jsx,yaml,yml}',
|
|
49
|
+
'**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx,yaml,yml}',
|
|
50
|
+
'**/__tests__/**',
|
|
51
|
+
'**/{ava,babel,nyc}.config.{js,cjs,mjs}',
|
|
52
|
+
'**/jest.config.{js,cjs,mjs,ts}',
|
|
53
|
+
'**/{karma,rollup,webpack}.config.js',
|
|
54
|
+
'**/.{eslint,mocha}rc.{js,cjs}',
|
|
55
|
+
'**/.{travis,yarnrc}.yml',
|
|
56
|
+
'**/{docker-compose,docker}.yml',
|
|
57
|
+
'**/.yamllint.{yaml,yml}',
|
|
58
|
+
'**/node_modules/**',
|
|
59
|
+
'**/pnpm-lock.yaml',
|
|
60
|
+
'**/pnpm-workspace.yaml',
|
|
61
|
+
'**/package-lock.json',
|
|
62
|
+
'**/yarn.lock',
|
|
63
|
+
'**/package.json',
|
|
64
|
+
'**/package.json5',
|
|
65
|
+
'**/.next/**',
|
|
66
|
+
],
|
|
67
|
+
followSymlinks: false,
|
|
68
|
+
swaggerDefinition: {
|
|
69
|
+
openapi: '3.0.0',
|
|
70
|
+
info: {
|
|
71
|
+
title: 'API',
|
|
72
|
+
version: '1.0.0',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
`,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.log("Created .openapirc.js");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
program
|
|
84
|
+
.command("generate")
|
|
85
|
+
.description("Generates OpenAPI (Swagger) documentation from JSDoc's")
|
|
86
|
+
.usage("[options] <path ...>")
|
|
87
|
+
.argument("[path ...]", "Paths to files or directories to parse")
|
|
88
|
+
.option("-c, --config [.openapirc.js]", "@visulima/jsdoc-open-api config file path.")
|
|
89
|
+
.option("-o, --output [swaggerSpec.json]", "Output swagger specification.")
|
|
90
|
+
.option("-v, --verbose", "Verbose output.")
|
|
91
|
+
.option("-vv, --very-verbose", "Very verbose output.")
|
|
92
|
+
// eslint-disable-next-line radar/cognitive-complexity
|
|
93
|
+
.action(async (paths, options) => {
|
|
94
|
+
let openapiConfig = {};
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// eslint-disable-next-line unicorn/prefer-module,import/no-dynamic-require
|
|
98
|
+
openapiConfig = require(path.resolve(options.config || defaultConfigName));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.log("No config file found, on:", options.config || ".openapirc.js\n");
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.error(error);
|
|
104
|
+
// eslint-disable-next-line no-undef
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const multibar = new cliProgress.MultiBar(
|
|
109
|
+
{
|
|
110
|
+
clearOnComplete: false,
|
|
111
|
+
hideCursor: true,
|
|
112
|
+
format: "{value}/{total} | {bar} | {filename}",
|
|
113
|
+
},
|
|
114
|
+
cliProgress.Presets.shades_grey,
|
|
115
|
+
);
|
|
116
|
+
const spec = new SpecBuilder(openapiConfig.swaggerDefinition);
|
|
117
|
+
|
|
118
|
+
// eslint-disable-next-line no-restricted-syntax,unicorn/prevent-abbreviations
|
|
119
|
+
for await (const dir of paths) {
|
|
120
|
+
const files = await collect(dir, {
|
|
121
|
+
// eslint-disable-next-line @rushstack/security/no-unsafe-regexp
|
|
122
|
+
skip: [...openapiConfig.exclude, "node_modules/**"],
|
|
123
|
+
extensions: openapiConfig.extension || [".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx", ".yaml", ".yml"],
|
|
124
|
+
followSymlinks: openapiConfig.followSymlinks || false,
|
|
125
|
+
match: openapiConfig.include,
|
|
126
|
+
minimatchOptions: {
|
|
127
|
+
debug: options.verbose,
|
|
128
|
+
matchBase: true,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (options.verbose || options.veryVerbose) {
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.log(`\nFound ${files.length} files in ${dir}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (options.veryVerbose) {
|
|
138
|
+
// eslint-disable-next-line no-console
|
|
139
|
+
console.log(files);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const bar = multibar.create(files.length, 0);
|
|
143
|
+
|
|
144
|
+
files.forEach((file) => {
|
|
145
|
+
if (options.verbose) {
|
|
146
|
+
// eslint-disable-next-line no-console
|
|
147
|
+
console.log(`Parsing file ${file}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
bar.increment(1, { filename: dir });
|
|
151
|
+
|
|
152
|
+
const parsedJsDocumentFile = parseFile(file, jsDocumentCommentsToOpenApi, this.verbose);
|
|
153
|
+
|
|
154
|
+
spec.addData(parsedJsDocumentFile.map((item) => item.spec));
|
|
155
|
+
|
|
156
|
+
const parsedSwaggerJsDocumentFile = parseFile(file, swaggerJsDocumentCommentsToOpenApi, this.verbose);
|
|
157
|
+
|
|
158
|
+
spec.addData(parsedSwaggerJsDocumentFile.map((item) => item.spec));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await SwaggerParser.validate(JSON.parse(JSON.stringify(spec)));
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// eslint-disable-next-line no-console
|
|
166
|
+
console.error(error.toJSON());
|
|
167
|
+
// eslint-disable-next-line no-undef
|
|
168
|
+
process.exit();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const output = options.output || "swagger.json";
|
|
172
|
+
|
|
173
|
+
multibar.stop();
|
|
174
|
+
|
|
175
|
+
fs.writeFileSync(output, JSON.stringify(spec, null, 2));
|
|
176
|
+
|
|
177
|
+
// eslint-disable-next-line no-console
|
|
178
|
+
console.log(`\nSwagger specification is ready, check the${output}file.`);
|
|
179
|
+
});
|
|
180
|
+
// eslint-disable-next-line no-undef
|
|
181
|
+
program.parse(process.argv);
|
|
182
|
+
|
|
183
|
+
// eslint-disable-next-line no-undef
|
|
184
|
+
if (process.argv.slice(2).length === 0) {
|
|
185
|
+
program.help();
|
|
186
|
+
// eslint-disable-next-line no-undef
|
|
187
|
+
process.exit();
|
|
188
|
+
}
|