@redocly/cli 1.0.0-beta.98 → 1.0.0-rc.1

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