@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.
Files changed (79) hide show
  1. package/README.md +39 -0
  2. package/bin/cli.js +3 -0
  3. package/lib/__mocks__/utils.d.ts +17 -0
  4. package/lib/__mocks__/utils.js +14 -0
  5. package/lib/__tests__/commands/bundle.test.d.ts +1 -0
  6. package/lib/__tests__/commands/bundle.test.js +92 -0
  7. package/lib/__tests__/commands/push-region.test.d.ts +1 -0
  8. package/lib/__tests__/commands/push-region.test.js +55 -0
  9. package/lib/__tests__/commands/push.test.d.ts +1 -0
  10. package/lib/__tests__/commands/push.test.js +153 -0
  11. package/lib/__tests__/utils.test.d.ts +1 -0
  12. package/lib/__tests__/utils.test.js +41 -0
  13. package/lib/assert-node-version.d.ts +1 -0
  14. package/lib/assert-node-version.js +10 -0
  15. package/lib/commands/bundle.d.ts +19 -0
  16. package/lib/commands/bundle.js +128 -0
  17. package/lib/commands/join.d.ts +7 -0
  18. package/lib/commands/join.js +421 -0
  19. package/lib/commands/lint.d.ts +11 -0
  20. package/lib/commands/lint.js +80 -0
  21. package/lib/commands/login.d.ts +6 -0
  22. package/lib/commands/login.js +28 -0
  23. package/lib/commands/preview-docs/index.d.ts +12 -0
  24. package/lib/commands/preview-docs/index.js +141 -0
  25. package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
  26. package/lib/commands/preview-docs/preview-server/hot.js +42 -0
  27. package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  28. package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
  29. package/lib/commands/preview-docs/preview-server/preview-server.js +120 -0
  30. package/lib/commands/preview-docs/preview-server/server.d.ts +23 -0
  31. package/lib/commands/preview-docs/preview-server/server.js +85 -0
  32. package/lib/commands/push.d.ts +25 -0
  33. package/lib/commands/push.js +247 -0
  34. package/lib/commands/split/__tests__/index.test.d.ts +1 -0
  35. package/lib/commands/split/__tests__/index.test.js +70 -0
  36. package/lib/commands/split/index.d.ts +8 -0
  37. package/lib/commands/split/index.js +279 -0
  38. package/lib/commands/split/types.d.ts +37 -0
  39. package/lib/commands/split/types.js +52 -0
  40. package/lib/commands/stats.d.ts +5 -0
  41. package/lib/commands/stats.js +92 -0
  42. package/lib/index.d.ts +2 -0
  43. package/lib/index.js +269 -0
  44. package/lib/js-utils.d.ts +3 -0
  45. package/lib/js-utils.js +16 -0
  46. package/lib/types.d.ts +13 -0
  47. package/lib/types.js +5 -0
  48. package/lib/utils.d.ts +28 -0
  49. package/lib/utils.js +260 -0
  50. package/package.json +54 -0
  51. package/src/__mocks__/utils.ts +11 -0
  52. package/src/__tests__/commands/bundle.test.ts +120 -0
  53. package/src/__tests__/commands/push-region.test.ts +51 -0
  54. package/src/__tests__/commands/push.test.ts +156 -0
  55. package/src/__tests__/utils.test.ts +50 -0
  56. package/src/assert-node-version.ts +8 -0
  57. package/src/commands/bundle.ts +178 -0
  58. package/src/commands/join.ts +488 -0
  59. package/src/commands/lint.ts +110 -0
  60. package/src/commands/login.ts +19 -0
  61. package/src/commands/preview-docs/index.ts +188 -0
  62. package/src/commands/preview-docs/preview-server/default.hbs +24 -0
  63. package/src/commands/preview-docs/preview-server/hot.js +42 -0
  64. package/src/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  65. package/src/commands/preview-docs/preview-server/preview-server.ts +150 -0
  66. package/src/commands/preview-docs/preview-server/server.ts +91 -0
  67. package/src/commands/push.ts +355 -0
  68. package/src/commands/split/__tests__/fixtures/spec.json +70 -0
  69. package/src/commands/split/__tests__/fixtures/webhooks.json +88 -0
  70. package/src/commands/split/__tests__/index.test.ts +96 -0
  71. package/src/commands/split/index.ts +349 -0
  72. package/src/commands/split/types.ts +73 -0
  73. package/src/commands/stats.ts +115 -0
  74. package/src/index.ts +311 -0
  75. package/src/js-utils.ts +12 -0
  76. package/src/types.ts +13 -0
  77. package/src/utils.ts +300 -0
  78. package/tsconfig.json +9 -0
  79. 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
+ }