@visulima/jsdoc-open-api 1.0.2 → 1.0.3
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 +13 -0
- package/README.md +91 -1
- package/package.json +4 -3
- package/src/exported.d.ts +266 -0
- package/src/index.ts +7 -0
- package/src/jsdoc/comments-to-open-api.ts +402 -0
- package/src/options.ts +28 -0
- package/src/parse-file.ts +52 -0
- package/src/spec-builder.ts +61 -0
- package/src/swagger-jsdoc/comments-to-open-api.ts +89 -0
- package/src/swagger-jsdoc/organize-swagger-object.ts +66 -0
- package/src/swagger-jsdoc/utils.ts +43 -0
- package/src/util/customizer.ts +9 -0
- package/src/util/load-definition.ts +22 -0
- package/src/util/object-merge.ts +20 -0
- package/src/util/yaml-loc.ts +17 -0
- package/src/webpack/swagger-compiler-plugin.ts +168 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Spec } from "comment-parser";
|
|
2
|
+
import mergeWith from "lodash.mergewith";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A recursive deep-merge that ignores null values when merging.
|
|
6
|
+
* This returns the merged object and does not mutate.
|
|
7
|
+
* @param {object} first the first object to get merged
|
|
8
|
+
* @param {object} second the second object to get merged
|
|
9
|
+
*/
|
|
10
|
+
export const mergeDeep = (first?: object, second?: object) => mergeWith({}, first, second, (a, b) => (b === null ? a : undefined));
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Checks if there is any properties of the input object which are an empty object
|
|
14
|
+
* @param {object} object - the object to check
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
export const hasEmptyProperty = (object: Record<string, any>): boolean => Object.keys(object)
|
|
18
|
+
.map((key) => object[key])
|
|
19
|
+
.every((keyObject) => typeof keyObject === "object" && Object.keys(keyObject).every((key) => !(key in keyObject)));
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} tag
|
|
23
|
+
* @param {array} tags
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
export const isTagPresentInTags = (tag: Spec, tags: Spec[]) => tags.some((targetTag) => tag.name === targetTag.name);
|
|
27
|
+
|
|
28
|
+
export const getSwaggerVersionFromSpec = (tag: Spec) => {
|
|
29
|
+
switch (tag.tag) {
|
|
30
|
+
case "openapi": {
|
|
31
|
+
return "v3";
|
|
32
|
+
}
|
|
33
|
+
case "asyncapi": {
|
|
34
|
+
return "v4";
|
|
35
|
+
}
|
|
36
|
+
case "swagger": {
|
|
37
|
+
return "v2";
|
|
38
|
+
}
|
|
39
|
+
default: {
|
|
40
|
+
return "v2";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import yaml from "yaml";
|
|
4
|
+
|
|
5
|
+
import type { BaseDefinition } from "../exported";
|
|
6
|
+
|
|
7
|
+
function parseFile(file: string): BaseDefinition {
|
|
8
|
+
const extension = path.extname(file);
|
|
9
|
+
if (extension !== ".yaml" && extension !== ".yml" && extension !== ".json") {
|
|
10
|
+
throw new Error("OpenAPI definition path must be YAML or JSON.");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const fileContent = fs.readFileSync(file, { encoding: "utf8" });
|
|
14
|
+
|
|
15
|
+
if (extension === ".yaml" || extension === ".yml") {
|
|
16
|
+
return yaml.parse(fileContent);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return JSON.parse(fileContent);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default parseFile;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function objectMerge<T>(a: T, b: T) {
|
|
2
|
+
Object.keys(b as object).forEach((key) => {
|
|
3
|
+
if (a[key as keyof typeof b] === undefined) {
|
|
4
|
+
// eslint-disable-next-line no-param-reassign
|
|
5
|
+
a[key as keyof typeof b] = {
|
|
6
|
+
...b[key as keyof typeof b],
|
|
7
|
+
};
|
|
8
|
+
} else {
|
|
9
|
+
Object.keys(b[key as keyof typeof b] as object).forEach((subKey) => {
|
|
10
|
+
// eslint-disable-next-line no-param-reassign
|
|
11
|
+
(a[key as keyof typeof b] as { [key: string]: object })[subKey] = {
|
|
12
|
+
...(a[key as keyof typeof b] as { [key: string]: object })[subKey],
|
|
13
|
+
...(b[key as keyof typeof b] as { [key: string]: object })[subKey],
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default objectMerge;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
function yamlLoc(string: string): number {
|
|
2
|
+
// Break string into lines.
|
|
3
|
+
const split = string.split(/\r\n|\r|\n/);
|
|
4
|
+
|
|
5
|
+
const filtered = split.filter((line) => {
|
|
6
|
+
// Remove comments.
|
|
7
|
+
if (/^\s*(#\s*.*)?$/.test(line)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
// Remove empty lines.
|
|
11
|
+
return line.trim().length > 0;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return filtered.length;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default yamlLoc;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
2
|
+
import { collect } from "@visulima/readdir";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
|
+
import { exit } from "node:process";
|
|
6
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
7
|
+
import { Compiler } from "webpack";
|
|
8
|
+
|
|
9
|
+
import type { BaseDefinition } from "../exported";
|
|
10
|
+
import jsDocumentCommentsToOpenApi from "../jsdoc/comments-to-open-api";
|
|
11
|
+
import parseFile from "../parse-file";
|
|
12
|
+
import SpecBuilder from "../spec-builder";
|
|
13
|
+
import swaggerJsDocumentCommentsToOpenApi from "../swagger-jsdoc/comments-to-open-api";
|
|
14
|
+
|
|
15
|
+
const exclude = [
|
|
16
|
+
"coverage/**",
|
|
17
|
+
".github/**",
|
|
18
|
+
"packages/*/test{,s}/**",
|
|
19
|
+
"**/*.d.ts",
|
|
20
|
+
"test{,s}/**",
|
|
21
|
+
"test{,-*}.{js,cjs,mjs,ts,tsx,jsx,yaml,yml}",
|
|
22
|
+
"**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx,yaml,yml}",
|
|
23
|
+
"**/__tests__/**",
|
|
24
|
+
"**/{ava,babel,nyc}.config.{js,cjs,mjs}",
|
|
25
|
+
"**/jest.config.{js,cjs,mjs,ts}",
|
|
26
|
+
"**/{karma,rollup,webpack}.config.js",
|
|
27
|
+
"**/.{eslint,mocha}rc.{js,cjs}",
|
|
28
|
+
"**/.{travis,yarnrc}.yml",
|
|
29
|
+
"**/{docker-compose,docker}.yml",
|
|
30
|
+
"**/.yamllint.{yaml,yml}",
|
|
31
|
+
"**/node_modules/**",
|
|
32
|
+
"**/pnpm-lock.yaml",
|
|
33
|
+
"**/pnpm-workspace.yaml",
|
|
34
|
+
"**/{package,package-lock}.json",
|
|
35
|
+
"**/yarn.lock",
|
|
36
|
+
"**/package.json5",
|
|
37
|
+
"**/.next/**",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const errorHandler = (error: any) => {
|
|
41
|
+
if (error) {
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.error(error);
|
|
44
|
+
exit(1);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
class SwaggerCompilerPlugin {
|
|
49
|
+
private readonly swaggerDefinition: BaseDefinition;
|
|
50
|
+
|
|
51
|
+
private readonly sources: string[];
|
|
52
|
+
|
|
53
|
+
private readonly verbose: boolean;
|
|
54
|
+
|
|
55
|
+
private readonly ignore: string | ReadonlyArray<string>;
|
|
56
|
+
|
|
57
|
+
assetsPath: string;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
assetsPath: string,
|
|
61
|
+
sources: string[],
|
|
62
|
+
swaggerDefinition: BaseDefinition,
|
|
63
|
+
options: {
|
|
64
|
+
verbose?: boolean;
|
|
65
|
+
ignore?: string | ReadonlyArray<string>;
|
|
66
|
+
},
|
|
67
|
+
) {
|
|
68
|
+
this.assetsPath = assetsPath;
|
|
69
|
+
this.swaggerDefinition = swaggerDefinition;
|
|
70
|
+
this.sources = sources;
|
|
71
|
+
this.verbose = options.verbose || false;
|
|
72
|
+
this.ignore = options.ignore || [];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
apply(compiler: Compiler) {
|
|
76
|
+
compiler.hooks.make.tapAsync("SwaggerCompilerPlugin", async (_, callback: VoidFunction) => {
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.log("Build paused, switching to swagger build");
|
|
79
|
+
|
|
80
|
+
const spec = new SpecBuilder(this.swaggerDefinition);
|
|
81
|
+
|
|
82
|
+
// eslint-disable-next-line no-restricted-syntax,unicorn/prevent-abbreviations
|
|
83
|
+
for await (const dir of this.sources) {
|
|
84
|
+
const files = await collect(dir, {
|
|
85
|
+
// eslint-disable-next-line @rushstack/security/no-unsafe-regexp
|
|
86
|
+
skip: [...this.ignore, ...exclude],
|
|
87
|
+
extensions: [".js", ".cjs", ".mjs", ".ts", ".tsx", ".jsx", ".yaml", ".yml"],
|
|
88
|
+
minimatchOptions: {
|
|
89
|
+
match: {
|
|
90
|
+
debug: this.verbose,
|
|
91
|
+
matchBase: true,
|
|
92
|
+
},
|
|
93
|
+
skip: {
|
|
94
|
+
debug: this.verbose,
|
|
95
|
+
matchBase: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (this.verbose) {
|
|
101
|
+
// eslint-disable-next-line no-console
|
|
102
|
+
console.log(`Found ${files.length} files in ${dir}`);
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.log(files);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
files.forEach((file) => {
|
|
108
|
+
if (this.verbose) {
|
|
109
|
+
// eslint-disable-next-line no-console
|
|
110
|
+
console.log(`Parsing file ${file}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const parsedJsDocumentFile = parseFile(file, jsDocumentCommentsToOpenApi, this.verbose);
|
|
115
|
+
|
|
116
|
+
spec.addData(parsedJsDocumentFile.map((item) => item.spec));
|
|
117
|
+
|
|
118
|
+
const parsedSwaggerJsDocumentFile = parseFile(file, swaggerJsDocumentCommentsToOpenApi, this.verbose);
|
|
119
|
+
|
|
120
|
+
spec.addData(parsedSwaggerJsDocumentFile.map((item) => item.spec));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// eslint-disable-next-line no-console
|
|
123
|
+
console.error(error);
|
|
124
|
+
exit(1);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
if (this.verbose) {
|
|
131
|
+
// eslint-disable-next-line no-console
|
|
132
|
+
console.log("Validating swagger spec");
|
|
133
|
+
// eslint-disable-next-line no-console
|
|
134
|
+
console.log(JSON.stringify(spec, null, 2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await SwaggerParser.validate(JSON.parse(JSON.stringify(spec)));
|
|
138
|
+
} catch (error: any) {
|
|
139
|
+
// eslint-disable-next-line no-console
|
|
140
|
+
console.error(error.toJSON());
|
|
141
|
+
exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { assetsPath } = this;
|
|
145
|
+
|
|
146
|
+
fs.mkdir(dirname(assetsPath), { recursive: true }, (error) => {
|
|
147
|
+
if (error) {
|
|
148
|
+
errorHandler(error);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
fs.writeFile(assetsPath, JSON.stringify(spec, null, 2), errorHandler);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// eslint-disable-next-line unicorn/consistent-destructuring
|
|
155
|
+
if (this.verbose) {
|
|
156
|
+
// eslint-disable-next-line no-console,unicorn/consistent-destructuring
|
|
157
|
+
console.log(`Written swagger spec to "${this.assetsPath}" file`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// eslint-disable-next-line no-console
|
|
161
|
+
console.log("switching back to normal build");
|
|
162
|
+
|
|
163
|
+
callback();
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export default SwaggerCompilerPlugin;
|