@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 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
@@ -0,0 +1,11 @@
1
+ const fs = require('fs');
2
+ const yaml = require('js-yaml');
3
+
4
+ async function dump(document, fileName) {
5
+ const content = yaml.dump(document, { lineWidth: -1 });
6
+ fs.writeFileSync(fileName, content);
7
+
8
+ return fileName;
9
+ }
10
+
11
+ exports.dump = dump;
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
- if (!fs.existsSync('./out')) {
9
- fs.mkdirSync('./out');
10
- }
9
+ if (!fs.existsSync('./out')) {
10
+ fs.mkdirSync('./out');
11
+ }
11
12
 
12
- if (source.inputs.length == 1) {
13
- return source.inputs[0];
14
- }
13
+ if (source.inputs.length == 1) {
14
+ return source.inputs[0];
15
+ }
15
16
 
16
- if (!fs.existsSync('./out/.tmp')) {
17
- fs.mkdirSync('./out/.tmp');
18
- }
19
- const outFile = `./out/.tmp/${source.id}-${crypto.randomBytes(4).readUInt32LE(0)}.yaml`;
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
- const content = await mergeDocuments(source.inputs);
22
+ const document = await mergeDocuments(source.inputs);
22
23
 
23
- fs.writeFileSync(outFile, content);
24
+ dump(document, outFile);
24
25
 
25
- return outFile;
26
+ return outFile;
26
27
  }
27
28
 
28
29
  async function mergeDocuments(documents) {
29
- let finalDoc = await SwaggerParser.parse(documents[0]);
30
- finalDoc = resolveExternalSpecifications(documents[0], finalDoc);
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
- const data = YAML.dump(finalDoc);
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
- return data;
39
+ return finalDoc;
41
40
  }
42
41
 
43
42
  function resolveExternalSpecifications(currentFile, document) {
44
- const files = new Set([]);
43
+ const files = new Set([]);
45
44
 
46
- getAllReferencedFiles(document, files);
45
+ getAllReferencedFiles(document, files);
47
46
 
48
- if (files.size > 0) {
49
- files.forEach(refFile => {
50
- const newRefFile = getAbsolutePathForReferencedFile(currentFile, refFile);
47
+ if (files.size > 0) {
48
+ files.forEach(refFile => {
49
+ const newRefFile = getAbsolutePathForReferencedFile(currentFile, refFile);
51
50
 
52
- document = updateReferences(document, refFile, newRefFile);
53
- })
54
- } else {
55
- console.log('No external files');
56
- }
51
+ document = updateReferences(document, refFile, newRefFile);
52
+ })
53
+ } else {
54
+ console.log('No external files');
55
+ }
57
56
 
58
- return document;
57
+ return document;
59
58
  }
60
59
 
61
60
  function getAllReferencedFiles(document, files) {
62
- if (typeof document === 'object') {
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
- if (document[keys].startsWith(".")) {
84
- const externalFilePath = document[keys].split('#')[0];
85
- files.add(externalFilePath);
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
- return document;
89
+ }
90
+ return document;
92
91
  }
93
92
 
94
93
  function getAbsolutePathForReferencedFile(currentFile, referencedFile) {
95
- const originalPath = process.cwd();
94
+ const originalPath = process.cwd();
96
95
 
97
- process.chdir(path.dirname(`${originalPath}/${currentFile}`));
96
+ process.chdir(path.dirname(`${originalPath}/${currentFile}`));
98
97
 
99
- const absolutePath = path.resolve(process.cwd(), referencedFile);
98
+ const absolutePath = path.resolve(process.cwd(), referencedFile);
100
99
 
101
- // move back
102
- process.chdir(originalPath);
100
+ // move back
101
+ process.chdir(originalPath);
103
102
 
104
- return absolutePath;
103
+ return absolutePath;
105
104
  }
106
105
 
107
106
  function moveReferencedFiles(folderName, currentFile, files) {
108
- // save current process dir
109
- const originalPath = process.cwd();
107
+ // save current process dir
108
+ const originalPath = process.cwd();
110
109
 
111
- // create output temp directory
112
- const outputFolderPath = `${originalPath}/out/.tmp/${folderName}`;
113
- fs.mkdirSync(outputFolderPath);
110
+ // create output temp directory
111
+ const outputFolderPath = `${originalPath}/out/.tmp/${folderName}`;
112
+ fs.mkdirSync(outputFolderPath);
114
113
 
115
- // move to original file directory, in order to find referenced files for copy command
116
- process.chdir(path.dirname(`${originalPath}/${currentFile}`));
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
- files.forEach(item => {
119
- const outName = path.basename(item);
120
- // copy referenced file to tmp output folder
121
- fs.copyFileSync(item, `${outputFolderPath}/${outName}`);
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
- // move back
125
- process.chdir(originalPath);
123
+ // move back
124
+ process.chdir(originalPath);
126
125
  }
127
126
 
128
127
  function updateReferences(document, oldValue, newValue) {
129
- if (typeof document === 'object') {
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
- if (document[keys].includes(oldValue)) {
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
- return document;
155
+ }
156
+ return document;
158
157
  }
159
158
 
160
159
  function mergeDocument(leftDoc, rightDoc) {
161
-
162
- const mergedPaths = { ...leftDoc.paths, ...rightDoc.paths };
163
- const mergedSchemas = { ...leftDoc.components.schemas, ...rightDoc.components.schemas };
164
- const mergedParameters = { ...leftDoc.components.parameters, ...rightDoc.components.parameters };
165
- const mergedTags = Array.from(new Set(leftDoc.tags.concat(rightDoc.tags)));
166
- const mergedRequestBodies = { ...leftDoc.components.requestBodies, ...rightDoc.components.requestBodies };
167
- const mergedResponses = { ...leftDoc.components.responses, ...rightDoc.components.responses };
168
- const mergedHeaders = { ...leftDoc.components.headers, ...rightDoc.components.headers };
169
-
170
- leftDoc.paths = mergedPaths;
171
- leftDoc.components.schemas = mergedSchemas;
172
- leftDoc.components.parameters = mergedParameters;
173
- leftDoc.tags = mergedTags;
174
- leftDoc.components.requestBodies = mergedRequestBodies;
175
- leftDoc.components.responses = mergedResponses;
176
- leftDoc.components.headers = mergedHeaders;
177
-
178
- return leftDoc;
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
- const { execSync } = require('child_process');
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
- const content = YAML.dump(document);
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.0.0",
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",