@redocly/cli 0.0.0-snapshot.1737554067

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 (133) hide show
  1. package/README.md +114 -0
  2. package/bin/cli.js +3 -0
  3. package/lib/__mocks__/@redocly/openapi-core.d.ts +99 -0
  4. package/lib/__mocks__/@redocly/openapi-core.js +84 -0
  5. package/lib/__mocks__/documents.d.ts +150 -0
  6. package/lib/__mocks__/documents.js +123 -0
  7. package/lib/__mocks__/fs.d.ts +8 -0
  8. package/lib/__mocks__/fs.js +9 -0
  9. package/lib/__mocks__/perf_hooks.d.ts +3 -0
  10. package/lib/__mocks__/perf_hooks.js +6 -0
  11. package/lib/__mocks__/redoc.d.ts +6 -0
  12. package/lib/__mocks__/redoc.js +5 -0
  13. package/lib/__tests__/commands/build-docs.test.d.ts +1 -0
  14. package/lib/__tests__/commands/build-docs.test.js +54 -0
  15. package/lib/__tests__/commands/bundle.test.d.ts +1 -0
  16. package/lib/__tests__/commands/bundle.test.js +235 -0
  17. package/lib/__tests__/commands/join.test.d.ts +1 -0
  18. package/lib/__tests__/commands/join.test.js +274 -0
  19. package/lib/__tests__/commands/lint.test.d.ts +1 -0
  20. package/lib/__tests__/commands/lint.test.js +149 -0
  21. package/lib/__tests__/commands/push-region.test.d.ts +1 -0
  22. package/lib/__tests__/commands/push-region.test.js +55 -0
  23. package/lib/__tests__/commands/push.test.d.ts +1 -0
  24. package/lib/__tests__/commands/push.test.js +463 -0
  25. package/lib/__tests__/fetch-with-timeout.test.d.ts +1 -0
  26. package/lib/__tests__/fetch-with-timeout.test.js +44 -0
  27. package/lib/__tests__/fixtures/config.d.ts +21 -0
  28. package/lib/__tests__/fixtures/config.js +24 -0
  29. package/lib/__tests__/spinner.test.d.ts +1 -0
  30. package/lib/__tests__/spinner.test.js +43 -0
  31. package/lib/__tests__/utils.test.d.ts +1 -0
  32. package/lib/__tests__/utils.test.js +593 -0
  33. package/lib/__tests__/wrapper.test.d.ts +1 -0
  34. package/lib/__tests__/wrapper.test.js +68 -0
  35. package/lib/cms/api/__tests__/api-keys.test.d.ts +1 -0
  36. package/lib/cms/api/__tests__/api-keys.test.js +26 -0
  37. package/lib/cms/api/__tests__/api.client.test.d.ts +1 -0
  38. package/lib/cms/api/__tests__/api.client.test.js +333 -0
  39. package/lib/cms/api/__tests__/domains.test.d.ts +1 -0
  40. package/lib/cms/api/__tests__/domains.test.js +13 -0
  41. package/lib/cms/api/api-client.d.ts +75 -0
  42. package/lib/cms/api/api-client.js +225 -0
  43. package/lib/cms/api/api-keys.d.ts +1 -0
  44. package/lib/cms/api/api-keys.js +23 -0
  45. package/lib/cms/api/domains.d.ts +1 -0
  46. package/lib/cms/api/domains.js +11 -0
  47. package/lib/cms/api/index.d.ts +3 -0
  48. package/lib/cms/api/index.js +19 -0
  49. package/lib/cms/api/types.d.ts +102 -0
  50. package/lib/cms/api/types.js +2 -0
  51. package/lib/cms/commands/__tests__/push-status.test.d.ts +1 -0
  52. package/lib/cms/commands/__tests__/push-status.test.js +563 -0
  53. package/lib/cms/commands/__tests__/push.test.d.ts +1 -0
  54. package/lib/cms/commands/__tests__/push.test.js +315 -0
  55. package/lib/cms/commands/__tests__/utils.test.d.ts +1 -0
  56. package/lib/cms/commands/__tests__/utils.test.js +51 -0
  57. package/lib/cms/commands/push-status.d.ts +23 -0
  58. package/lib/cms/commands/push-status.js +206 -0
  59. package/lib/cms/commands/push.d.ts +28 -0
  60. package/lib/cms/commands/push.js +142 -0
  61. package/lib/cms/commands/utils.d.ts +25 -0
  62. package/lib/cms/commands/utils.js +46 -0
  63. package/lib/cms/utils.d.ts +2 -0
  64. package/lib/cms/utils.js +6 -0
  65. package/lib/commands/build-docs/index.d.ts +3 -0
  66. package/lib/commands/build-docs/index.js +39 -0
  67. package/lib/commands/build-docs/template.hbs +23 -0
  68. package/lib/commands/build-docs/types.d.ts +23 -0
  69. package/lib/commands/build-docs/types.js +2 -0
  70. package/lib/commands/build-docs/utils.d.ts +7 -0
  71. package/lib/commands/build-docs/utils.js +87 -0
  72. package/lib/commands/bundle.d.ts +14 -0
  73. package/lib/commands/bundle.js +91 -0
  74. package/lib/commands/eject.d.ts +9 -0
  75. package/lib/commands/eject.js +28 -0
  76. package/lib/commands/join.d.ts +11 -0
  77. package/lib/commands/join.js +565 -0
  78. package/lib/commands/lint.d.ts +13 -0
  79. package/lib/commands/lint.js +108 -0
  80. package/lib/commands/login.d.ts +9 -0
  81. package/lib/commands/login.js +23 -0
  82. package/lib/commands/preview-docs/index.d.ts +12 -0
  83. package/lib/commands/preview-docs/index.js +127 -0
  84. package/lib/commands/preview-docs/preview-server/default.hbs +24 -0
  85. package/lib/commands/preview-docs/preview-server/hot.js +59 -0
  86. package/lib/commands/preview-docs/preview-server/oauth2-redirect.html +21 -0
  87. package/lib/commands/preview-docs/preview-server/preview-server.d.ts +5 -0
  88. package/lib/commands/preview-docs/preview-server/preview-server.js +113 -0
  89. package/lib/commands/preview-docs/preview-server/server.d.ts +22 -0
  90. package/lib/commands/preview-docs/preview-server/server.js +85 -0
  91. package/lib/commands/preview-project/constants.d.ts +14 -0
  92. package/lib/commands/preview-project/constants.js +22 -0
  93. package/lib/commands/preview-project/index.d.ts +3 -0
  94. package/lib/commands/preview-project/index.js +56 -0
  95. package/lib/commands/preview-project/types.d.ts +10 -0
  96. package/lib/commands/preview-project/types.js +2 -0
  97. package/lib/commands/push.d.ts +44 -0
  98. package/lib/commands/push.js +295 -0
  99. package/lib/commands/split/__tests__/index.test.d.ts +1 -0
  100. package/lib/commands/split/__tests__/index.test.js +91 -0
  101. package/lib/commands/split/index.d.ts +13 -0
  102. package/lib/commands/split/index.js +259 -0
  103. package/lib/commands/split/types.d.ts +36 -0
  104. package/lib/commands/split/types.js +51 -0
  105. package/lib/commands/stats.d.ts +8 -0
  106. package/lib/commands/stats.js +96 -0
  107. package/lib/commands/translations.d.ts +7 -0
  108. package/lib/commands/translations.js +20 -0
  109. package/lib/index.d.ts +2 -0
  110. package/lib/index.js +733 -0
  111. package/lib/types.d.ts +43 -0
  112. package/lib/types.js +5 -0
  113. package/lib/utils/__mocks__/miscellaneous.d.ts +43 -0
  114. package/lib/utils/__mocks__/miscellaneous.js +24 -0
  115. package/lib/utils/assert-node-version.d.ts +1 -0
  116. package/lib/utils/assert-node-version.js +16 -0
  117. package/lib/utils/fetch-with-timeout.d.ts +7 -0
  118. package/lib/utils/fetch-with-timeout.js +26 -0
  119. package/lib/utils/getCommandNameFromArgs.d.ts +2 -0
  120. package/lib/utils/getCommandNameFromArgs.js +6 -0
  121. package/lib/utils/js-utils.d.ts +5 -0
  122. package/lib/utils/js-utils.js +28 -0
  123. package/lib/utils/miscellaneous.d.ts +81 -0
  124. package/lib/utils/miscellaneous.js +551 -0
  125. package/lib/utils/platform.d.ts +16 -0
  126. package/lib/utils/platform.js +34 -0
  127. package/lib/utils/spinner.d.ts +10 -0
  128. package/lib/utils/spinner.js +42 -0
  129. package/lib/utils/update-version-notifier.d.ts +3 -0
  130. package/lib/utils/update-version-notifier.js +102 -0
  131. package/lib/wrapper.d.ts +11 -0
  132. package/lib/wrapper.js +65 -0
  133. package/package.json +69 -0
