@redocly/cli 1.0.0-beta.126 → 1.0.0-beta.127

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 (51) hide show
  1. package/lib/__tests__/utils.test.js +7 -0
  2. package/lib/index.js +4 -0
  3. package/lib/update-version-notifier.d.ts +2 -0
  4. package/lib/update-version-notifier.js +100 -0
  5. package/lib/utils.d.ts +1 -0
  6. package/lib/utils.js +7 -2
  7. package/package.json +5 -3
  8. package/src/__mocks__/@redocly/openapi-core.ts +0 -80
  9. package/src/__mocks__/documents.ts +0 -63
  10. package/src/__mocks__/fs.ts +0 -6
  11. package/src/__mocks__/perf_hooks.ts +0 -3
  12. package/src/__mocks__/redoc.ts +0 -2
  13. package/src/__mocks__/utils.ts +0 -19
  14. package/src/__tests__/commands/build-docs.test.ts +0 -61
  15. package/src/__tests__/commands/bundle.test.ts +0 -169
  16. package/src/__tests__/commands/join.test.ts +0 -114
  17. package/src/__tests__/commands/lint.test.ts +0 -166
  18. package/src/__tests__/commands/push-region.test.ts +0 -51
  19. package/src/__tests__/commands/push.test.ts +0 -364
  20. package/src/__tests__/fixtures/config.ts +0 -21
  21. package/src/__tests__/utils.test.ts +0 -441
  22. package/src/assert-node-version.ts +0 -8
  23. package/src/commands/build-docs/index.ts +0 -56
  24. package/src/commands/build-docs/template.hbs +0 -23
  25. package/src/commands/build-docs/types.ts +0 -26
  26. package/src/commands/build-docs/utils.ts +0 -112
  27. package/src/commands/bundle.ts +0 -170
  28. package/src/commands/join.ts +0 -810
  29. package/src/commands/lint.ts +0 -161
  30. package/src/commands/login.ts +0 -21
  31. package/src/commands/preview-docs/index.ts +0 -183
  32. package/src/commands/preview-docs/preview-server/default.hbs +0 -24
  33. package/src/commands/preview-docs/preview-server/hot.js +0 -42
  34. package/src/commands/preview-docs/preview-server/oauth2-redirect.html +0 -21
  35. package/src/commands/preview-docs/preview-server/preview-server.ts +0 -156
  36. package/src/commands/preview-docs/preview-server/server.ts +0 -91
  37. package/src/commands/push.ts +0 -387
  38. package/src/commands/split/__tests__/fixtures/samples.json +0 -61
  39. package/src/commands/split/__tests__/fixtures/spec.json +0 -70
  40. package/src/commands/split/__tests__/fixtures/webhooks.json +0 -88
  41. package/src/commands/split/__tests__/index.test.ts +0 -137
  42. package/src/commands/split/index.ts +0 -378
  43. package/src/commands/split/types.ts +0 -85
  44. package/src/commands/stats.ts +0 -117
  45. package/src/custom.d.ts +0 -1
  46. package/src/index.ts +0 -431
  47. package/src/js-utils.ts +0 -17
  48. package/src/types.ts +0 -28
  49. package/src/utils.ts +0 -472
  50. package/tsconfig.json +0 -9
  51. package/tsconfig.tsbuildinfo +0 -1
