@team-supercharge/oasg 7.0.0 → 7.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/CHANGELOG.md +16 -0
- package/README.md +50 -0
- package/bin/bundler.js +34 -0
- package/bin/dump.js +11 -0
- package/bin/exec.js +14 -0
- package/bin/merger.js +98 -85
- package/bin/oasg +18 -13
- package/bin/openapi-target.js +65 -0
- package/bin/overrider.js +3 -4
- package/config.schema.yml +18 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [7.1.0](https://gitlab.com/team-supercharge/oasg/compare/v7.0.0...v7.1.0) (2023-02-22)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* merge complete specifications with every possible field ([60b2e61](https://gitlab.com/team-supercharge/oasg/commit/60b2e614fd72238a9b64a77cc975de24f08f75cb))
|
|
11
|
+
* **openapi:** add openapi target for outputting documentation ([051da63](https://gitlab.com/team-supercharge/oasg/commit/051da63eee8bf3eede9c5ca84ea130713128596c))
|
|
12
|
+
* **openapi:** add option to sort schemas alphabetically ([0611f5e](https://gitlab.com/team-supercharge/oasg/commit/0611f5e2ba6d65bdb78fd8666a98f69c4d3edd8a))
|
|
13
|
+
* **openapi:** bundle specifications using @redocly/cli ([d50fd9c](https://gitlab.com/team-supercharge/oasg/commit/d50fd9c5a987c8dfe476b8659710dcbf52557dca))
|
|
14
|
+
* use js-yaml to dump YAML in a more nicely formatted way ([8728bcd](https://gitlab.com/team-supercharge/oasg/commit/8728bcd8c3442531290d487571ae1652ebe5b691))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Refactoring
|
|
18
|
+
|
|
19
|
+
* handle dumping YAML files centralized ([1d16b05](https://gitlab.com/team-supercharge/oasg/commit/1d16b059d180bb655befe2e0d905017bd94309b4))
|
|
20
|
+
|
|
5
21
|
## [7.0.0](https://gitlab.com/team-supercharge/oasg/compare/v6.2.1...v7.0.0) (2023-02-21)
|
|
6
22
|
|
|
7
23
|
|
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ Design APIs in OpenAPI 3.0 format, lint them, and generate client/server package
|
|
|
31
31
|
* [Python](#python)
|
|
32
32
|
* [Contract-Testing](#contract-testing)
|
|
33
33
|
* [NestJS](#nestjs)
|
|
34
|
+
* [OpenAPI](#openapi)
|
|
34
35
|
* [Template Customization](#template-customization)
|
|
35
36
|
* [Migration Guide](#migration-guide)
|
|
36
37
|
* [Roadmap](#roadmap)
|
|
@@ -559,6 +560,55 @@ describe('Auth', function () {
|
|
|
559
560
|
- array of enums in query/header/path/form parameters are not validated (stay as string)
|
|
560
561
|
- no multidimensional array validation in DTOs
|
|
561
562
|
|
|
563
|
+
#### OpenAPI
|
|
564
|
+
|
|
565
|
+
```json
|
|
566
|
+
{
|
|
567
|
+
"id": "api-docs",
|
|
568
|
+
"type": "openapi",
|
|
569
|
+
"source": "source-simple",
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
|Parameter| Description| Required | Default |
|
|
574
|
+
|-|-|-|-|
|
|
575
|
+
| fileName | Name of the generated file | N | `openapi.yaml` |
|
|
576
|
+
| bundle | Bundle specification into a single file | N | `true` |
|
|
577
|
+
| sortSchemas | Sort `components.schemas` alphabetically | N | `true` |
|
|
578
|
+
| decorators | Array of files for decorator functions | N | `[]` |
|
|
579
|
+
|
|
580
|
+
The target specification is by default bundled into a single file (resolving external dependencies) using the [@redocly/cli package's](https://www.npmjs.com/package/@redocly/cli) `bundle` command.
|
|
581
|
+
|
|
582
|
+
> ⚠️ It is higly recommended to keep `bundle` on, as external refs won't be part of the target artifact.
|
|
583
|
+
|
|
584
|
+
Decorator files must export a `decorate(document)` function which transforms the parsed OpenAPI document and returns it at the end of the function.
|
|
585
|
+
|
|
586
|
+
Full example with decorators:
|
|
587
|
+
|
|
588
|
+
```json
|
|
589
|
+
{
|
|
590
|
+
"id": "api-docs",
|
|
591
|
+
"type": "openapi",
|
|
592
|
+
"source": "source-simple",
|
|
593
|
+
"fileName": "my-openapi-file.yaml",
|
|
594
|
+
"bundle": false,
|
|
595
|
+
"sortSchemas": false,
|
|
596
|
+
"decorators": ["decorators/one", "decorators/two"]
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
```js
|
|
601
|
+
// decorators/one.js
|
|
602
|
+
|
|
603
|
+
function decorate(document) {
|
|
604
|
+
document.info.title = 'My Custom Decorated Title';
|
|
605
|
+
|
|
606
|
+
return document;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
exports.decorate = decorate;
|
|
610
|
+
```
|
|
611
|
+
|
|
562
612
|
## Template Customization
|
|
563
613
|
|
|
564
614
|
Using the comming `templateDir` config parameter there is a possibility to customize the templates used during generation.
|
package/bin/bundler.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
const { exec } = require(`${__dirname}/exec.js`);
|
|
5
|
+
|
|
6
|
+
async function bundleSpecification(source, file) {
|
|
7
|
+
// do NOT bundle by default for any sources
|
|
8
|
+
if (source.bundle === undefined || source.bundle === false) {
|
|
9
|
+
return file;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let outFile;
|
|
13
|
+
|
|
14
|
+
if (!file.startsWith("./out/.tmp")) {
|
|
15
|
+
if (!fs.existsSync('./out')) {
|
|
16
|
+
fs.mkdirSync('./out');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync('./out/.tmp')) {
|
|
20
|
+
fs.mkdirSync('./out/.tmp');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
outFile = `./out/.tmp/${source.id}-${crypto.randomBytes(4).readUInt32LE(0)}`;
|
|
24
|
+
} else {
|
|
25
|
+
outFile = file;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// using @redocly/cli to bundle
|
|
29
|
+
exec(`npx redocly bundle ${file} --keep-url-references --output ${outFile}`);
|
|
30
|
+
|
|
31
|
+
return outFile;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
exports.bundleSpecification = bundleSpecification;
|
package/bin/dump.js
ADDED
package/bin/exec.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { exit } = require('process');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
|
|
4
|
+
function exec(command, stdio) {
|
|
5
|
+
try {
|
|
6
|
+
execSync(command, { stdio: stdio || 'inherit' });
|
|
7
|
+
}
|
|
8
|
+
catch (e) {
|
|
9
|
+
console.error(e.message);
|
|
10
|
+
exit(1);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.exec = exec;
|
package/bin/merger.js
CHANGED
|
@@ -1,65 +1,64 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const YAML = require('yamljs');
|
|
5
4
|
const SwaggerParser = require("@apidevtools/swagger-parser");
|
|
6
5
|
|
|
6
|
+
const { dump } = require(`${__dirname}/dump.js`);
|
|
7
|
+
|
|
7
8
|
async function merge(source) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
if (!fs.existsSync('./out')) {
|
|
10
|
+
fs.mkdirSync('./out');
|
|
11
|
+
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if (source.inputs.length == 1) {
|
|
14
|
+
return source.inputs[0];
|
|
15
|
+
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
if (!fs.existsSync('./out/.tmp')) {
|
|
18
|
+
fs.mkdirSync('./out/.tmp');
|
|
19
|
+
}
|
|
20
|
+
const outFile = `./out/.tmp/${source.id}-${crypto.randomBytes(4).readUInt32LE(0)}.yaml`;
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
const document = await mergeDocuments(source.inputs);
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
dump(document, outFile);
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
return outFile;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
async function mergeDocuments(documents) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
for (var i = 1; i < documents.length; i++) {
|
|
33
|
-
let nextDoc = await SwaggerParser.parse(documents[i]);
|
|
34
|
-
nextDoc = resolveExternalSpecifications(documents[i], nextDoc);
|
|
35
|
-
finalDoc = mergeDocument(finalDoc, nextDoc);
|
|
36
|
-
}
|
|
30
|
+
let finalDoc = await SwaggerParser.parse(documents[0]);
|
|
31
|
+
finalDoc = resolveExternalSpecifications(documents[0], finalDoc);
|
|
37
32
|
|
|
38
|
-
|
|
33
|
+
for (var i = 1; i < documents.length; i++) {
|
|
34
|
+
let nextDoc = await SwaggerParser.parse(documents[i]);
|
|
35
|
+
nextDoc = resolveExternalSpecifications(documents[i], nextDoc);
|
|
36
|
+
finalDoc = mergeDocument(finalDoc, nextDoc);
|
|
37
|
+
}
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
return finalDoc;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
function resolveExternalSpecifications(currentFile, document) {
|
|
44
|
-
|
|
43
|
+
const files = new Set([]);
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
getAllReferencedFiles(document, files);
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
if (files.size > 0) {
|
|
48
|
+
files.forEach(refFile => {
|
|
49
|
+
const newRefFile = getAbsolutePathForReferencedFile(currentFile, refFile);
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
document = updateReferences(document, refFile, newRefFile);
|
|
52
|
+
})
|
|
53
|
+
} else {
|
|
54
|
+
console.log('No external files');
|
|
55
|
+
}
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
return document;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
function getAllReferencedFiles(document, files) {
|
|
62
|
-
|
|
61
|
+
if (typeof document === 'object') {
|
|
63
62
|
// iterating over the object using for..in
|
|
64
63
|
for (var keys in document) {
|
|
65
64
|
//checking if the current value is an object itself
|
|
@@ -80,53 +79,53 @@ function getAllReferencedFiles(document, files) {
|
|
|
80
79
|
} else {
|
|
81
80
|
// else checking for ref property, getting the file path if its an external local path
|
|
82
81
|
if (keys === "$ref") {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
if (document[keys].startsWith(".")) {
|
|
83
|
+
const externalFilePath = document[keys].split('#')[0];
|
|
84
|
+
files.add(externalFilePath);
|
|
85
|
+
}
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
}
|
|
90
|
+
return document;
|
|
92
91
|
}
|
|
93
92
|
|
|
94
93
|
function getAbsolutePathForReferencedFile(currentFile, referencedFile) {
|
|
95
|
-
|
|
94
|
+
const originalPath = process.cwd();
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
process.chdir(path.dirname(`${originalPath}/${currentFile}`));
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
const absolutePath = path.resolve(process.cwd(), referencedFile);
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
// move back
|
|
101
|
+
process.chdir(originalPath);
|
|
103
102
|
|
|
104
|
-
|
|
103
|
+
return absolutePath;
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
function moveReferencedFiles(folderName, currentFile, files) {
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
// save current process dir
|
|
108
|
+
const originalPath = process.cwd();
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
// create output temp directory
|
|
111
|
+
const outputFolderPath = `${originalPath}/out/.tmp/${folderName}`;
|
|
112
|
+
fs.mkdirSync(outputFolderPath);
|
|
114
113
|
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
// move to original file directory, in order to find referenced files for copy command
|
|
115
|
+
process.chdir(path.dirname(`${originalPath}/${currentFile}`));
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
files.forEach(item => {
|
|
118
|
+
const outName = path.basename(item);
|
|
119
|
+
// copy referenced file to tmp output folder
|
|
120
|
+
fs.copyFileSync(item, `${outputFolderPath}/${outName}`);
|
|
121
|
+
});
|
|
123
122
|
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
// move back
|
|
124
|
+
process.chdir(originalPath);
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
function updateReferences(document, oldValue, newValue) {
|
|
129
|
-
|
|
128
|
+
if (typeof document === 'object') {
|
|
130
129
|
// iterating over the object using for..in
|
|
131
130
|
for (var keys in document) {
|
|
132
131
|
//checking if the current value is an object itself
|
|
@@ -146,36 +145,50 @@ function updateReferences(document, oldValue, newValue) {
|
|
|
146
145
|
} else {
|
|
147
146
|
// else checking for ref property, if then replace old value with new one
|
|
148
147
|
if (keys === "$ref") {
|
|
149
|
-
|
|
148
|
+
if (document[keys].includes(oldValue)) {
|
|
150
149
|
let keyValue = document[keys].replace(oldValue, newValue);
|
|
151
150
|
document[keys] = keyValue;
|
|
152
|
-
|
|
151
|
+
}
|
|
153
152
|
}
|
|
154
153
|
}
|
|
155
154
|
}
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
}
|
|
156
|
+
return document;
|
|
158
157
|
}
|
|
159
158
|
|
|
160
159
|
function mergeDocument(leftDoc, rightDoc) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
160
|
+
// not overriding: info, externalDocs (always from first file)
|
|
161
|
+
// not merging: servers, webhooks, security
|
|
162
|
+
|
|
163
|
+
// paths
|
|
164
|
+
const mergedPaths = { ...leftDoc.paths, ...rightDoc.paths };
|
|
165
|
+
leftDoc.paths = mergedPaths;
|
|
166
|
+
|
|
167
|
+
// components
|
|
168
|
+
const mergedSchemas = { ...leftDoc.components.schemas, ...rightDoc.components.schemas };
|
|
169
|
+
const mergedResponses = { ...leftDoc.components.responses, ...rightDoc.components.responses };
|
|
170
|
+
const mergedParameters = { ...leftDoc.components.parameters, ...rightDoc.components.parameters };
|
|
171
|
+
const mergedExamples = { ...leftDoc.components.examples, ...rightDoc.components.examples };
|
|
172
|
+
const mergedRequestBodies = { ...leftDoc.components.requestBodies, ...rightDoc.components.requestBodies };
|
|
173
|
+
const mergedHeaders = { ...leftDoc.components.headers, ...rightDoc.components.headers };
|
|
174
|
+
const mergedSecuritySchemes = { ...leftDoc.components.securitySchemes, ...rightDoc.components.securitySchemes };
|
|
175
|
+
const mergedLinks = { ...leftDoc.components.links, ...rightDoc.components.links };
|
|
176
|
+
const mergedCallbacks = { ...leftDoc.components.callbacks, ...rightDoc.components.callbacks };
|
|
177
|
+
leftDoc.components.schemas = mergedSchemas;
|
|
178
|
+
leftDoc.components.responses = mergedResponses;
|
|
179
|
+
leftDoc.components.parameters = mergedParameters;
|
|
180
|
+
leftDoc.components.examples = mergedExamples;
|
|
181
|
+
leftDoc.components.requestBodies = mergedRequestBodies;
|
|
182
|
+
leftDoc.components.headers = mergedHeaders;
|
|
183
|
+
leftDoc.components.securitySchemes = mergedSecuritySchemes;
|
|
184
|
+
leftDoc.components.links = mergedLinks;
|
|
185
|
+
leftDoc.components.callbacks = mergedCallbacks;
|
|
186
|
+
|
|
187
|
+
// tags
|
|
188
|
+
const mergedTags = Array.from(new Set(leftDoc.tags.concat(rightDoc.tags)));
|
|
189
|
+
leftDoc.tags = mergedTags;
|
|
190
|
+
|
|
191
|
+
return leftDoc;
|
|
179
192
|
}
|
|
180
193
|
|
|
181
194
|
exports.merge = merge;
|
package/bin/oasg
CHANGED
|
@@ -12,11 +12,12 @@ const commandExistsSync = require('command-exists').sync;
|
|
|
12
12
|
const express = require('express');
|
|
13
13
|
const swaggerUi = require('swagger-ui-express');
|
|
14
14
|
const expressHttpProxy = require('express-http-proxy');
|
|
15
|
-
|
|
16
15
|
const { exit } = require('process');
|
|
17
|
-
|
|
16
|
+
|
|
17
|
+
const { exec } = require(`${__dirname}/exec.js`);
|
|
18
18
|
const { merge } = require(`${__dirname}/merger.js`);
|
|
19
19
|
const { applyOverrides } = require(`${__dirname}/overrider.js`);
|
|
20
|
+
const { openApiTarget } = require(`${__dirname}/openapi-target.js`);
|
|
20
21
|
|
|
21
22
|
let VERSION = JSON.parse(fs.readFileSync('package.json')).version;
|
|
22
23
|
|
|
@@ -60,6 +61,7 @@ const DEFAULT_GENERATOR_ID_MAPPING = {
|
|
|
60
61
|
"python": 'python',
|
|
61
62
|
"contract-testing": 'typescript-node',
|
|
62
63
|
"nestjs": 'typescript-angular',
|
|
64
|
+
"openapi": undefined,
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
let config;
|
|
@@ -424,6 +426,13 @@ async function generate(argv) {
|
|
|
424
426
|
|
|
425
427
|
targetIds.forEach(targetId => {
|
|
426
428
|
const target = config.targets.find(t => t.id === targetId);
|
|
429
|
+
|
|
430
|
+
// handle docs target
|
|
431
|
+
if (target.type === 'openapi') {
|
|
432
|
+
openApiTarget(target, sources[target.source], VERSION);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
427
436
|
const binary = fetchBinary(target);
|
|
428
437
|
let formatter;
|
|
429
438
|
|
|
@@ -450,6 +459,13 @@ async function publish(argv) {
|
|
|
450
459
|
|
|
451
460
|
targetIds.forEach(targetId => {
|
|
452
461
|
const target = config.targets.find(t => t.id === targetId);
|
|
462
|
+
|
|
463
|
+
// handle docs target
|
|
464
|
+
if (target.type === 'openapi') {
|
|
465
|
+
console.log('publishing need to be implemented manuallly for `openapi` targets');
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
453
469
|
const binary = fetchBinary(target);
|
|
454
470
|
let formatter;
|
|
455
471
|
|
|
@@ -621,14 +637,3 @@ function determineArtifactVersion(argv) {
|
|
|
621
637
|
function determinePreRelease(argv) {
|
|
622
638
|
return argv.preRelease ? true : false;
|
|
623
639
|
}
|
|
624
|
-
|
|
625
|
-
// execute command
|
|
626
|
-
function exec(command) {
|
|
627
|
-
try {
|
|
628
|
-
execSync(command, { stdio: 'inherit' });
|
|
629
|
-
}
|
|
630
|
-
catch (e) {
|
|
631
|
-
console.error(e.message);
|
|
632
|
-
exit(1);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const SwaggerParser = require("@apidevtools/swagger-parser");
|
|
3
|
+
|
|
4
|
+
const { dump } = require(`${__dirname}/dump.js`);
|
|
5
|
+
const { exec } = require(`${__dirname}/exec.js`);
|
|
6
|
+
|
|
7
|
+
async function openApiTarget(target, source, version) {
|
|
8
|
+
const outDir = `out/${target.id}`;
|
|
9
|
+
let outFile = target.fileName || `openapi.yaml`;
|
|
10
|
+
outFile = `${outDir}/${outFile}`;
|
|
11
|
+
|
|
12
|
+
if (fs.existsSync(outDir)) {
|
|
13
|
+
fs.rmdirSync(outDir, { recursive: true })
|
|
14
|
+
}
|
|
15
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
16
|
+
|
|
17
|
+
// bundle spec to a single file by default
|
|
18
|
+
const bundle = target.bundle === undefined || target.bundle;
|
|
19
|
+
if (bundle) {
|
|
20
|
+
exec(`npx redocly bundle ${source} --keep-url-references --output ${outFile}`, 'pipe');
|
|
21
|
+
console.log(`bundled specification`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// parse document
|
|
25
|
+
let document = await SwaggerParser.parse(bundle ? outFile : source);
|
|
26
|
+
|
|
27
|
+
// set version of target document
|
|
28
|
+
document.info.version = version;
|
|
29
|
+
|
|
30
|
+
// sort schemas alphabeticaly
|
|
31
|
+
const sortSchemas = target.sortSchemas === undefined || target.sortSchemas;
|
|
32
|
+
if (sortSchemas) {
|
|
33
|
+
document.components.schemas = Object.keys(document.components.schemas)
|
|
34
|
+
.sort()
|
|
35
|
+
.reduce((obj, key) => { obj[key] = document.components.schemas[key]; return obj; }, {});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// apply custom decorators
|
|
39
|
+
const decorators = target.decorators || [];
|
|
40
|
+
decorators.forEach(d => {
|
|
41
|
+
const decoratorFile = `${d}.js`;
|
|
42
|
+
const decoratorPath = `${process.cwd()}/${decoratorFile}`;
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(decoratorPath)) {
|
|
45
|
+
console.error(`skipped decorator '${decoratorFile}' - file does not exist`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const decorator = require(decoratorPath);
|
|
50
|
+
|
|
51
|
+
if (!decorator.decorate) {
|
|
52
|
+
console.error(`skipped decorator '${decoratorFile}' - must export a 'decorate(document)' function`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
document = decorator.decorate(document);
|
|
57
|
+
|
|
58
|
+
console.log(`applied decorator '${decoratorFile}'`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// write file
|
|
62
|
+
dump(document, outFile);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
exports.openApiTarget = openApiTarget;
|
package/bin/overrider.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const YAML = require('yamljs');
|
|
4
3
|
const SwaggerParser = require("@apidevtools/swagger-parser");
|
|
5
4
|
|
|
5
|
+
const { dump } = require(`${__dirname}/dump.js`);
|
|
6
|
+
|
|
6
7
|
async function applyOverrides(source, file) {
|
|
7
8
|
if (!source.overrides) {
|
|
8
9
|
return file;
|
|
@@ -54,9 +55,7 @@ async function applyOverrides(source, file) {
|
|
|
54
55
|
document.info.license = overrides.license;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fs.writeFileSync(outFile, content);
|
|
58
|
+
dump(document, outFile);
|
|
60
59
|
|
|
61
60
|
return outFile;
|
|
62
61
|
}
|
package/config.schema.yml
CHANGED
|
@@ -21,6 +21,7 @@ properties:
|
|
|
21
21
|
- $ref: '#/targets/Python'
|
|
22
22
|
- $ref: '#/targets/ContractTesting'
|
|
23
23
|
- $ref: '#/targets/NestJS'
|
|
24
|
+
- $ref: '#/targets/OpenAPI'
|
|
24
25
|
required:
|
|
25
26
|
- targets
|
|
26
27
|
additionalProperties: false
|
|
@@ -285,6 +286,23 @@ targets:
|
|
|
285
286
|
- packageName
|
|
286
287
|
- repository
|
|
287
288
|
|
|
289
|
+
OpenAPI:
|
|
290
|
+
allOf:
|
|
291
|
+
- $ref: '#/targets/Base'
|
|
292
|
+
- properties:
|
|
293
|
+
type:
|
|
294
|
+
pattern: "^openapi$"
|
|
295
|
+
fileName:
|
|
296
|
+
type: string
|
|
297
|
+
bundle:
|
|
298
|
+
type: boolean
|
|
299
|
+
sortSchemas:
|
|
300
|
+
type: boolean
|
|
301
|
+
decorators:
|
|
302
|
+
type: array
|
|
303
|
+
items:
|
|
304
|
+
type: string
|
|
305
|
+
|
|
288
306
|
definitions:
|
|
289
307
|
# default
|
|
290
308
|
SourceOverrides:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-supercharge/oasg",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "Node-based tool to lint OpenAPI documents and generate clients, servers and documentation from them",
|
|
5
5
|
"author": "Supercharge",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@apidevtools/json-schema-ref-parser": "^9.0.6",
|
|
17
17
|
"@apidevtools/swagger-parser": "^10.0.2",
|
|
18
|
+
"@redocly/cli": "^1.0.0-beta.123",
|
|
18
19
|
"@stoplight/prism-cli": "^4.1.0",
|
|
19
20
|
"@stoplight/spectral": "5.9.0",
|
|
20
21
|
"ajv": "^8.11.0",
|
|
21
22
|
"command-exists": "^1.2.9",
|
|
22
23
|
"express": "^4.17.1",
|
|
23
24
|
"express-http-proxy": "^1.6.2",
|
|
25
|
+
"js-yaml": "^4.1.0",
|
|
24
26
|
"json-schema-merge-allof": "^0.7.0",
|
|
25
27
|
"openapi-types": "^7.0.1",
|
|
26
28
|
"openapi-typescript-validator-ext-ref": "^3.2.0-external-ref-support",
|