@@ -0,0 +1,565 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleJoin = handleJoin;
4
+ const path = require("path");
5
+ const colorette_1 = require("colorette");
6
+ const perf_hooks_1 = require("perf_hooks");
7
+ const openapi_core_1 = require("@redocly/openapi-core");
8
+ const utils_1 = require("@redocly/openapi-core/lib/utils");
9
+ const miscellaneous_1 = require("../utils/miscellaneous");
10
+ const js_utils_1 = require("../utils/js-utils");
11
+ const types_1 = require("./split/types");
12
+ const split_1 = require("./split");
13
+ const Tags = 'tags';
14
+ const xTagGroups = 'x-tagGroups';
15
+ let potentialConflictsTotal = 0;
16
+ async function handleJoin({ argv, config, version: packageVersion, }) {
17
+ const startedAt = perf_hooks_1.performance.now();
18
+ const { 'prefix-components-with-info-prop': prefixComponentsWithInfoProp, 'prefix-tags-with-filename': prefixTagsWithFilename, 'prefix-tags-with-info-prop': prefixTagsWithInfoProp, 'without-x-tag-groups': withoutXTagGroups, output, } = argv;
19
+ const usedTagsOptions = [
20
+ prefixTagsWithFilename && 'prefix-tags-with-filename',
21
+ prefixTagsWithInfoProp && 'prefix-tags-with-info-prop',
22
+ withoutXTagGroups && 'without-x-tag-groups',
23
+ ].filter(Boolean);
24
+ if (usedTagsOptions.length > 1) {
25
+ return (0, miscellaneous_1.exitWithError)(`You use ${(0, colorette_1.yellow)(usedTagsOptions.join(', '))} together.\nPlease choose only one!`);
26
+ }
27
+ const apis = await (0, miscellaneous_1.getFallbackApisOrExit)(argv.apis, config);
28
+ if (apis.length < 2) {
29
+ return (0, miscellaneous_1.exitWithError)(`At least 2 APIs should be provided.`);
30
+ }
31
+ const fileExtension = (0, miscellaneous_1.getAndValidateFileExtension)(output || apis[0].path);
32
+ const specFilename = output || `openapi.${fileExtension}`;
33
+ const externalRefResolver = new openapi_core_1.BaseResolver(config.resolve);
34
+ const documents = await Promise.all(apis.map(({ path }) => externalRefResolver.resolveDocument(null, path, true)));
35
+ const decorators = new Set([
36
+ ...Object.keys(config.styleguide.decorators.oas3_0),
37
+ ...Object.keys(config.styleguide.decorators.oas3_1),
38
+ ...Object.keys(config.styleguide.decorators.oas2),
39
+ ]);
40
+ config.styleguide.skipDecorators(Array.from(decorators));
41
+ const preprocessors = new Set([
42
+ ...Object.keys(config.styleguide.preprocessors.oas3_0),
43
+ ...Object.keys(config.styleguide.preprocessors.oas3_1),
44
+ ...Object.keys(config.styleguide.preprocessors.oas2),
45
+ ]);
46
+ config.styleguide.skipPreprocessors(Array.from(preprocessors));
47
+ const bundleResults = await Promise.all(documents.map((document) => (0, openapi_core_1.bundleDocument)({
48
+ document,
49
+ config: config.styleguide,
50
+ externalRefResolver: new openapi_core_1.BaseResolver(config.resolve),
51
+ }).catch((e) => {
52
+ (0, miscellaneous_1.exitWithError)(`${e.message}: ${(0, colorette_1.blue)(document.source.absoluteRef)}`);
53
+ })));
54
+ for (const { problems, bundle: document } of bundleResults) {
55
+ const fileTotals = (0, openapi_core_1.getTotals)(problems);
56
+ if (fileTotals.errors) {
57
+ (0, openapi_core_1.formatProblems)(problems, {
58
+ totals: fileTotals,
59
+ version: packageVersion,
60
+ });
61
+ (0, miscellaneous_1.exitWithError)(`❌ Errors encountered while bundling ${(0, colorette_1.blue)(document.source.absoluteRef)}: join will not proceed.`);
62
+ }
63
+ }
64
+ let oasVersion = null;
65
+ for (const document of documents) {
66
+ try {
67
+ const version = (0, openapi_core_1.detectSpec)(document.parsed);
68
+ if (version !== openapi_core_1.SpecVersion.OAS3_0 && version !== openapi_core_1.SpecVersion.OAS3_1) {
69
+ return (0, miscellaneous_1.exitWithError)(`Only OpenAPI 3.0 and OpenAPI 3.1 are supported: ${(0, colorette_1.blue)(document.source.absoluteRef)}.`);
70
+ }
71
+ oasVersion = oasVersion ?? version;
72
+ if (oasVersion !== version) {
73
+ return (0, miscellaneous_1.exitWithError)(`All APIs must use the same OpenAPI version: ${(0, colorette_1.blue)(document.source.absoluteRef)}.`);
74
+ }
75
+ }
76
+ catch (e) {
77
+ return (0, miscellaneous_1.exitWithError)(`${e.message}: ${(0, colorette_1.blue)(document.source.absoluteRef)}.`);
78
+ }
79
+ }
80
+ const joinedDef = {};
81
+ const potentialConflicts = {
82
+ tags: {},
83
+ paths: {},
84
+ components: {},
85
+ webhooks: {},
86
+ };
87
+ addInfoSectionAndSpecVersion(documents, prefixComponentsWithInfoProp);
88
+ for (const document of documents) {
89
+ const openapi = document.parsed;
90
+ const { tags, info } = openapi;
91
+ const api = path.relative(process.cwd(), document.source.absoluteRef);
92
+ const apiFilename = getApiFilename(api);
93
+ const tagsPrefix = prefixTagsWithFilename
94
+ ? apiFilename
95
+ : getInfoPrefix(info, prefixTagsWithInfoProp, 'tags');
96
+ const componentsPrefix = getInfoPrefix(info, prefixComponentsWithInfoProp, types_1.COMPONENTS);
97
+ if (openapi.hasOwnProperty('x-tagGroups')) {
98
+ process.stderr.write((0, colorette_1.yellow)(`warning: x-tagGroups at ${(0, colorette_1.blue)(api)} will be skipped \n`));
99
+ }
100
+ const context = {
101
+ api,
102
+ apiFilename,
103
+ apiTitle: info?.title,
104
+ tags,
105
+ potentialConflicts,
106
+ tagsPrefix,
107
+ componentsPrefix,
108
+ };
109
+ if (tags) {
110
+ populateTags(context);
111
+ }
112
+ collectServers(openapi);
113
+ collectExternalDocs(openapi, context);
114
+ collectPaths(openapi, context);
115
+ collectComponents(openapi, context);
116
+ collectWebhooks(oasVersion, openapi, context);
117
+ if (componentsPrefix) {
118
+ replace$Refs(openapi, componentsPrefix);
119
+ }
120
+ }
121
+ iteratePotentialConflicts(potentialConflicts, withoutXTagGroups);
122
+ const noRefs = true;
123
+ if (potentialConflictsTotal) {
124
+ return (0, miscellaneous_1.exitWithError)(`Please fix conflicts before running ${(0, colorette_1.yellow)('join')}.`);
125
+ }
126
+ (0, miscellaneous_1.writeToFileByExtension)((0, miscellaneous_1.sortTopLevelKeysForOas)(joinedDef), specFilename, noRefs);
127
+ (0, miscellaneous_1.printExecutionTime)('join', startedAt, specFilename);
128
+ function populateTags({ api, apiFilename, apiTitle, tags, potentialConflicts, tagsPrefix, componentsPrefix, }) {
129
+ if (!joinedDef.hasOwnProperty(Tags)) {
130
+ joinedDef[Tags] = [];
131
+ }
132
+ if (!potentialConflicts.tags.hasOwnProperty('all')) {
133
+ potentialConflicts.tags['all'] = {};
134
+ }
135
+ if (withoutXTagGroups && !potentialConflicts.tags.hasOwnProperty('description')) {
136
+ potentialConflicts.tags['description'] = {};
137
+ }
138
+ for (const tag of tags) {
139
+ const entrypointTagName = addPrefix(tag.name, tagsPrefix);
140
+ if (tag.description) {
141
+ tag.description = addComponentsPrefix(tag.description, componentsPrefix);
142
+ }
143
+ const tagDuplicate = joinedDef.tags.find((t) => t.name === entrypointTagName);
144
+ if (tagDuplicate && withoutXTagGroups) {
145
+ // If tag already exist and `without-x-tag-groups` option,
146
+ // check if description are different for potential conflicts warning.
147
+ const isTagDescriptionNotEqual = tag.hasOwnProperty('description') && tagDuplicate.description !== tag.description;
148
+ potentialConflicts.tags.description[entrypointTagName].push(...(isTagDescriptionNotEqual ? [api] : []));
149
+ }
150
+ else if (!tagDuplicate) {
151
+ // Instead add tag to joinedDef if there no duplicate;
152
+ tag['x-displayName'] = tag['x-displayName'] || tag.name;
153
+ tag.name = entrypointTagName;
154
+ joinedDef.tags.push(tag);
155
+ if (withoutXTagGroups) {
156
+ potentialConflicts.tags.description[entrypointTagName] = [api];
157
+ }
158
+ }
159
+ if (!withoutXTagGroups) {
160
+ const groupName = apiTitle || apiFilename;
161
+ createXTagGroups(groupName);
162
+ if (!tagDuplicate) {
163
+ populateXTagGroups(entrypointTagName, getIndexGroup(groupName));
164
+ }
165
+ }
166
+ const doesEntrypointExist = !potentialConflicts.tags.all[entrypointTagName] ||
167
+ (potentialConflicts.tags.all[entrypointTagName] &&
168
+ !potentialConflicts.tags.all[entrypointTagName].includes(api));
169
+ potentialConflicts.tags.all[entrypointTagName] = [
170
+ ...(potentialConflicts.tags.all[entrypointTagName] || []),
171
+ ...(!withoutXTagGroups && doesEntrypointExist ? [api] : []),
172
+ ];
173
+ }
174
+ }
175
+ function getIndexGroup(name) {
176
+ return joinedDef[xTagGroups].findIndex((item) => item.name === name);
177
+ }
178
+ function createXTagGroups(name) {
179
+ if (!joinedDef.hasOwnProperty(xTagGroups)) {
180
+ joinedDef[xTagGroups] = [];
181
+ }
182
+ if (!joinedDef[xTagGroups].some((g) => g.name === name)) {
183
+ joinedDef[xTagGroups].push({ name, tags: [] });
184
+ }
185
+ const indexGroup = getIndexGroup(name);
186
+ if (!joinedDef[xTagGroups][indexGroup].hasOwnProperty(Tags)) {
187
+ joinedDef[xTagGroups][indexGroup][Tags] = [];
188
+ }
189
+ }
190
+ function populateXTagGroups(entrypointTagName, indexGroup) {
191
+ if (!joinedDef[xTagGroups][indexGroup][Tags].find((t) => t.name === entrypointTagName)) {
192
+ joinedDef[xTagGroups][indexGroup][Tags].push(entrypointTagName);
193
+ }
194
+ }
195
+ function collectServers(openapi) {
196
+ const { servers } = openapi;
197
+ if (servers) {
198
+ if (!joinedDef.hasOwnProperty('servers')) {
199
+ joinedDef['servers'] = [];
200
+ }
201
+ for (const server of servers) {
202
+ if (!joinedDef.servers.some((s) => s.url === server.url)) {
203
+ joinedDef.servers.push(server);
204
+ }
205
+ }
206
+ }
207
+ }
208
+ function collectExternalDocs(openapi, { api }) {
209
+ const { externalDocs } = openapi;
210
+ if (externalDocs) {
211
+ if (joinedDef.hasOwnProperty('externalDocs')) {
212
+ process.stderr.write((0, colorette_1.yellow)(`warning: skip externalDocs from ${(0, colorette_1.blue)(path.basename(api))} \n`));
213
+ return;
214
+ }
215
+ joinedDef['externalDocs'] = externalDocs;
216
+ }
217
+ }
218
+ function collectPaths(openapi, { apiFilename, apiTitle, api, potentialConflicts, tagsPrefix, componentsPrefix, }) {
219
+ const { paths } = openapi;
220
+ const operationsSet = new Set((0, js_utils_1.keysOf)(types_1.OPENAPI3_METHOD));
221
+ if (paths) {
222
+ if (!joinedDef.hasOwnProperty('paths')) {
223
+ joinedDef['paths'] = {};
224
+ }
225
+ for (const path of (0, js_utils_1.keysOf)(paths)) {
226
+ if (!joinedDef.paths.hasOwnProperty(path)) {
227
+ joinedDef.paths[path] = {};
228
+ }
229
+ if (!potentialConflicts.paths.hasOwnProperty(path)) {
230
+ potentialConflicts.paths[path] = {};
231
+ }
232
+ const pathItem = paths[path];
233
+ for (const field of (0, js_utils_1.keysOf)(pathItem)) {
234
+ if (operationsSet.has(field)) {
235
+ collectPathOperation(pathItem, path, field);
236
+ }
237
+ if (field === 'servers') {
238
+ collectPathServers(pathItem, path);
239
+ }
240
+ if (field === 'parameters') {
241
+ collectPathParameters(pathItem, path);
242
+ }
243
+ if (typeof pathItem[field] === 'string') {
244
+ collectPathStringFields(pathItem, path, field);
245
+ }
246
+ }
247
+ }
248
+ }
249
+ function collectPathStringFields(pathItem, path, field) {
250
+ const fieldValue = pathItem[field];
251
+ if (joinedDef.paths[path].hasOwnProperty(field) &&
252
+ joinedDef.paths[path][field] !== fieldValue) {
253
+ process.stderr.write((0, colorette_1.yellow)(`warning: different ${field} values in ${path}\n`));
254
+ return;
255
+ }
256
+ joinedDef.paths[path][field] = fieldValue;
257
+ }
258
+ function collectPathServers(pathItem, path) {
259
+ if (!pathItem.servers) {
260
+ return;
261
+ }
262
+ if (!joinedDef.paths[path].hasOwnProperty('servers')) {
263
+ joinedDef.paths[path].servers = [];
264
+ }
265
+ for (const server of pathItem.servers) {
266
+ let isFoundServer = false;
267
+ for (const pathServer of joinedDef.paths[path].servers) {
268
+ if (pathServer.url === server.url) {
269
+ if (!isServersEqual(pathServer, server)) {
270
+ (0, miscellaneous_1.exitWithError)(`Different server values for (${server.url}) in ${path}.`);
271
+ }
272
+ isFoundServer = true;
273
+ }
274
+ }
275
+ if (!isFoundServer) {
276
+ joinedDef.paths[path].servers.push(server);
277
+ }
278
+ }
279
+ }
280
+ function collectPathParameters(pathItem, path) {
281
+ if (!pathItem.parameters) {
282
+ return;
283
+ }
284
+ if (!joinedDef.paths[path].hasOwnProperty('parameters')) {
285
+ joinedDef.paths[path].parameters = [];
286
+ }
287
+ for (const parameter of pathItem.parameters) {
288
+ let isFoundParameter = false;
289
+ for (const pathParameter of joinedDef.paths[path]
290
+ .parameters) {
291
+ // Compare $ref only if both are reference objects
292
+ if ((0, openapi_core_1.isRef)(pathParameter) && (0, openapi_core_1.isRef)(parameter)) {
293
+ if (pathParameter['$ref'] === parameter['$ref']) {
294
+ isFoundParameter = true;
295
+ }
296
+ }
297
+ // Compare properties only if both are reference objects
298
+ if (!(0, openapi_core_1.isRef)(pathParameter) && !(0, openapi_core_1.isRef)(parameter)) {
299
+ if (pathParameter.name === parameter.name && pathParameter.in === parameter.in) {
300
+ if (!(0, utils_1.dequal)(pathParameter.schema, parameter.schema)) {
301
+ (0, miscellaneous_1.exitWithError)(`Different parameter schemas for (${parameter.name}) in ${path}.`);
302
+ }
303
+ isFoundParameter = true;
304
+ }
305
+ }
306
+ }
307
+ if (!isFoundParameter) {
308
+ joinedDef.paths[path].parameters.push(parameter);
309
+ }
310
+ }
311
+ }
312
+ function collectPathOperation(pathItem, path, operation) {
313
+ const pathOperation = pathItem[operation];
314
+ if (!pathOperation) {
315
+ return;
316
+ }
317
+ joinedDef.paths[path][operation] = pathOperation;
318
+ potentialConflicts.paths[path][operation] = [
319
+ ...(potentialConflicts.paths[path][operation] || []),
320
+ api,
321
+ ];
322
+ const { operationId } = pathOperation;
323
+ if (operationId) {
324
+ if (!potentialConflicts.paths.hasOwnProperty('operationIds')) {
325
+ potentialConflicts.paths['operationIds'] = {};
326
+ }
327
+ potentialConflicts.paths.operationIds[operationId] = [
328
+ ...(potentialConflicts.paths.operationIds[operationId] || []),
329
+ api,
330
+ ];
331
+ }
332
+ const { tags, security } = joinedDef.paths[path][operation];
333
+ if (tags) {
334
+ joinedDef.paths[path][operation].tags = tags.map((tag) => addPrefix(tag, tagsPrefix));
335
+ populateTags({
336
+ api,
337
+ apiFilename,
338
+ apiTitle,
339
+ tags: formatTags(tags),
340
+ potentialConflicts,
341
+ tagsPrefix,
342
+ componentsPrefix,
343
+ });
344
+ }
345
+ else {
346
+ joinedDef.paths[path][operation]['tags'] = [addPrefix('other', tagsPrefix || apiFilename)];
347
+ populateTags({
348
+ api,
349
+ apiFilename,
350
+ apiTitle,
351
+ tags: formatTags(['other']),
352
+ potentialConflicts,
353
+ tagsPrefix: tagsPrefix || apiFilename,
354
+ componentsPrefix,
355
+ });
356
+ }
357
+ if (!security && openapi.hasOwnProperty('security')) {
358
+ joinedDef.paths[path][operation]['security'] = addSecurityPrefix(openapi.security, componentsPrefix);
359
+ }
360
+ else if (pathOperation.security) {
361
+ joinedDef.paths[path][operation].security = addSecurityPrefix(pathOperation.security, componentsPrefix);
362
+ }
363
+ }
364
+ }
365
+ function isServersEqual(serverOne, serverTwo) {
366
+ if (serverOne.description === serverTwo.description) {
367
+ return (0, utils_1.dequal)(serverOne.variables, serverTwo.variables);
368
+ }
369
+ return false;
370
+ }
371
+ function collectComponents(openapi, { api, potentialConflicts, componentsPrefix }) {
372
+ const { components } = openapi;
373
+ if (components) {
374
+ if (!joinedDef.hasOwnProperty(types_1.COMPONENTS)) {
375
+ joinedDef[types_1.COMPONENTS] = {};
376
+ }
377
+ for (const [component, componentObj] of Object.entries(components)) {
378
+ if (!potentialConflicts[types_1.COMPONENTS].hasOwnProperty(component)) {
379
+ potentialConflicts[types_1.COMPONENTS][component] = {};
380
+ joinedDef[types_1.COMPONENTS][component] = {};
381
+ }
382
+ for (const item of Object.keys(componentObj)) {
383
+ const componentPrefix = addPrefix(item, componentsPrefix);
384
+ potentialConflicts.components[component][componentPrefix] = [
385
+ ...(potentialConflicts.components[component][item] || []),
386
+ { [api]: componentObj[item] },
387
+ ];
388
+ joinedDef.components[component][componentPrefix] = componentObj[item];
389
+ }
390
+ }
391
+ }
392
+ }
393
+ function collectWebhooks(oasVersion, openapi, { apiFilename, apiTitle, api, potentialConflicts, tagsPrefix, componentsPrefix, }) {
394
+ const webhooks = oasVersion === openapi_core_1.SpecVersion.OAS3_1 ? 'webhooks' : 'x-webhooks';
395
+ const openapiWebhooks = openapi[webhooks];
396
+ if (openapiWebhooks) {
397
+ if (!joinedDef.hasOwnProperty(webhooks)) {
398
+ joinedDef[webhooks] = {};
399
+ }
400
+ for (const webhook of Object.keys(openapiWebhooks)) {
401
+ joinedDef[webhooks][webhook] = openapiWebhooks[webhook];
402
+ if (!potentialConflicts.webhooks.hasOwnProperty(webhook)) {
403
+ potentialConflicts.webhooks[webhook] = {};
404
+ }
405
+ for (const operation of Object.keys(openapiWebhooks[webhook])) {
406
+ potentialConflicts.webhooks[webhook][operation] = [
407
+ ...(potentialConflicts.webhooks[webhook][operation] || []),
408
+ api,
409
+ ];
410
+ }
411
+ for (const operationKey of Object.keys(joinedDef[webhooks][webhook])) {
412
+ const { tags } = joinedDef[webhooks][webhook][operationKey];
413
+ if (tags) {
414
+ joinedDef[webhooks][webhook][operationKey].tags = tags.map((tag) => addPrefix(tag, tagsPrefix));
415
+ populateTags({
416
+ api,
417
+ apiFilename,
418
+ apiTitle,
419
+ tags: formatTags(tags),
420
+ potentialConflicts,
421
+ tagsPrefix,
422
+ componentsPrefix,
423
+ });
424
+ }
425
+ }
426
+ }
427
+ }
428
+ }
429
+ function addInfoSectionAndSpecVersion(documents, prefixComponentsWithInfoProp) {
430
+ const firstApi = documents[0];
431
+ const openapi = firstApi.parsed;
432
+ const componentsPrefix = getInfoPrefix(openapi.info, prefixComponentsWithInfoProp, types_1.COMPONENTS);
433
+ if (!openapi.openapi)
434
+ (0, miscellaneous_1.exitWithError)('Version of specification is not found.');
435
+ if (!openapi.info)
436
+ (0, miscellaneous_1.exitWithError)('Info section is not found in specification.');
437
+ if (openapi.info?.description) {
438
+ openapi.info.description = addComponentsPrefix(openapi.info.description, componentsPrefix);
439
+ }
440
+ joinedDef.openapi = openapi.openapi;
441
+ joinedDef.info = openapi.info;
442
+ }
443
+ }
444
+ function doesComponentsDiffer(curr, next) {
445
+ return !(0, utils_1.dequal)(Object.values(curr)[0], Object.values(next)[0]);
446
+ }
447
+ function validateComponentsDifference(files) {
448
+ let isDiffer = false;
449
+ for (let i = 0, len = files.length; i < len; i++) {
450
+ const next = files[i + 1];
451
+ if (next && doesComponentsDiffer(files[i], next)) {
452
+ isDiffer = true;
453
+ }
454
+ }
455
+ return isDiffer;
456
+ }
457
+ function iteratePotentialConflicts(potentialConflicts, withoutXTagGroups) {
458
+ for (const group of Object.keys(potentialConflicts)) {
459
+ for (const [key, value] of Object.entries(potentialConflicts[group])) {
460
+ const conflicts = filterConflicts(value);
461
+ if (conflicts.length) {
462
+ if (group === types_1.COMPONENTS) {
463
+ for (const [_, conflict] of Object.entries(conflicts)) {
464
+ if (validateComponentsDifference(conflict[1])) {
465
+ conflict[1] = conflict[1].map((c) => Object.keys(c)[0]);
466
+ showConflicts((0, colorette_1.green)(group) + ' => ' + key, [conflict]);
467
+ potentialConflictsTotal += 1;
468
+ }
469
+ }
470
+ }
471
+ else {
472
+ if (withoutXTagGroups && group === 'tags') {
473
+ duplicateTagDescriptionWarning(conflicts);
474
+ }
475
+ else {
476
+ potentialConflictsTotal += conflicts.length;
477
+ showConflicts((0, colorette_1.green)(group) + ' => ' + key, conflicts);
478
+ }
479
+ }
480
+ if (group === 'tags' && !withoutXTagGroups) {
481
+ prefixTagSuggestion(conflicts.length);
482
+ }
483
+ }
484
+ }
485
+ }
486
+ }
487
+ function duplicateTagDescriptionWarning(conflicts) {
488
+ const tagsKeys = conflicts.map(([tagName]) => `\`${tagName}\``);
489
+ const joinString = (0, colorette_1.yellow)(', ');
490
+ process.stderr.write((0, colorette_1.yellow)(`\nwarning: ${tagsKeys.length} conflict(s) on the ${(0, colorette_1.red)(tagsKeys.join(joinString))} tags description.\n`));
491
+ }
492
+ function prefixTagSuggestion(conflictsLength) {
493
+ process.stderr.write((0, colorette_1.green)(`\n${conflictsLength} conflict(s) on tags.\nSuggestion: please use ${(0, colorette_1.blue)('prefix-tags-with-filename')}, ${(0, colorette_1.blue)('prefix-tags-with-info-prop')} or ${(0, colorette_1.blue)('without-x-tag-groups')} to prevent naming conflicts.\n\n`));
494
+ }
495
+ function showConflicts(key, conflicts) {
496
+ for (const [path, files] of conflicts) {
497
+ process.stderr.write((0, colorette_1.yellow)(`Conflict on ${key} : ${(0, colorette_1.red)(path)} in files: ${(0, colorette_1.blue)(files)} \n`));
498
+ }
499
+ }
500
+ function filterConflicts(entities) {
501
+ return Object.entries(entities).filter(([_, files]) => files.length > 1);
502
+ }
503
+ function getApiFilename(filePath) {
504
+ return path.basename(filePath, path.extname(filePath));
505
+ }
506
+ function addPrefix(tag, tagsPrefix) {
507
+ return tagsPrefix ? tagsPrefix + '_' + tag : tag;
508
+ }
509
+ function formatTags(tags) {
510
+ return tags.map((tag) => ({ name: tag }));
511
+ }
512
+ function addComponentsPrefix(description, componentsPrefix) {
513
+ return description.replace(/"(#\/components\/.*?)"/g, (match) => {
514
+ const componentName = path.basename(match);
515
+ return match.replace(componentName, addPrefix(componentName, componentsPrefix));
516
+ });
517
+ }
518
+ function addSecurityPrefix(security, componentsPrefix) {
519
+ return componentsPrefix
520
+ ? security?.map((s) => {
521
+ const joinedSecuritySchema = {};
522
+ for (const [key, value] of Object.entries(s)) {
523
+ Object.assign(joinedSecuritySchema, { [componentsPrefix + '_' + key]: value });
524
+ }
525
+ return joinedSecuritySchema;
526
+ })
527
+ : security;
528
+ }
529
+ function getInfoPrefix(info, prefixArg, type) {
530
+ if (!prefixArg)
531
+ return '';
532
+ if (!info)
533
+ (0, miscellaneous_1.exitWithError)('Info section is not found in specification.');
534
+ if (!info[prefixArg])
535
+ (0, miscellaneous_1.exitWithError)(`${(0, colorette_1.yellow)(`prefix-${type}-with-info-prop`)} argument value is not found in info section.`);
536
+ if (!(0, js_utils_1.isString)(info[prefixArg]))
537
+ (0, miscellaneous_1.exitWithError)(`${(0, colorette_1.yellow)(`prefix-${type}-with-info-prop`)} argument value should be string.`);
538
+ if (info[prefixArg].length > 50)
539
+ (0, miscellaneous_1.exitWithError)(`${(0, colorette_1.yellow)(`prefix-${type}-with-info-prop`)} argument value length should not exceed 50 characters.`);
540
+ return info[prefixArg].replaceAll(/\s/g, '_');
541
+ }
542
+ function replace$Refs(obj, componentsPrefix) {
543
+ (0, split_1.crawl)(obj, (node) => {
544
+ if (node.$ref && typeof node.$ref === 'string' && (0, split_1.startsWithComponents)(node.$ref)) {
545
+ const name = path.basename(node.$ref);
546
+ node.$ref = node.$ref.replace(name, componentsPrefix + '_' + name);
547
+ }
548
+ else if ((0, js_utils_1.isObject)(node.discriminator) && (0, js_utils_1.isObject)(node.discriminator.mapping)) {
549
+ const { mapping } = node.discriminator;
550
+ for (const name of Object.keys(mapping)) {
551
+ const mappingPointer = mapping[name];
552
+ if (typeof mappingPointer === 'string' && (0, split_1.startsWithComponents)(mappingPointer)) {
553
+ mapping[name] = mappingPointer
554
+ .split('/')
555
+ .map((name, i, arr) => {
556
+ return arr.length - 1 === i && !name.includes(componentsPrefix)
557
+ ? componentsPrefix + '_' + name
558
+ : name;
559
+ })
560
+ .join('/');
561
+ }
562
+ }
563
+ }
564
+ });
565
+ }
@@ -0,0 +1,13 @@
1
+ import type { OutputFormat } from '@redocly/openapi-core';
2
+ import type { RawConfigProcessor } from '@redocly/openapi-core/lib/config';
3
+ import type { CommandOptions, Skips, VerifyConfigOptions } from '../types';
4
+ import type { CommandArgs } from '../wrapper';
5
+ export type LintOptions = {
6
+ apis?: string[];
7
+ 'max-problems': number;
8
+ extends?: string[];
9
+ format: OutputFormat;
10
+ 'generate-ignore-file'?: boolean;
11
+ } & Omit<Skips, 'skip-decorator'> & VerifyConfigOptions;
12
+ export declare function handleLint({ argv, config, version, collectSpecData, }: CommandArgs<LintOptions>): Promise<void>;
13
+ export declare function lintConfigCallback(argv: CommandOptions & Record<string, undefined>, version: string): RawConfigProcessor | undefined;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleLint = handleLint;
4
+ exports.lintConfigCallback = lintConfigCallback;
5
+ const colorette_1 = require("colorette");
6
+ const perf_hooks_1 = require("perf_hooks");
7
+ const openapi_core_1 = require("@redocly/openapi-core");
8
+ const config_1 = require("@redocly/openapi-core/lib/config");
9
+ const utils_1 = require("@redocly/openapi-core/lib/utils");
10
+ const miscellaneous_1 = require("../utils/miscellaneous");
11
+ const getCommandNameFromArgs_1 = require("../utils/getCommandNameFromArgs");
12
+ async function handleLint({ argv, config, version, collectSpecData, }) {
13
+ const apis = await (0, miscellaneous_1.getFallbackApisOrExit)(argv.apis, config);
14
+ if (!apis.length) {
15
+ (0, miscellaneous_1.exitWithError)('No APIs were provided.');
16
+ }
17
+ if (argv['generate-ignore-file']) {
18
+ config.styleguide.ignore = {}; // clear ignore
19
+ }
20
+ const totals = { errors: 0, warnings: 0, ignored: 0 };
21
+ let totalIgnored = 0;
22
+ // TODO: use shared externalRef resolver, blocked by preprocessors now as they can mutate documents
23
+ for (const { path, alias } of apis) {
24
+ try {
25
+ const startedAt = perf_hooks_1.performance.now();
26
+ const resolvedConfig = (0, openapi_core_1.getMergedConfig)(config, alias);
27
+ const { styleguide } = resolvedConfig;
28
+ (0, miscellaneous_1.checkIfRulesetExist)(styleguide.rules);
29
+ styleguide.skipRules(argv['skip-rule']);
30
+ styleguide.skipPreprocessors(argv['skip-preprocessor']);
31
+ if (styleguide.recommendedFallback) {
32
+ process.stderr.write(`No configurations were provided -- using built in ${(0, colorette_1.blue)('recommended')} configuration by default.\n\n`);
33
+ }
34
+ process.stderr.write((0, colorette_1.gray)(`validating ${(0, miscellaneous_1.formatPath)(path)}...\n`));
35
+ const results = await (0, openapi_core_1.lint)({
36
+ ref: path,
37
+ config: resolvedConfig,
38
+ collectSpecData,
39
+ });
40
+ const fileTotals = (0, openapi_core_1.getTotals)(results);
41
+ totals.errors += fileTotals.errors;
42
+ totals.warnings += fileTotals.warnings;
43
+ totals.ignored += fileTotals.ignored;
44
+ if (argv['generate-ignore-file']) {
45
+ for (const m of results) {
46
+ config.styleguide.addIgnore(m);
47
+ totalIgnored++;
48
+ }
49
+ }
50
+ else {
51
+ (0, openapi_core_1.formatProblems)(results, {
52
+ format: argv.format,
53
+ maxProblems: argv['max-problems'],
54
+ totals: fileTotals,
55
+ version,
56
+ });
57
+ }
58
+ const elapsed = (0, miscellaneous_1.getExecutionTime)(startedAt);
59
+ process.stderr.write((0, colorette_1.gray)(`${(0, miscellaneous_1.formatPath)(path)}: validated in ${elapsed}\n\n`));
60
+ }
61
+ catch (e) {
62
+ (0, miscellaneous_1.handleError)(e, path);
63
+ }
64
+ }
65
+ if (argv['generate-ignore-file']) {
66
+ config.styleguide.saveIgnore();
67
+ process.stderr.write(`Generated ignore file with ${totalIgnored} ${(0, utils_1.pluralize)('problem', totalIgnored)}.\n\n`);
68
+ }
69
+ else {
70
+ (0, miscellaneous_1.printLintTotals)(totals, apis.length);
71
+ }
72
+ (0, miscellaneous_1.printUnusedWarnings)(config.styleguide);
73
+ if (!(totals.errors === 0 || argv['generate-ignore-file'])) {
74
+ throw new Error('Lint failed.');
75
+ }
76
+ }
77
+ function lintConfigCallback(argv, version) {
78
+ if (argv['lint-config'] === 'off') {
79
+ return;
80
+ }
81
+ if (argv.format === 'json') {
82
+ // we can't print config lint results as it will break json output
83
+ return;
84
+ }
85
+ return async ({ document, resolvedRefMap, config, parsed: { theme = {} } }) => {
86
+ const command = argv ? (0, getCommandNameFromArgs_1.getCommandNameFromArgs)(argv) : undefined;
87
+ if (command === 'check-config') {
88
+ (0, miscellaneous_1.notifyAboutIncompatibleConfigOptions)(theme.openapi);
89
+ }
90
+ const problems = await (0, openapi_core_1.lintConfig)({
91
+ document,
92
+ resolvedRefMap,
93
+ config,
94
+ severity: (argv['lint-config'] || 'warn'),
95
+ });
96
+ const fileTotals = (0, openapi_core_1.getTotals)(problems);
97
+ (0, openapi_core_1.formatProblems)(problems, {
98
+ format: argv.format,
99
+ maxProblems: argv['max-problems'],
100
+ totals: fileTotals,
101
+ version,
102
+ });
103
+ (0, miscellaneous_1.printConfigLintTotals)(fileTotals, command);
104
+ if (fileTotals.errors > 0) {
105
+ throw new config_1.ConfigValidationError();
106
+ }
107
+ };
108
+ }