@@ -1,810 +0,0 @@
1
- import * as path from 'path';
2
- import { red, blue, yellow, green } from 'colorette';
3
- import { performance } from 'perf_hooks';
4
- const isEqual = require('lodash.isequal');
5
- import {
6
- Config,
7
- Oas3Definition,
8
- OasVersion,
9
- BaseResolver,
10
- Document,
11
- StyleguideConfig,
12
- Oas3Tag,
13
- formatProblems,
14
- getTotals,
15
- lintDocument,
16
- detectOpenAPI,
17
- bundleDocument,
18
- Referenced,
19
- isRef,
20
- } from '@redocly/openapi-core';
21
-
22
- import {
23
- getFallbackApisOrExit,
24
- printExecutionTime,
25
- handleError,
26
- printLintTotals,
27
- writeYaml,
28
- exitWithError,
29
- loadConfigAndHandleErrors,
30
- sortTopLevelKeysForOas,
31
- } from '../utils';
32
- import { isObject, isString, keysOf } from '../js-utils';
33
- import { Oas3Parameter, Oas3PathItem, Oas3Server } from '@redocly/openapi-core/lib/typings/openapi';
34
- import { OPENAPI3_METHOD } from './split/types';
35
- import { BundleResult } from '@redocly/openapi-core/lib/bundle';
36
-
37
- const COMPONENTS = 'components';
38
- const Tags = 'tags';
39
- const xTagGroups = 'x-tagGroups';
40
- let potentialConflictsTotal = 0;
41
-
42
- type JoinDocumentContext = {
43
- api: string;
44
- apiFilename: string;
45
- tags: Oas3Tag[];
46
- potentialConflicts: any;
47
- tagsPrefix: string;
48
- componentsPrefix: string | undefined;
49
- };
50
-
51
- type JoinArgv = {
52
- apis: string[];
53
- lint?: boolean;
54
- decorate?: boolean;
55
- preprocess?: boolean;
56
- 'prefix-tags-with-info-prop'?: string;
57
- 'prefix-tags-with-filename'?: boolean;
58
- 'prefix-components-with-info-prop'?: string;
59
- 'without-x-tag-groups'?: boolean;
60
- output?: string;
61
- };
62
-
63
- export async function handleJoin(argv: JoinArgv, packageVersion: string) {
64
- const startedAt = performance.now();
65
- if (argv.apis.length < 2) {
66
- return exitWithError(`At least 2 apis should be provided. \n\n`);
67
- }
68
-
69
- const {
70
- 'prefix-components-with-info-prop': prefixComponentsWithInfoProp,
71
- 'prefix-tags-with-filename': prefixTagsWithFilename,
72
- 'prefix-tags-with-info-prop': prefixTagsWithInfoProp,
73
- 'without-x-tag-groups': withoutXTagGroups,
74
- output: specFilename = 'openapi.yaml',
75
- } = argv;
76
-
77
- const usedTagsOptions = [
78
- prefixTagsWithFilename && 'prefix-tags-with-filename',
79
- prefixTagsWithInfoProp && 'prefix-tags-with-info-prop',
80
- withoutXTagGroups && 'without-x-tag-groups',
81
- ].filter(Boolean);
82
-
83
- if (usedTagsOptions.length > 1) {
84
- return exitWithError(
85
- `You use ${yellow(usedTagsOptions.join(', '))} together.\nPlease choose only one! \n\n`
86
- );
87
- }
88
-
89
- const config: Config = await loadConfigAndHandleErrors();
90
- const apis = await getFallbackApisOrExit(argv.apis, config);
91
- const externalRefResolver = new BaseResolver(config.resolve);
92
- const documents = await Promise.all(
93
- apis.map(
94
- ({ path }) => externalRefResolver.resolveDocument(null, path, true) as Promise<Document>
95
- )
96
- );
97
-
98
- if (!argv.decorate) {
99
- const decorators = new Set([
100
- ...Object.keys(config.styleguide.decorators.oas3_0),
101
- ...Object.keys(config.styleguide.decorators.oas3_1),
102
- ...Object.keys(config.styleguide.decorators.oas2),
103
- ]);
104
- config.styleguide.skipDecorators(Array.from(decorators));
105
- }
106
-
107
- if (!argv.preprocess) {
108
- const preprocessors = new Set([
109
- ...Object.keys(config.styleguide.preprocessors.oas3_0),
110
- ...Object.keys(config.styleguide.preprocessors.oas3_1),
111
- ...Object.keys(config.styleguide.preprocessors.oas2),
112
- ]);
113
- config.styleguide.skipPreprocessors(Array.from(preprocessors));
114
- }
115
-
116
- const bundleResults = await Promise.all(
117
- documents.map((document) =>
118
- bundleDocument({
119
- document,
120
- config: config.styleguide,
121
- externalRefResolver,
122
- }).catch((e) => {
123
- exitWithError(`${e.message}: ${blue(document.source.absoluteRef)}`);
124
- })
125
- )
126
- );
127
-
128
- for (const { problems, bundle: document } of bundleResults as BundleResult[]) {
129
- const fileTotals = getTotals(problems);
130
- if (fileTotals.errors) {
131
- formatProblems(problems, {
132
- totals: fileTotals,
133
- version: document.parsed.version,
134
- });
135
- exitWithError(
136
- `❌ Errors encountered while bundling ${blue(
137
- document.source.absoluteRef
138
- )}: join will not proceed.\n`
139
- );
140
- }
141
- }
142
-
143
- for (const document of documents) {
144
- try {
145
- const version = detectOpenAPI(document.parsed);
146
- if (version !== OasVersion.Version3_0) {
147
- return exitWithError(
148
- `Only OpenAPI 3 is supported: ${blue(document.source.absoluteRef)} \n\n`
149
- );
150
- }
151
- } catch (e) {
152
- return exitWithError(`${e.message}: ${blue(document.source.absoluteRef)}`);
153
- }
154
- }
155
-
156
- if (argv.lint) {
157
- for (const document of documents) {
158
- await validateApi(document, config.styleguide, externalRefResolver, packageVersion);
159
- }
160
- }
161
-
162
- const joinedDef: any = {};
163
- const potentialConflicts = {
164
- tags: {},
165
- paths: {},
166
- components: {},
167
- xWebhooks: {},
168
- };
169
-
170
- addInfoSectionAndSpecVersion(documents, prefixComponentsWithInfoProp);
171
-
172
- for (const document of documents) {
173
- const openapi = document.parsed;
174
- const { tags, info } = openapi;
175
- const api = path.relative(process.cwd(), document.source.absoluteRef);
176
- const apiFilename = getApiFilename(api);
177
- const tagsPrefix = prefixTagsWithFilename
178
- ? apiFilename
179
- : getInfoPrefix(info, prefixTagsWithInfoProp, 'tags');
180
- const componentsPrefix = getInfoPrefix(info, prefixComponentsWithInfoProp, COMPONENTS);
181
-
182
- if (openapi.hasOwnProperty('x-tagGroups')) {
183
- process.stderr.write(yellow(`warning: x-tagGroups at ${blue(api)} will be skipped \n`));
184
- }
185
-
186
- const context = {
187
- api,
188
- apiFilename,
189
- tags,
190
- potentialConflicts,
191
- tagsPrefix,
192
- componentsPrefix,
193
- };
194
- if (tags) {
195
- populateTags(context);
196
- }
197
- collectServers(openapi);
198
- collectInfoDescriptions(openapi, context);
199
- collectExternalDocs(openapi, context);
200
- collectPaths(openapi, context);
201
- collectComponents(openapi, context);
202
- collectXWebhooks(openapi, context);
203
- if (componentsPrefix) {
204
- replace$Refs(openapi, componentsPrefix);
205
- }
206
- }
207
-
208
- iteratePotentialConflicts(potentialConflicts, withoutXTagGroups);
209
- const noRefs = true;
210
-
211
- if (potentialConflictsTotal) {
212
- return exitWithError(`Please fix conflicts before running ${yellow('join')}.`);
213
- }
214
-
215
- writeYaml(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
216
- printExecutionTime('join', startedAt, specFilename);
217
-
218
- function populateTags({
219
- api,
220
- apiFilename,
221
- tags,
222
- potentialConflicts,
223
- tagsPrefix,
224
- componentsPrefix,
225
- }: JoinDocumentContext) {
226
- if (!joinedDef.hasOwnProperty(Tags)) {
227
- joinedDef[Tags] = [];
228
- }
229
- if (!potentialConflicts.tags.hasOwnProperty('all')) {
230
- potentialConflicts.tags['all'] = {};
231
- }
232
- if (withoutXTagGroups && !potentialConflicts.tags.hasOwnProperty('description')) {
233
- potentialConflicts.tags['description'] = {};
234
- }
235
- for (const tag of tags) {
236
- const entrypointTagName = addPrefix(tag.name, tagsPrefix);
237
- if (tag.description) {
238
- tag.description = addComponentsPrefix(tag.description, componentsPrefix!);
239
- }
240
-
241
- const tagDuplicate = joinedDef.tags.find((t: Oas3Tag) => t.name === entrypointTagName);
242
-
243
- if (tagDuplicate && withoutXTagGroups) {
244
- // If tag already exist and `without-x-tag-groups` option,
245
- // check if description are different for potential conflicts warning.
246
- const isTagDescriptionNotEqual =
247
- tag.hasOwnProperty('description') && tagDuplicate.description !== tag.description;
248
-
249
- potentialConflicts.tags.description[entrypointTagName].push(
250
- ...(isTagDescriptionNotEqual ? [api] : [])
251
- );
252
- } else if (!tagDuplicate) {
253
- // Instead add tag to joinedDef if there no duplicate;
254
- tag['x-displayName'] = tag['x-displayName'] || tag.name;
255
- tag.name = entrypointTagName;
256
- joinedDef.tags.push(tag);
257
-
258
- if (withoutXTagGroups) {
259
- potentialConflicts.tags.description[entrypointTagName] = [api];
260
- }
261
- }
262
-
263
- if (!withoutXTagGroups) {
264
- createXTagGroups(apiFilename);
265
- if (!tagDuplicate) {
266
- populateXTagGroups(entrypointTagName, getIndexGroup(apiFilename));
267
- }
268
- }
269
-
270
- const doesEntrypointExist =
271
- !potentialConflicts.tags.all[entrypointTagName] ||
272
- (potentialConflicts.tags.all[entrypointTagName] &&
273
- !potentialConflicts.tags.all[entrypointTagName].includes(api));
274
- potentialConflicts.tags.all[entrypointTagName] = [
275
- ...(potentialConflicts.tags.all[entrypointTagName] || []),
276
- ...(!withoutXTagGroups && doesEntrypointExist ? [api] : []),
277
- ];
278
- }
279
- }
280
-
281
- function getIndexGroup(apiFilename: string): number {
282
- return joinedDef[xTagGroups].findIndex((item: any) => item.name === apiFilename);
283
- }
284
-
285
- function createXTagGroups(apiFilename: string) {
286
- if (!joinedDef.hasOwnProperty(xTagGroups)) {
287
- joinedDef[xTagGroups] = [];
288
- }
289
-
290
- if (!joinedDef[xTagGroups].some((g: any) => g.name === apiFilename)) {
291
- joinedDef[xTagGroups].push({ name: apiFilename, tags: [] });
292
- }
293
-
294
- const indexGroup = getIndexGroup(apiFilename);
295
-
296
- if (!joinedDef[xTagGroups][indexGroup].hasOwnProperty(Tags)) {
297
- joinedDef[xTagGroups][indexGroup][Tags] = [];
298
- }
299
- }
300
-
301
- function populateXTagGroups(entrypointTagName: string, indexGroup: number) {
302
- if (
303
- !joinedDef[xTagGroups][indexGroup][Tags].find((t: Oas3Tag) => t.name === entrypointTagName)
304
- ) {
305
- joinedDef[xTagGroups][indexGroup][Tags].push(entrypointTagName);
306
- }
307
- }
308
-
309
- function collectServers(openapi: Oas3Definition) {
310
- const { servers } = openapi;
311
- if (servers) {
312
- if (!joinedDef.hasOwnProperty('servers')) {
313
- joinedDef['servers'] = [];
314
- }
315
- for (const server of servers) {
316
- if (!joinedDef.servers.some((s: any) => s.url === server.url)) {
317
- joinedDef.servers.push(server);
318
- }
319
- }
320
- }
321
- }
322
-
323
- function collectInfoDescriptions(
324
- openapi: Oas3Definition,
325
- { apiFilename, componentsPrefix }: JoinDocumentContext
326
- ) {
327
- const { info } = openapi;
328
- if (info?.description) {
329
- const groupIndex = joinedDef[xTagGroups] ? getIndexGroup(apiFilename) : -1;
330
- if (
331
- joinedDef.hasOwnProperty(xTagGroups) &&
332
- groupIndex !== -1 &&
333
- joinedDef[xTagGroups][groupIndex]['tags'] &&
334
- joinedDef[xTagGroups][groupIndex]['tags'].length
335
- ) {
336
- joinedDef[xTagGroups][groupIndex]['description'] = addComponentsPrefix(
337
- info.description,
338
- componentsPrefix!
339
- );
340
- }
341
- }
342
- }
343
-
344
- function collectExternalDocs(openapi: Oas3Definition, { api }: JoinDocumentContext) {
345
- const { externalDocs } = openapi;
346
- if (externalDocs) {
347
- if (joinedDef.hasOwnProperty('externalDocs')) {
348
- process.stderr.write(
349
- yellow(`warning: skip externalDocs from ${blue(path.basename(api))} \n`)
350
- );
351
- return;
352
- }
353
- joinedDef['externalDocs'] = externalDocs;
354
- }
355
- }
356
-
357
- function collectPaths(
358
- openapi: Oas3Definition,
359
- { apiFilename, api, potentialConflicts, tagsPrefix, componentsPrefix }: JoinDocumentContext
360
- ) {
361
- const { paths } = openapi;
362
- const operationsSet = new Set(keysOf<typeof OPENAPI3_METHOD>(OPENAPI3_METHOD));
363
- if (paths) {
364
- if (!joinedDef.hasOwnProperty('paths')) {
365
- joinedDef['paths'] = {};
366
- }
367
-
368
- for (const path of keysOf(paths)) {
369
- if (!joinedDef.paths.hasOwnProperty(path)) {
370
- joinedDef.paths[path] = {};
371
- }
372
- if (!potentialConflicts.paths.hasOwnProperty(path)) {
373
- potentialConflicts.paths[path] = {};
374
- }
375
-
376
- const pathItem = paths[path] as Oas3PathItem;
377
-
378
- for (const field of keysOf(pathItem)) {
379
- if (operationsSet.has(field as OPENAPI3_METHOD)) {
380
- collectPathOperation(pathItem, path, field as OPENAPI3_METHOD);
381
- }
382
- if (field === 'servers') {
383
- collectPathServers(pathItem, path);
384
- }
385
- if (field === 'parameters') {
386
- collectPathParameters(pathItem, path);
387
- }
388
- if (typeof pathItem[field] === 'string') {
389
- collectPathStringFields(pathItem, path, field);
390
- }
391
- }
392
- }
393
- }
394
-
395
- function collectPathStringFields(
396
- pathItem: Oas3PathItem,
397
- path: string | number,
398
- field: keyof Oas3PathItem
399
- ) {
400
- const fieldValue = pathItem[field];
401
- if (
402
- joinedDef.paths[path].hasOwnProperty(field) &&
403
- joinedDef.paths[path][field] !== fieldValue
404
- ) {
405
- process.stderr.write(yellow(`warning: different ${field} values in ${path}\n`));
406
- return;
407
- }
408
- joinedDef.paths[path][field] = fieldValue;
409
- }
410
-
411
- function collectPathServers(pathItem: Oas3PathItem, path: string | number) {
412
- if (!pathItem.servers) {
413
- return;
414
- }
415
-
416
- if (!joinedDef.paths[path].hasOwnProperty('servers')) {
417
- joinedDef.paths[path].servers = [];
418
- }
419
-
420
- for (const server of pathItem.servers) {
421
- let isFoundServer = false;
422
- for (const pathServer of joinedDef.paths[path].servers) {
423
- if (pathServer.url === server.url) {
424
- if (!isServersEqual(pathServer, server)) {
425
- exitWithError(`Different server values for (${server.url}) in ${path}`);
426
- }
427
- isFoundServer = true;
428
- }
429
- }
430
-
431
- if (!isFoundServer) {
432
- joinedDef.paths[path].servers.push(server);
433
- }
434
- }
435
- }
436
-
437
- function collectPathParameters(pathItem: Oas3PathItem, path: string | number) {
438
- if (!pathItem.parameters) {
439
- return;
440
- }
441
- if (!joinedDef.paths[path].hasOwnProperty('parameters')) {
442
- joinedDef.paths[path].parameters = [];
443
- }
444
-
445
- for (const parameter of pathItem.parameters as Referenced<Oas3Parameter>[]) {
446
- let isFoundParameter = false;
447
-
448
- for (const pathParameter of joinedDef.paths[path]
449
- .parameters as Referenced<Oas3Parameter>[]) {
450
- // Compare $ref only if both are reference objects
451
- if (isRef(pathParameter) && isRef(parameter)) {
452
- if (pathParameter['$ref'] === parameter['$ref']) {
453
- isFoundParameter = true;
454
- }
455
- }
456
- // Compare properties only if both are reference objects
457
- if (!isRef(pathParameter) && !isRef(parameter)) {
458
- if (pathParameter.name === parameter.name && pathParameter.in === parameter.in) {
459
- if (!isEqual(pathParameter.schema, parameter.schema)) {
460
- exitWithError(`Different parameter schemas for (${parameter.name}) in ${path}`);
461
- }
462
- isFoundParameter = true;
463
- }
464
- }
465
- }
466
-
467
- if (!isFoundParameter) {
468
- joinedDef.paths[path].parameters.push(parameter);
469
- }
470
- }
471
- }
472
-
473
- function collectPathOperation(
474
- pathItem: Oas3PathItem,
475
- path: string | number,
476
- operation: OPENAPI3_METHOD
477
- ) {
478
- const pathOperation = pathItem[operation];
479
-
480
- if (!pathOperation) {
481
- return;
482
- }
483
-
484
- joinedDef.paths[path][operation] = pathOperation;
485
- potentialConflicts.paths[path][operation] = [
486
- ...(potentialConflicts.paths[path][operation] || []),
487
- api,
488
- ];
489
-
490
- const { operationId } = pathOperation;
491
-
492
- if (operationId) {
493
- if (!potentialConflicts.paths.hasOwnProperty('operationIds')) {
494
- potentialConflicts.paths['operationIds'] = {};
495
- }
496
- potentialConflicts.paths.operationIds[operationId] = [
497
- ...(potentialConflicts.paths.operationIds[operationId] || []),
498
- api,
499
- ];
500
- }
501
-
502
- const { tags, security } = joinedDef.paths[path][operation];
503
-
504
- if (tags) {
505
- joinedDef.paths[path][operation].tags = tags.map((tag: string) =>
506
- addPrefix(tag, tagsPrefix)
507
- );
508
- populateTags({
509
- api,
510
- apiFilename,
511
- tags: formatTags(tags),
512
- potentialConflicts,
513
- tagsPrefix,
514
- componentsPrefix,
515
- });
516
- } else {
517
- joinedDef.paths[path][operation]['tags'] = [addPrefix('other', tagsPrefix || apiFilename)];
518
- populateTags({
519
- api,
520
- apiFilename,
521
- tags: formatTags(['other']),
522
- potentialConflicts,
523
- tagsPrefix: tagsPrefix || apiFilename,
524
- componentsPrefix,
525
- });
526
- }
527
- if (!security && openapi.hasOwnProperty('security')) {
528
- joinedDef.paths[path][operation]['security'] = addSecurityPrefix(
529
- openapi.security,
530
- componentsPrefix!
531
- );
532
- } else if (pathOperation.security) {
533
- joinedDef.paths[path][operation].security = addSecurityPrefix(
534
- pathOperation.security,
535
- componentsPrefix!
536
- );
537
- }
538
- }
539
- }
540
-
541
- function isServersEqual(serverOne: Oas3Server, serverTwo: Oas3Server) {
542
- if (serverOne.description === serverTwo.description) {
543
- return isEqual(serverOne.variables, serverTwo.variables);
544
- }
545
-
546
- return false;
547
- }
548
-
549
- function collectComponents(
550
- openapi: Oas3Definition,
551
- { api, potentialConflicts, componentsPrefix }: JoinDocumentContext
552
- ) {
553
- const { components } = openapi;
554
- if (components) {
555
- if (!joinedDef.hasOwnProperty(COMPONENTS)) {
556
- joinedDef[COMPONENTS] = {};
557
- }
558
- for (const [component, componentObj] of Object.entries(components)) {
559
- if (!potentialConflicts[COMPONENTS].hasOwnProperty(component)) {
560
- potentialConflicts[COMPONENTS][component] = {};
561
- joinedDef[COMPONENTS][component] = {};
562
- }
563
- for (const item of Object.keys(componentObj)) {
564
- const componentPrefix = addPrefix(item, componentsPrefix!);
565
- potentialConflicts.components[component][componentPrefix] = [
566
- ...(potentialConflicts.components[component][item] || []),
567
- { [api]: componentObj[item] },
568
- ];
569
- joinedDef.components[component][componentPrefix] = componentObj[item];
570
- }
571
- }
572
- }
573
- }
574
-
575
- function collectXWebhooks(
576
- openapi: Oas3Definition,
577
- { apiFilename, api, potentialConflicts, tagsPrefix, componentsPrefix }: JoinDocumentContext
578
- ) {
579
- const xWebhooks = 'x-webhooks';
580
- const openapiXWebhooks = openapi[xWebhooks];
581
- if (openapiXWebhooks) {
582
- if (!joinedDef.hasOwnProperty(xWebhooks)) {
583
- joinedDef[xWebhooks] = {};
584
- }
585
- for (const webhook of Object.keys(openapiXWebhooks)) {
586
- joinedDef[xWebhooks][webhook] = openapiXWebhooks[webhook];
587
-
588
- if (!potentialConflicts.xWebhooks.hasOwnProperty(webhook)) {
589
- potentialConflicts.xWebhooks[webhook] = {};
590
- }
591
- for (const operation of Object.keys(openapiXWebhooks[webhook])) {
592
- potentialConflicts.xWebhooks[webhook][operation] = [
593
- ...(potentialConflicts.xWebhooks[webhook][operation] || []),
594
- api,
595
- ];
596
- }
597
- for (const operationKey of Object.keys(joinedDef[xWebhooks][webhook])) {
598
- const { tags } = joinedDef[xWebhooks][webhook][operationKey];
599
- if (tags) {
600
- joinedDef[xWebhooks][webhook][operationKey].tags = tags.map((tag: string) =>
601
- addPrefix(tag, tagsPrefix)
602
- );
603
- populateTags({
604
- api,
605
- apiFilename,
606
- tags: formatTags(tags),
607
- potentialConflicts,
608
- tagsPrefix,
609
- componentsPrefix,
610
- });
611
- }
612
- }
613
- }
614
- }
615
- }
616
-
617
- function addInfoSectionAndSpecVersion(
618
- documents: any,
619
- prefixComponentsWithInfoProp: string | undefined
620
- ) {
621
- const firstApi = documents[0];
622
- const openapi = firstApi.parsed;
623
- const componentsPrefix = getInfoPrefix(openapi.info, prefixComponentsWithInfoProp, COMPONENTS);
624
- if (!openapi.openapi) exitWithError('Version of specification is not found in. \n');
625
- if (!openapi.info) exitWithError('Info section is not found in specification. \n');
626
- if (openapi.info?.description) {
627
- openapi.info.description = addComponentsPrefix(openapi.info.description, componentsPrefix);
628
- }
629
- joinedDef.openapi = openapi.openapi;
630
- joinedDef.info = openapi.info;
631
- }
632
- }
633
-
634
- function doesComponentsDiffer(curr: object, next: object) {
635
- return !isEqual(Object.values(curr)[0], Object.values(next)[0]);
636
- }
637
-
638
- function validateComponentsDifference(files: any) {
639
- let isDiffer = false;
640
- for (let i = 0, len = files.length; i < len; i++) {
641
- const next = files[i + 1];
642
- if (next && doesComponentsDiffer(files[i], next)) {
643
- isDiffer = true;
644
- }
645
- }
646
- return isDiffer;
647
- }
648
-
649
- function iteratePotentialConflicts(potentialConflicts: any, withoutXTagGroups?: boolean) {
650
- for (const group of Object.keys(potentialConflicts)) {
651
- for (const [key, value] of Object.entries(potentialConflicts[group])) {
652
- const conflicts = filterConflicts(value as object);
653
- if (conflicts.length) {
654
- if (group === COMPONENTS) {
655
- for (const [_, conflict] of Object.entries(conflicts)) {
656
- if (validateComponentsDifference(conflict[1])) {
657
- conflict[1] = conflict[1].map((c: string) => Object.keys(c)[0]);
658
- showConflicts(green(group) + ' => ' + key, [conflict]);
659
- potentialConflictsTotal += 1;
660
- }
661
- }
662
- } else {
663
- if (withoutXTagGroups && group === 'tags') {
664
- duplicateTagDescriptionWarning(conflicts);
665
- } else {
666
- potentialConflictsTotal += conflicts.length;
667
- showConflicts(green(group) + ' => ' + key, conflicts);
668
- }
669
- }
670
-
671
- if (group === 'tags' && !withoutXTagGroups) {
672
- prefixTagSuggestion(conflicts.length);
673
- }
674
- }
675
- }
676
- }
677
- }
678
-
679
- function duplicateTagDescriptionWarning(conflicts: [string, any][]) {
680
- const tagsKeys = conflicts.map(([tagName]) => `\`${tagName}\``);
681
- const joinString = yellow(', ');
682
- process.stderr.write(
683
- yellow(
684
- `\nwarning: ${tagsKeys.length} conflict(s) on the ${red(
685
- tagsKeys.join(joinString)
686
- )} tags description.\n`
687
- )
688
- );
689
- }
690
-
691
- function prefixTagSuggestion(conflictsLength: number) {
692
- process.stderr.write(
693
- green(
694
- `\n${conflictsLength} conflict(s) on tags.\nSuggestion: please use ${blue(
695
- 'prefix-tags-with-filename'
696
- )}, ${blue('prefix-tags-with-info-prop')} or ${blue(
697
- 'without-x-tag-groups'
698
- )} to prevent naming conflicts.\n\n`
699
- )
700
- );
701
- }
702
-
703
- function showConflicts(key: string, conflicts: any) {
704
- for (const [path, files] of conflicts) {
705
- process.stderr.write(yellow(`Conflict on ${key} : ${red(path)} in files: ${blue(files)} \n`));
706
- }
707
- }
708
-
709
- function filterConflicts(entities: object) {
710
- return Object.entries(entities).filter(([_, files]) => files.length > 1);
711
- }
712
-
713
- function getApiFilename(filePath: string) {
714
- return path.basename(filePath, path.extname(filePath));
715
- }
716
-
717
- function addPrefix(tag: string, tagsPrefix: string) {
718
- return tagsPrefix ? tagsPrefix + '_' + tag : tag;
719
- }
720
-
721
- function formatTags(tags: string[]) {
722
- return tags.map((tag: string) => ({ name: tag }));
723
- }
724
-
725
- function addComponentsPrefix(description: string, componentsPrefix: string) {
726
- return description.replace(/"(#\/components\/.*?)"/g, (match) => {
727
- const componentName = path.basename(match);
728
- return match.replace(componentName, addPrefix(componentName, componentsPrefix));
729
- });
730
- }
731
-
732
- function addSecurityPrefix(security: any, componentsPrefix: string) {
733
- return componentsPrefix
734
- ? security?.map((s: any) => {
735
- const key = Object.keys(s)[0];
736
- return { [componentsPrefix + '_' + key]: s[key] };
737
- })
738
- : security;
739
- }
740
-
741
- function getInfoPrefix(info: any, prefixArg: string | undefined, type: string) {
742
- if (!prefixArg) return '';
743
- if (!info) exitWithError('Info section is not found in specification. \n');
744
- if (!info[prefixArg])
745
- exitWithError(
746
- `${yellow(`prefix-${type}-with-info-prop`)} argument value is not found in info section. \n`
747
- );
748
- if (!isString(info[prefixArg]))
749
- exitWithError(
750
- `${yellow(`prefix-${type}-with-info-prop`)} argument value should be string. \n\n`
751
- );
752
- if (info[prefixArg].length > 50)
753
- exitWithError(
754
- `${yellow(
755
- `prefix-${type}-with-info-prop`
756
- )} argument value length should not exceed 50 characters. \n\n`
757
- );
758
- return info[prefixArg];
759
- }
760
-
761
- async function validateApi(
762
- document: Document,
763
- config: StyleguideConfig,
764
- externalRefResolver: BaseResolver,
765
- packageVersion: string
766
- ) {
767
- try {
768
- const results = await lintDocument({ document, config, externalRefResolver });
769
- const fileTotals = getTotals(results);
770
- formatProblems(results, { format: 'stylish', totals: fileTotals, version: packageVersion });
771
- printLintTotals(fileTotals, 2);
772
- } catch (err) {
773
- handleError(err, document.parsed);
774
- }
775
- }
776
-
777
- function crawl(object: any, visitor: any) {
778
- if (!isObject(object)) return;
779
- for (const key of Object.keys(object)) {
780
- visitor(object, key);
781
- crawl(object[key], visitor);
782
- }
783
- }
784
-
785
- function replace$Refs(obj: any, componentsPrefix: string) {
786
- crawl(obj, (node: any) => {
787
- if (node.$ref && isString(node.$ref) && node.$ref.startsWith(`#/${COMPONENTS}/`)) {
788
- const name = path.basename(node.$ref);
789
- node.$ref = node.$ref.replace(name, componentsPrefix + '_' + name);
790
- } else if (
791
- node.discriminator &&
792
- node.discriminator.mapping &&
793
- isObject(node.discriminator.mapping)
794
- ) {
795
- const { mapping } = node.discriminator;
796
- for (const name of Object.keys(mapping)) {
797
- if (isString(mapping[name]) && mapping[name].startsWith(`#/${COMPONENTS}/`)) {
798
- mapping[name] = mapping[name]
799
- .split('/')
800
- .map((name: string, i: number, arr: []) => {
801
- return arr.length - 1 === i && !name.includes(componentsPrefix)
802
- ? componentsPrefix + '_' + name
803
- : name;
804
- })
805
- .join('/');
806
- }
807
- }
808
- }
809
- });
810
- }