@redocly/cli 1.0.0-beta.96
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 +39 -0
- package/bin/cli.js +3 -0
- package/lib/__mocks__/utils.d.ts +17 -0
- package/lib/__mocks__/utils.js +14 -0
- package/lib/__tests__/commands/bundle.test.d.ts +1 -0
- package/lib/__tests__/commands/bundle.test.js +92 -0
- package/lib/__tests__/commands/push-region.test.d.ts +1 -0
- package/lib/__tests__/commands/push-region.test.js +55 -0
- package/lib/__tests__/commands/push.test.d.ts +1 -0
- package/lib/__tests__/commands/push.test.js +153 -0
- package/lib/__tests__/utils.test.d.ts +1 -0
- package/lib/__tests__/utils.test.js +41 -0
- package/lib/assert-node-version.d.ts +1 -0
- package/lib/assert-node-version.js +10 -0
- package/lib/commands/bundle.d.ts +19 -0
- package/lib/commands/bundle.js +128 -0
- package/lib/commands/join.d.ts +7 -0
- package/lib/commands/join.js +421 -0
- package/lib/commands/lint.d.ts +11 -0
- package/lib/commands/lint.js +80 -0
- package/lib/commands/login.d.ts +6 -0
- package/lib/commands/login.js +28 -0
- package/lib/commands/preview-docs/index.d.ts +12 -0
- package/lib/commands/preview-docs/index.js +141 -0
- package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
- package/lib/commands/preview-docs/preview-server/hot.js +42 -0
- package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
- package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
- package/lib/commands/preview-docs/preview-server/preview-server.js +120 -0
- package/lib/commands/preview-docs/preview-server/server.d.ts +23 -0
- package/lib/commands/preview-docs/preview-server/server.js +85 -0
- package/lib/commands/push.d.ts +25 -0
- package/lib/commands/push.js +247 -0
- package/lib/commands/split/__tests__/index.test.d.ts +1 -0
- package/lib/commands/split/__tests__/index.test.js +70 -0
- package/lib/commands/split/index.d.ts +8 -0
- package/lib/commands/split/index.js +279 -0
- package/lib/commands/split/types.d.ts +37 -0
- package/lib/commands/split/types.js +52 -0
- package/lib/commands/stats.d.ts +5 -0
- package/lib/commands/stats.js +92 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +269 -0
- package/lib/js-utils.d.ts +3 -0
- package/lib/js-utils.js +16 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +5 -0
- package/lib/utils.d.ts +28 -0
- package/lib/utils.js +260 -0
- package/package.json +54 -0
- package/src/__mocks__/utils.ts +11 -0
- package/src/__tests__/commands/bundle.test.ts +120 -0
- package/src/__tests__/commands/push-region.test.ts +51 -0
- package/src/__tests__/commands/push.test.ts +156 -0
- package/src/__tests__/utils.test.ts +50 -0
- package/src/assert-node-version.ts +8 -0
- package/src/commands/bundle.ts +178 -0
- package/src/commands/join.ts +488 -0
- package/src/commands/lint.ts +110 -0
- package/src/commands/login.ts +19 -0
- package/src/commands/preview-docs/index.ts +188 -0
- package/src/commands/preview-docs/preview-server/default.hbs +24 -0
- package/src/commands/preview-docs/preview-server/hot.js +42 -0
- package/src/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
- package/src/commands/preview-docs/preview-server/preview-server.ts +150 -0
- package/src/commands/preview-docs/preview-server/server.ts +91 -0
- package/src/commands/push.ts +355 -0
- package/src/commands/split/__tests__/fixtures/spec.json +70 -0
- package/src/commands/split/__tests__/fixtures/webhooks.json +88 -0
- package/src/commands/split/__tests__/index.test.ts +96 -0
- package/src/commands/split/index.ts +349 -0
- package/src/commands/split/types.ts +73 -0
- package/src/commands/stats.ts +115 -0
- package/src/index.ts +311 -0
- package/src/js-utils.ts +12 -0
- package/src/types.ts +13 -0
- package/src/utils.ts +300 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { red, blue, yellow, green } from 'colorette';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { parseYaml, slash, isRef } from '@redocly/openapi-core';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { performance } from 'perf_hooks';
|
|
6
|
+
const isEqual = require('lodash.isequal');
|
|
7
|
+
|
|
8
|
+
import { printExecutionTime, pathToFilename, readYaml, writeYaml, exitWithError } from '../../utils';
|
|
9
|
+
import { isString, isObject, isEmptyObject } from '../../js-utils';
|
|
10
|
+
import {
|
|
11
|
+
Definition,
|
|
12
|
+
Oas2Definition,
|
|
13
|
+
Oas3Schema,
|
|
14
|
+
Oas3Definition,
|
|
15
|
+
Oas3_1Definition,
|
|
16
|
+
Oas3Components,
|
|
17
|
+
Oas3ComponentName,
|
|
18
|
+
ComponentsFiles,
|
|
19
|
+
refObj,
|
|
20
|
+
Oas3PathItem,
|
|
21
|
+
OPENAPI3_COMPONENT,
|
|
22
|
+
COMPONENTS,
|
|
23
|
+
componentsPath,
|
|
24
|
+
OPENAPI3_METHOD_NAMES,
|
|
25
|
+
OPENAPI3_COMPONENT_NAMES,
|
|
26
|
+
Referenced
|
|
27
|
+
} from './types';
|
|
28
|
+
|
|
29
|
+
export async function handleSplit (argv: {
|
|
30
|
+
entrypoint: string;
|
|
31
|
+
outDir: string
|
|
32
|
+
separator: string
|
|
33
|
+
}) {
|
|
34
|
+
const startedAt = performance.now();
|
|
35
|
+
const { entrypoint, outDir, separator } = argv;
|
|
36
|
+
validateDefinitionFileName(entrypoint!);
|
|
37
|
+
const openapi = readYaml(entrypoint!) as Oas3Definition | Oas3_1Definition;
|
|
38
|
+
splitDefinition(openapi, outDir, separator);
|
|
39
|
+
process.stderr.write(
|
|
40
|
+
`🪓 Document: ${blue(entrypoint!)} ${green('is successfully split')}
|
|
41
|
+
and all related files are saved to the directory: ${blue(outDir)} \n`,
|
|
42
|
+
);
|
|
43
|
+
printExecutionTime('split', startedAt, entrypoint!);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function splitDefinition(openapi: Oas3Definition | Oas3_1Definition, openapiDir: string, pathSeparator: string) {
|
|
47
|
+
fs.mkdirSync(openapiDir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
const componentsFiles: ComponentsFiles = {};
|
|
50
|
+
iterateComponents(openapi, openapiDir, componentsFiles);
|
|
51
|
+
iteratePathItems(openapi.paths, openapiDir, path.join(openapiDir, 'paths'), componentsFiles, pathSeparator);
|
|
52
|
+
const webhooks = (openapi as Oas3_1Definition).webhooks || (openapi as Oas3Definition)['x-webhooks'];
|
|
53
|
+
// use webhook_ prefix for code samples to prevent potential name-clashes with paths samples
|
|
54
|
+
iteratePathItems(webhooks, openapiDir, path.join(openapiDir, 'webhooks'), componentsFiles, pathSeparator, 'webhook_');
|
|
55
|
+
|
|
56
|
+
replace$Refs(openapi, openapiDir, componentsFiles);
|
|
57
|
+
writeYaml(openapi, path.join(openapiDir, 'openapi.yaml'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isStartsWithComponents(node: string) {
|
|
61
|
+
return node.startsWith(componentsPath)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isNotYaml(filename: string) {
|
|
65
|
+
return !(filename.endsWith('.yaml') || filename.endsWith('.yml'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function loadFile(fileName: string) {
|
|
69
|
+
try {
|
|
70
|
+
return parseYaml(fs.readFileSync(fileName, 'utf8')) as Definition;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return exitWithError(e.message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateDefinitionFileName(fileName: string) {
|
|
77
|
+
if (!fs.existsSync(fileName)) exitWithError(`File ${blue(fileName)} does not exist \n`);
|
|
78
|
+
const file = loadFile(fileName);
|
|
79
|
+
if ((file as Oas2Definition).swagger) exitWithError('OpenAPI 2 is not supported by this command');
|
|
80
|
+
if (!(file as Oas3Definition | Oas3_1Definition).openapi) exitWithError('File does not conform to the OpenAPI Specification. OpenAPI version is not specified');
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function langToExt(lang: string) {
|
|
85
|
+
const langObj: any = {
|
|
86
|
+
php: '.php',
|
|
87
|
+
'c#': '.cs',
|
|
88
|
+
shell: '.sh',
|
|
89
|
+
curl: '.sh',
|
|
90
|
+
bash: '.sh',
|
|
91
|
+
javascript: '.js',
|
|
92
|
+
js: '.js',
|
|
93
|
+
python: '.py'
|
|
94
|
+
}
|
|
95
|
+
return langObj[lang];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function traverseDirectoryDeep(directory: string, callback: any, componentsFiles: object) {
|
|
99
|
+
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) return;
|
|
100
|
+
const files = fs.readdirSync(directory);
|
|
101
|
+
for (const f of files) {
|
|
102
|
+
const filename = path.join(directory, f);
|
|
103
|
+
if (fs.statSync(filename).isDirectory()) {
|
|
104
|
+
traverseDirectoryDeep(filename, callback, componentsFiles);
|
|
105
|
+
} else {
|
|
106
|
+
callback(filename, directory, componentsFiles);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function traverseDirectoryDeepCallback(
|
|
112
|
+
filename: string,
|
|
113
|
+
directory: string,
|
|
114
|
+
componentsFiles: object,
|
|
115
|
+
) {
|
|
116
|
+
if (isNotYaml(filename)) return;
|
|
117
|
+
const pathData = readYaml(filename);
|
|
118
|
+
replace$Refs(pathData, directory, componentsFiles);
|
|
119
|
+
writeYaml(pathData, filename);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function crawl(object: any, visitor: any) {
|
|
123
|
+
if (!isObject(object)) return;
|
|
124
|
+
for (const key of Object.keys(object)) {
|
|
125
|
+
visitor(object, key);
|
|
126
|
+
crawl(object[key], visitor);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function replace$Refs(
|
|
131
|
+
obj: any,
|
|
132
|
+
relativeFrom: string,
|
|
133
|
+
componentFiles = {} as ComponentsFiles
|
|
134
|
+
) {
|
|
135
|
+
crawl(obj, (node: any) => {
|
|
136
|
+
if (node.$ref && isString(node.$ref) && isStartsWithComponents(node.$ref)) {
|
|
137
|
+
replace(node, '$ref');
|
|
138
|
+
} else if (
|
|
139
|
+
node.discriminator &&
|
|
140
|
+
node.discriminator.mapping &&
|
|
141
|
+
isObject(node.discriminator.mapping)
|
|
142
|
+
) {
|
|
143
|
+
const { mapping } = node.discriminator;
|
|
144
|
+
for (const name of Object.keys(mapping)) {
|
|
145
|
+
if (isString(mapping[name]) && isStartsWithComponents(mapping[name])) {
|
|
146
|
+
replace(node.discriminator.mapping, name);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
function replace(node: refObj, key: string) {
|
|
153
|
+
const splittedNode = node[key].split('/');
|
|
154
|
+
const name = splittedNode.pop();
|
|
155
|
+
const groupName = splittedNode[2];
|
|
156
|
+
const filesGroupName = componentFiles[groupName];
|
|
157
|
+
if (!filesGroupName || !filesGroupName[name!]) return;
|
|
158
|
+
let filename = path.relative(relativeFrom, filesGroupName[name!].filename);
|
|
159
|
+
if (!filename.startsWith('.')) { filename = '.' + path.sep + filename; }
|
|
160
|
+
node[key] = filename;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function implicitlyReferenceDiscriminator(
|
|
165
|
+
obj: any,
|
|
166
|
+
defName: string,
|
|
167
|
+
filename: string,
|
|
168
|
+
schemaFiles: any
|
|
169
|
+
) {
|
|
170
|
+
if (!obj.discriminator) return;
|
|
171
|
+
const defPtr = `#/${COMPONENTS}/${OPENAPI3_COMPONENT.Schemas}/${defName}`;
|
|
172
|
+
const implicitMapping = {} as any;
|
|
173
|
+
for (const [name, { inherits, filename: parentFilename }] of Object.entries(schemaFiles) as any) {
|
|
174
|
+
if (inherits.indexOf(defPtr) > -1) {
|
|
175
|
+
const res = path.relative(path.dirname(filename), parentFilename);
|
|
176
|
+
implicitMapping[name] = res.startsWith('.') ? res : '.' + path.sep + res;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (isEmptyObject(implicitMapping)) return;
|
|
181
|
+
const discriminatorPropSchema = obj.properties[obj.discriminator.propertyName];
|
|
182
|
+
const discriminatorEnum = discriminatorPropSchema && discriminatorPropSchema.enum;
|
|
183
|
+
const mapping = (obj.discriminator.mapping = obj.discriminator.mapping || {});
|
|
184
|
+
for (const name of Object.keys(implicitMapping)) {
|
|
185
|
+
if (discriminatorEnum && !discriminatorEnum.includes(name)) { continue; }
|
|
186
|
+
if (mapping[name] && mapping[name] !== implicitMapping[name]) {
|
|
187
|
+
process.stderr.write(yellow(
|
|
188
|
+
`warning: explicit mapping overlaps with local mapping entry ${red(name)} at ${blue(filename)}. Please check it.`
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
mapping[name] = implicitMapping[name];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isNotSecurityComponentType(componentType: string) {
|
|
196
|
+
return componentType !== OPENAPI3_COMPONENT.SecuritySchemes
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function findComponentTypes(components: any) {
|
|
200
|
+
return OPENAPI3_COMPONENT_NAMES
|
|
201
|
+
.filter(item => isNotSecurityComponentType(item) && Object.keys(components).includes(item))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function doesFileDiffer(filename: string, componentData: any) {
|
|
205
|
+
return fs.existsSync(filename) && !isEqual(readYaml(filename), componentData);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function removeEmptyComponents(openapi: Oas3Definition | Oas3_1Definition, componentType: Oas3ComponentName) {
|
|
209
|
+
if (openapi.components && isEmptyObject(openapi.components[componentType])) {
|
|
210
|
+
delete openapi.components[componentType];
|
|
211
|
+
}
|
|
212
|
+
if (isEmptyObject(openapi.components)) {
|
|
213
|
+
delete openapi.components;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function createComponentDir(componentDirPath: string, componentType: string) {
|
|
218
|
+
if (isNotSecurityComponentType(componentType)) {
|
|
219
|
+
fs.mkdirSync(componentDirPath, { recursive: true });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function extractFileNameFromPath(filename: string) {
|
|
224
|
+
return path.basename(filename, path.extname(filename));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getFileNamePath(componentDirPath: string, componentName: string) {
|
|
228
|
+
return path.join(componentDirPath, componentName) + '.yaml';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function gatherComponentsFiles(
|
|
232
|
+
components: Oas3Components,
|
|
233
|
+
componentsFiles: ComponentsFiles,
|
|
234
|
+
componentType: Oas3ComponentName,
|
|
235
|
+
componentName: string,
|
|
236
|
+
filename: string
|
|
237
|
+
) {
|
|
238
|
+
let inherits = [];
|
|
239
|
+
if (componentType === OPENAPI3_COMPONENT.Schemas) {
|
|
240
|
+
inherits = ((components?.[componentType]?.[componentName] as Oas3Schema)?.allOf || []).map((s: any) => s.$ref).filter(Boolean);
|
|
241
|
+
}
|
|
242
|
+
componentsFiles[componentType] = componentsFiles[componentType] || {};
|
|
243
|
+
componentsFiles[componentType][componentName] = { inherits, filename };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function iteratePathItems(
|
|
247
|
+
pathItems: Record<string, Referenced<Oas3PathItem>> | undefined,
|
|
248
|
+
openapiDir: string,
|
|
249
|
+
outDir: string,
|
|
250
|
+
componentsFiles: object,
|
|
251
|
+
pathSeparator: string,
|
|
252
|
+
codeSamplesPathPrefix: string = '',
|
|
253
|
+
) {
|
|
254
|
+
if (!pathItems) return;
|
|
255
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
256
|
+
|
|
257
|
+
for (const pathName of Object.keys(pathItems)) {
|
|
258
|
+
const pathFile = `${path.join(outDir, pathToFilename(pathName, pathSeparator))}.yaml`;
|
|
259
|
+
const pathData = pathItems[pathName] as Oas3PathItem;
|
|
260
|
+
|
|
261
|
+
if (isRef(pathData)) continue;
|
|
262
|
+
|
|
263
|
+
for (const method of OPENAPI3_METHOD_NAMES) {
|
|
264
|
+
const methodData = pathData[method];
|
|
265
|
+
const methodDataXCode = methodData?.['x-code-samples'] || methodData?.['x-codeSamples'];
|
|
266
|
+
if (!methodDataXCode || !Array.isArray(methodDataXCode)) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
for (const sample of methodDataXCode) {
|
|
270
|
+
if (sample.source && (sample.source as any).$ref) continue;
|
|
271
|
+
const sampleFileName = path.join(
|
|
272
|
+
openapiDir,
|
|
273
|
+
'code_samples',
|
|
274
|
+
sample.lang,
|
|
275
|
+
codeSamplesPathPrefix + pathToFilename(pathName, pathSeparator),
|
|
276
|
+
method + langToExt(sample.lang),
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
fs.mkdirSync(path.dirname(sampleFileName), { recursive: true });
|
|
280
|
+
fs.writeFileSync(sampleFileName, sample.source);
|
|
281
|
+
// @ts-ignore
|
|
282
|
+
sample.source = {
|
|
283
|
+
$ref: slash(path.relative(outDir, sampleFileName))
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
writeYaml(pathData, pathFile);
|
|
288
|
+
pathItems[pathName] = {
|
|
289
|
+
$ref: slash(path.relative(openapiDir, pathFile))
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
traverseDirectoryDeep(outDir, traverseDirectoryDeepCallback, componentsFiles);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function iterateComponents(
|
|
297
|
+
openapi: Oas3Definition | Oas3_1Definition,
|
|
298
|
+
openapiDir: string,
|
|
299
|
+
componentsFiles: ComponentsFiles
|
|
300
|
+
) {
|
|
301
|
+
const { components } = openapi;
|
|
302
|
+
if (components) {
|
|
303
|
+
const componentsDir = path.join(openapiDir, COMPONENTS);
|
|
304
|
+
fs.mkdirSync(componentsDir, { recursive: true });
|
|
305
|
+
const componentTypes = findComponentTypes(components);
|
|
306
|
+
componentTypes.forEach(iterateAndGatherComponentsFiles);
|
|
307
|
+
componentTypes.forEach(iterateComponentTypes);
|
|
308
|
+
|
|
309
|
+
function iterateAndGatherComponentsFiles(componentType: Oas3ComponentName) {
|
|
310
|
+
const componentDirPath = path.join(componentsDir, componentType);
|
|
311
|
+
for (const componentName of Object.keys(components?.[componentType] || {})) {
|
|
312
|
+
const filename = getFileNamePath(componentDirPath, componentName);
|
|
313
|
+
gatherComponentsFiles(components!, componentsFiles, componentType, componentName, filename);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function iterateComponentTypes(componentType: Oas3ComponentName) {
|
|
318
|
+
const componentDirPath = path.join(componentsDir, componentType);
|
|
319
|
+
createComponentDir(componentDirPath, componentType);
|
|
320
|
+
for (const componentName of Object.keys(components?.[componentType] || {})) {
|
|
321
|
+
const filename = getFileNamePath(componentDirPath, componentName);
|
|
322
|
+
const componentData = components?.[componentType]?.[componentName];
|
|
323
|
+
replace$Refs(componentData, path.dirname(filename), componentsFiles);
|
|
324
|
+
implicitlyReferenceDiscriminator(
|
|
325
|
+
componentData,
|
|
326
|
+
extractFileNameFromPath(filename),
|
|
327
|
+
filename,
|
|
328
|
+
componentsFiles.schemas || {}
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
if (doesFileDiffer(filename, componentData)) {
|
|
332
|
+
process.stderr.write(yellow(
|
|
333
|
+
`warning: conflict for ${componentName} - file already exists with different content: ${blue(filename)} ... Skip.\n`
|
|
334
|
+
));
|
|
335
|
+
} else {
|
|
336
|
+
writeYaml(componentData, filename);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (isNotSecurityComponentType(componentType)) {
|
|
340
|
+
// security schemas must referenced from components
|
|
341
|
+
delete openapi.components?.[componentType]?.[componentName];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
removeEmptyComponents(openapi, componentType);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export { iteratePathItems };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Oas3Schema,
|
|
3
|
+
Oas3_1Schema,
|
|
4
|
+
Oas3Definition,
|
|
5
|
+
Oas3_1Definition,
|
|
6
|
+
Oas3Components,
|
|
7
|
+
Oas3PathItem,
|
|
8
|
+
Oas3Paths,
|
|
9
|
+
Oas3ComponentName,
|
|
10
|
+
Oas3_1Webhooks,
|
|
11
|
+
Oas2Definition,
|
|
12
|
+
Referenced
|
|
13
|
+
} from "@redocly/openapi-core";
|
|
14
|
+
export { Oas3_1Definition, Oas3Definition, Oas2Definition, Oas3Components, Oas3Paths, Oas3PathItem, Oas3ComponentName, Oas3_1Schema, Oas3Schema, Oas3_1Webhooks, Referenced }
|
|
15
|
+
export type Definition = Oas3_1Definition | Oas3Definition | Oas2Definition;
|
|
16
|
+
export interface ComponentsFiles {
|
|
17
|
+
[schemas: string]: any;
|
|
18
|
+
}
|
|
19
|
+
export interface refObj {
|
|
20
|
+
[$ref: string]: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const COMPONENTS = 'components';
|
|
24
|
+
export const PATHS = 'paths';
|
|
25
|
+
export const WEBHOOKS = 'webhooks';
|
|
26
|
+
export const xWEBHOOKS = 'x-webhooks';
|
|
27
|
+
export const componentsPath = `#/${COMPONENTS}/`;
|
|
28
|
+
|
|
29
|
+
enum OPENAPI3_METHOD {
|
|
30
|
+
Get = 'get',
|
|
31
|
+
Put = 'put',
|
|
32
|
+
Post = 'post',
|
|
33
|
+
Delete = 'delete',
|
|
34
|
+
Options = 'options',
|
|
35
|
+
Head = 'head',
|
|
36
|
+
Patch = 'patch',
|
|
37
|
+
Trace = 'trace'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const OPENAPI3_METHOD_NAMES: OPENAPI3_METHOD[] = [
|
|
41
|
+
OPENAPI3_METHOD.Get,
|
|
42
|
+
OPENAPI3_METHOD.Put,
|
|
43
|
+
OPENAPI3_METHOD.Post,
|
|
44
|
+
OPENAPI3_METHOD.Delete,
|
|
45
|
+
OPENAPI3_METHOD.Options,
|
|
46
|
+
OPENAPI3_METHOD.Head,
|
|
47
|
+
OPENAPI3_METHOD.Patch,
|
|
48
|
+
OPENAPI3_METHOD.Trace
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
export enum OPENAPI3_COMPONENT {
|
|
52
|
+
Schemas = 'schemas',
|
|
53
|
+
Responses = 'responses',
|
|
54
|
+
Parameters = 'parameters',
|
|
55
|
+
Examples = 'examples',
|
|
56
|
+
Headers = 'headers',
|
|
57
|
+
RequestBodies = 'requestBodies',
|
|
58
|
+
Links = 'links',
|
|
59
|
+
Callbacks = 'callbacks',
|
|
60
|
+
SecuritySchemes = 'securitySchemes'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const OPENAPI3_COMPONENT_NAMES: OPENAPI3_COMPONENT[] = [
|
|
64
|
+
OPENAPI3_COMPONENT.RequestBodies,
|
|
65
|
+
OPENAPI3_COMPONENT.Schemas,
|
|
66
|
+
OPENAPI3_COMPONENT.Responses,
|
|
67
|
+
OPENAPI3_COMPONENT.Parameters,
|
|
68
|
+
OPENAPI3_COMPONENT.Examples,
|
|
69
|
+
OPENAPI3_COMPONENT.Headers,
|
|
70
|
+
OPENAPI3_COMPONENT.Links,
|
|
71
|
+
OPENAPI3_COMPONENT.Callbacks,
|
|
72
|
+
OPENAPI3_COMPONENT.SecuritySchemes
|
|
73
|
+
];
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { performance } from 'perf_hooks';
|
|
2
|
+
import * as colors from 'colorette';
|
|
3
|
+
import {
|
|
4
|
+
Config,
|
|
5
|
+
LintConfig,
|
|
6
|
+
loadConfig,
|
|
7
|
+
normalizeTypes,
|
|
8
|
+
Oas3Types,
|
|
9
|
+
Oas2Types,
|
|
10
|
+
StatsAccumulator,
|
|
11
|
+
StatsName,
|
|
12
|
+
BaseResolver,
|
|
13
|
+
resolveDocument,
|
|
14
|
+
detectOpenAPI,
|
|
15
|
+
OasMajorVersion,
|
|
16
|
+
openAPIMajor,
|
|
17
|
+
normalizeVisitors,
|
|
18
|
+
WalkContext,
|
|
19
|
+
walkDocument,
|
|
20
|
+
Stats,
|
|
21
|
+
bundle
|
|
22
|
+
} from '@redocly/openapi-core';
|
|
23
|
+
|
|
24
|
+
import { getFallbackEntryPointsOrExit } from '../utils'
|
|
25
|
+
import { printExecutionTime } from '../utils';
|
|
26
|
+
|
|
27
|
+
const statsAccumulator: StatsAccumulator = {
|
|
28
|
+
refs: { metric: '🚗 References', total: 0, color: 'red', items: new Set() },
|
|
29
|
+
externalDocs: { metric: '📦 External Documents', total: 0, color: 'magenta' },
|
|
30
|
+
schemas: { metric: '📈 Schemas', total: 0, color: 'white'},
|
|
31
|
+
parameters: { metric: '👉 Parameters', total: 0, color: 'yellow', items: new Set() },
|
|
32
|
+
links: { metric: '🔗 Links', total: 0, color: 'cyan', items: new Set() },
|
|
33
|
+
pathItems: { metric: '➡️ Path Items', total: 0, color: 'green' },
|
|
34
|
+
operations: { metric: '👷 Operations', total: 0, color: 'yellow' },
|
|
35
|
+
tags: { metric: '🔖 Tags', total: 0, color: 'white', items: new Set() },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function printStatsStylish(statsAccumulator: StatsAccumulator) {
|
|
39
|
+
for (const node in statsAccumulator) {
|
|
40
|
+
const { metric, total, color } = statsAccumulator[node as StatsName];
|
|
41
|
+
process.stderr.write(colors[color](`${metric}: ${total} \n`));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printStatsJson(statsAccumulator: StatsAccumulator) {
|
|
46
|
+
const json: any = {};
|
|
47
|
+
for (const key of Object.keys(statsAccumulator)) {
|
|
48
|
+
json[key] = {
|
|
49
|
+
metric: statsAccumulator[key as StatsName].metric,
|
|
50
|
+
total: statsAccumulator[key as StatsName].total,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
process.stdout.write(JSON.stringify(json, null, 2));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printStats(statsAccumulator: StatsAccumulator, entrypoint: string, format: string) {
|
|
57
|
+
process.stderr.write(`Document: ${colors.magenta(entrypoint)} stats:\n\n`);
|
|
58
|
+
switch (format) {
|
|
59
|
+
case 'stylish': printStatsStylish(statsAccumulator); break;
|
|
60
|
+
case 'json': printStatsJson(statsAccumulator); break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function handleStats (argv: {
|
|
65
|
+
config?: string;
|
|
66
|
+
entrypoint?: string;
|
|
67
|
+
format: string;
|
|
68
|
+
}) {
|
|
69
|
+
const config: Config = await loadConfig(argv.config);
|
|
70
|
+
const [{ path }] = await getFallbackEntryPointsOrExit(argv.entrypoint ? [argv.entrypoint] : [], config);
|
|
71
|
+
const externalRefResolver = new BaseResolver(config.resolve);
|
|
72
|
+
const { bundle: document } = await bundle({ config, ref: path });
|
|
73
|
+
const lintConfig: LintConfig = config.lint;
|
|
74
|
+
const oasVersion = detectOpenAPI(document.parsed);
|
|
75
|
+
const oasMajorVersion = openAPIMajor(oasVersion);
|
|
76
|
+
const types = normalizeTypes(
|
|
77
|
+
lintConfig.extendTypes(
|
|
78
|
+
oasMajorVersion === OasMajorVersion.Version3 ? Oas3Types : Oas2Types,
|
|
79
|
+
oasVersion,
|
|
80
|
+
),
|
|
81
|
+
lintConfig
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const startedAt = performance.now();
|
|
85
|
+
const ctx: WalkContext = {
|
|
86
|
+
problems: [],
|
|
87
|
+
oasVersion: oasVersion,
|
|
88
|
+
visitorsData: {},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const resolvedRefMap = await resolveDocument({
|
|
92
|
+
rootDocument: document,
|
|
93
|
+
rootType: types.DefinitionRoot,
|
|
94
|
+
externalRefResolver,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const statsVisitor = normalizeVisitors([{
|
|
98
|
+
severity: 'warn',
|
|
99
|
+
ruleId: 'stats',
|
|
100
|
+
visitor: Stats(statsAccumulator)
|
|
101
|
+
}],
|
|
102
|
+
types
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
walkDocument({
|
|
106
|
+
document,
|
|
107
|
+
rootType: types.DefinitionRoot,
|
|
108
|
+
normalizedVisitors: statsVisitor,
|
|
109
|
+
resolvedRefMap,
|
|
110
|
+
ctx,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
printStats(statsAccumulator, path, argv.format);
|
|
114
|
+
printExecutionTime('stats', startedAt, path);
|
|
115
|
+
}
|