@redocly/openapi-core 1.0.0-beta.106 → 1.0.0-beta.109

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 (193) hide show
  1. package/lib/benchmark/benches/lint-with-top-level-rule-report.bench.js +0 -1
  2. package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
  3. package/lib/bundle.d.ts +1 -1
  4. package/lib/bundle.js +9 -6
  5. package/lib/config/all.js +5 -3
  6. package/lib/config/config-resolvers.js +32 -14
  7. package/lib/config/config.d.ts +3 -5
  8. package/lib/config/config.js +7 -4
  9. package/lib/config/load.d.ts +7 -0
  10. package/lib/config/load.js +14 -6
  11. package/lib/config/minimal.js +7 -4
  12. package/lib/config/recommended.js +7 -4
  13. package/lib/config/rules.d.ts +1 -1
  14. package/lib/config/rules.js +1 -1
  15. package/lib/config/types.d.ts +7 -0
  16. package/lib/config/utils.d.ts +2 -2
  17. package/lib/config/utils.js +49 -11
  18. package/lib/decorators/common/registry-dependencies.js +2 -2
  19. package/lib/env.d.ts +3 -0
  20. package/lib/env.js +8 -0
  21. package/lib/format/codeframes.js +16 -10
  22. package/lib/format/format.d.ts +1 -1
  23. package/lib/format/format.js +49 -26
  24. package/lib/index.d.ts +5 -5
  25. package/lib/index.js +3 -1
  26. package/lib/js-yaml/index.js +1 -0
  27. package/lib/lint.js +2 -2
  28. package/lib/logger.d.ts +10 -0
  29. package/lib/logger.js +31 -0
  30. package/lib/output.d.ts +3 -0
  31. package/lib/output.js +9 -0
  32. package/lib/redocly/index.js +10 -9
  33. package/lib/redocly/registry-api-types.d.ts +28 -30
  34. package/lib/redocly/registry-api.d.ts +3 -3
  35. package/lib/redocly/registry-api.js +7 -1
  36. package/lib/ref-utils.js +2 -1
  37. package/lib/resolve.d.ts +1 -1
  38. package/lib/resolve.js +4 -2
  39. package/lib/rules/ajv.d.ts +1 -1
  40. package/lib/rules/ajv.js +7 -7
  41. package/lib/rules/common/assertions/asserts.js +4 -4
  42. package/lib/rules/common/assertions/index.js +1 -1
  43. package/lib/rules/common/no-ambiguous-paths.js +1 -1
  44. package/lib/rules/common/no-identical-paths.js +1 -1
  45. package/lib/rules/common/no-invalid-parameter-examples.js +3 -3
  46. package/lib/rules/common/no-invalid-schema-examples.js +3 -3
  47. package/lib/rules/common/operation-2xx-response.js +1 -1
  48. package/lib/rules/common/operation-4xx-response.js +1 -1
  49. package/lib/rules/common/operation-operationId.js +1 -1
  50. package/lib/rules/common/operation-tag-defined.js +1 -1
  51. package/lib/rules/common/path-not-include-query.js +1 -1
  52. package/lib/rules/common/security-defined.d.ts +2 -0
  53. package/lib/rules/common/{operation-security-defined.js → security-defined.js} +19 -5
  54. package/lib/rules/common/spec.js +14 -3
  55. package/lib/rules/common/tags-alphabetical.js +1 -1
  56. package/lib/rules/oas2/index.d.ts +1 -1
  57. package/lib/rules/oas2/index.js +2 -2
  58. package/lib/rules/oas2/remove-unused-components.js +3 -3
  59. package/lib/rules/oas2/request-mime-type.js +1 -1
  60. package/lib/rules/oas2/response-mime-type.js +1 -1
  61. package/lib/rules/oas3/index.js +8 -4
  62. package/lib/rules/oas3/no-empty-servers.js +1 -1
  63. package/lib/rules/oas3/no-invalid-media-type-examples.js +2 -2
  64. package/lib/rules/oas3/no-server-variables-empty-enum.d.ts +2 -0
  65. package/lib/rules/oas3/{no-servers-empty-enum.js → no-server-variables-empty-enum.js} +5 -5
  66. package/lib/rules/oas3/no-unused-components.js +2 -2
  67. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.d.ts +5 -0
  68. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.js +36 -0
  69. package/lib/rules/oas3/remove-unused-components.js +4 -4
  70. package/lib/rules/oas3/request-mime-type.js +1 -1
  71. package/lib/rules/oas3/response-mime-type.js +1 -1
  72. package/lib/rules/oas3/spec-components-invalid-map-name.d.ts +2 -0
  73. package/lib/rules/oas3/spec-components-invalid-map-name.js +46 -0
  74. package/lib/rules/other/stats.d.ts +2 -2
  75. package/lib/rules/other/stats.js +2 -2
  76. package/lib/rules/utils.d.ts +3 -2
  77. package/lib/rules/utils.js +16 -4
  78. package/lib/types/oas2.js +5 -5
  79. package/lib/types/oas3.js +27 -20
  80. package/lib/types/oas3_1.js +3 -3
  81. package/lib/types/redocly-yaml.js +47 -56
  82. package/lib/utils.d.ts +6 -1
  83. package/lib/utils.js +24 -7
  84. package/lib/visitors.d.ts +12 -12
  85. package/lib/visitors.js +15 -3
  86. package/lib/walk.d.ts +2 -1
  87. package/lib/walk.js +6 -3
  88. package/package.json +2 -2
  89. package/src/__tests__/__snapshots__/bundle.test.ts.snap +3 -3
  90. package/src/__tests__/fixtures/extension.js +3 -3
  91. package/src/__tests__/format.test.ts +76 -0
  92. package/src/__tests__/lint.test.ts +106 -131
  93. package/src/__tests__/logger-browser.test.ts +53 -0
  94. package/src/__tests__/logger.test.ts +47 -0
  95. package/src/__tests__/output-browser.test.ts +18 -0
  96. package/src/__tests__/output.test.ts +15 -0
  97. package/src/__tests__/resolve-http.test.ts +1 -1
  98. package/src/__tests__/resolve.test.ts +9 -9
  99. package/src/__tests__/utils-browser.test.ts +11 -0
  100. package/src/__tests__/utils.test.ts +7 -0
  101. package/src/__tests__/walk.test.ts +78 -10
  102. package/src/benchmark/benches/lint-with-top-level-rule-report.bench.ts +0 -1
  103. package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
  104. package/src/bundle.ts +10 -7
  105. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +12 -6
  106. package/src/config/__tests__/config.test.ts +35 -0
  107. package/src/config/__tests__/fixtures/plugin-config.yaml +2 -3
  108. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -12
  109. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -8
  110. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -19
  111. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
  112. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -4
  113. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -5
  114. package/src/config/__tests__/load.test.ts +76 -1
  115. package/src/config/__tests__/utils.test.ts +64 -4
  116. package/src/config/all.ts +5 -3
  117. package/src/config/config-resolvers.ts +45 -19
  118. package/src/config/config.ts +10 -8
  119. package/src/config/load.ts +31 -7
  120. package/src/config/minimal.ts +7 -4
  121. package/src/config/recommended.ts +7 -4
  122. package/src/config/rules.ts +2 -2
  123. package/src/config/types.ts +11 -0
  124. package/src/config/utils.ts +115 -25
  125. package/src/decorators/common/registry-dependencies.ts +2 -2
  126. package/src/env.ts +5 -0
  127. package/src/format/codeframes.ts +15 -9
  128. package/src/format/format.ts +59 -34
  129. package/src/index.ts +6 -4
  130. package/src/js-yaml/index.ts +1 -0
  131. package/src/lint.ts +2 -2
  132. package/src/logger.ts +34 -0
  133. package/src/output.ts +7 -0
  134. package/src/redocly/index.ts +7 -4
  135. package/src/redocly/registry-api-types.ts +27 -29
  136. package/src/redocly/registry-api.ts +18 -7
  137. package/src/ref-utils.ts +2 -1
  138. package/src/resolve.ts +7 -5
  139. package/src/rules/__tests__/utils.test.ts +39 -1
  140. package/src/rules/ajv.ts +7 -7
  141. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +1 -0
  142. package/src/rules/common/__tests__/operation-2xx-response.test.ts +1 -1
  143. package/src/rules/common/__tests__/operation-4xx-response.test.ts +26 -3
  144. package/src/rules/common/__tests__/security-defined.test.ts +175 -0
  145. package/src/rules/common/__tests__/spec.test.ts +79 -0
  146. package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
  147. package/src/rules/common/assertions/asserts.ts +4 -4
  148. package/src/rules/common/assertions/index.ts +1 -1
  149. package/src/rules/common/no-ambiguous-paths.ts +1 -1
  150. package/src/rules/common/no-identical-paths.ts +1 -1
  151. package/src/rules/common/no-invalid-parameter-examples.ts +4 -4
  152. package/src/rules/common/no-invalid-schema-examples.ts +4 -4
  153. package/src/rules/common/operation-2xx-response.ts +1 -1
  154. package/src/rules/common/operation-4xx-response.ts +1 -1
  155. package/src/rules/common/operation-operationId.ts +1 -1
  156. package/src/rules/common/operation-tag-defined.ts +1 -1
  157. package/src/rules/common/path-not-include-query.ts +1 -1
  158. package/src/rules/common/{operation-security-defined.ts → security-defined.ts} +20 -5
  159. package/src/rules/common/spec.ts +17 -3
  160. package/src/rules/common/tags-alphabetical.ts +1 -1
  161. package/src/rules/oas2/index.ts +2 -2
  162. package/src/rules/oas2/remove-unused-components.ts +3 -3
  163. package/src/rules/oas2/request-mime-type.ts +1 -1
  164. package/src/rules/oas2/response-mime-type.ts +1 -1
  165. package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +16 -16
  166. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +5 -5
  167. package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
  168. package/src/rules/oas3/__tests__/spec/spec.test.ts +10 -0
  169. package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +217 -0
  170. package/src/rules/oas3/index.ts +8 -4
  171. package/src/rules/oas3/no-empty-servers.ts +1 -1
  172. package/src/rules/oas3/no-invalid-media-type-examples.ts +3 -3
  173. package/src/rules/oas3/{no-servers-empty-enum.ts → no-server-variables-empty-enum.ts} +3 -3
  174. package/src/rules/oas3/no-unused-components.ts +2 -2
  175. package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
  176. package/src/rules/oas3/remove-unused-components.ts +5 -5
  177. package/src/rules/oas3/request-mime-type.ts +1 -1
  178. package/src/rules/oas3/response-mime-type.ts +1 -1
  179. package/src/rules/oas3/spec-components-invalid-map-name.ts +53 -0
  180. package/src/rules/other/stats.ts +2 -2
  181. package/src/rules/utils.ts +17 -3
  182. package/src/types/index.ts +2 -2
  183. package/src/types/oas2.ts +5 -5
  184. package/src/types/oas3.ts +27 -20
  185. package/src/types/oas3_1.ts +3 -3
  186. package/src/types/redocly-yaml.ts +53 -41
  187. package/src/utils.ts +31 -4
  188. package/src/visitors.ts +34 -18
  189. package/src/walk.ts +15 -11
  190. package/tsconfig.tsbuildinfo +1 -1
  191. package/lib/rules/common/operation-security-defined.d.ts +0 -2
  192. package/lib/rules/oas3/no-servers-empty-enum.d.ts +0 -2
  193. package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
package/src/config/all.ts CHANGED
@@ -26,7 +26,7 @@ export default {
26
26
  'operation-operationId-url-safe': 'error',
27
27
  'operation-parameters-unique': 'error',
28
28
  'operation-tag-defined': 'error',
29
- 'operation-security-defined': 'error',
29
+ 'security-defined': 'error',
30
30
  'operation-singular-tag': 'error',
31
31
  'no-unresolved-refs': 'error',
32
32
  'no-enum-type-mismatch': 'error',
@@ -51,7 +51,8 @@ export default {
51
51
  'no-example-value-and-externalValue': 'error',
52
52
  'no-unused-components': 'error',
53
53
  'no-undefined-server-variable': 'error',
54
- 'no-servers-empty-enum': 'error',
54
+ 'no-server-variables-empty-enum': 'error',
55
+ 'operation-4xx-problem-details-rfc7807': 'error',
55
56
  },
56
57
  oas3_1Rules: {
57
58
  'no-server-example.com': 'error',
@@ -60,6 +61,7 @@ export default {
60
61
  'no-example-value-and-externalValue': 'error',
61
62
  'no-unused-components': 'error',
62
63
  'no-undefined-server-variable': 'error',
63
- 'no-servers-empty-enum': 'error',
64
+ 'no-server-variables-empty-enum': 'error',
65
+ 'operation-4xx-problem-details-rfc7807': 'error',
64
66
  },
65
67
  } as PluginStyleguideConfig;
@@ -1,5 +1,4 @@
1
1
  import * as path from 'path';
2
- import { blue, red } from 'colorette';
3
2
  import { isAbsoluteUrl } from '../ref-utils';
4
3
  import { BaseResolver } from '../resolve';
5
4
  import { defaultPlugin } from './builtIn';
@@ -21,8 +20,10 @@ import type {
21
20
  RuleConfig,
22
21
  DeprecatedInRawConfig,
23
22
  } from './types';
24
- import { isNotString, isString, notUndefined, parseYaml } from '../utils';
23
+ import { isBrowser } from '../env';
24
+ import { isNotString, isString, isDefined, parseYaml } from '../utils';
25
25
  import { Config } from './config';
26
+ import { colorize, logger } from '../logger';
26
27
 
27
28
  export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
28
29
  if (rawConfig.styleguide?.extends?.some(isNotString)) {
@@ -71,34 +72,57 @@ export function resolvePlugins(
71
72
  ): Plugin[] {
72
73
  if (!plugins) return [];
73
74
 
74
- // @ts-ignore
75
- const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
75
+ // TODO: implement or reuse Resolver approach so it will work in node and browser envs
76
+ const requireFunc = (plugin: string | Plugin): Plugin | undefined => {
77
+ if (isBrowser && isString(plugin)) {
78
+ logger.error(`Cannot load ${plugin}. Plugins aren't supported in browser yet.`);
79
+
80
+ return undefined;
81
+ }
82
+
83
+ if (isString(plugin)) {
84
+ const absoltePluginPath = path.resolve(path.dirname(configPath), plugin);
85
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
86
+ // @ts-ignore
87
+ return typeof __webpack_require__ === 'function'
88
+ ? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
89
+ // @ts-ignore
90
+ __non_webpack_require__(absoltePluginPath)
91
+ : require(absoltePluginPath);
92
+ }
93
+
94
+ return plugin;
95
+ };
76
96
 
77
97
  const seenPluginIds = new Map<string, string>();
78
98
 
79
99
  return plugins
80
100
  .map((p) => {
81
101
  if (isString(p) && isAbsoluteUrl(p)) {
82
- throw new Error(red(`We don't support remote plugins yet.`));
102
+ throw new Error(colorize.red(`We don't support remote plugins yet.`));
83
103
  }
84
104
 
85
105
  // TODO: resolve npm packages similar to eslint
86
- const pluginModule = isString(p)
87
- ? (requireFunc(path.resolve(path.dirname(configPath), p)) as Plugin)
88
- : p;
106
+ const pluginModule = requireFunc(p);
107
+
108
+ if (!pluginModule) {
109
+ return;
110
+ }
89
111
 
90
112
  const id = pluginModule.id;
91
113
  if (typeof id !== 'string') {
92
- throw new Error(red(`Plugin must define \`id\` property in ${blue(p.toString())}.`));
114
+ throw new Error(
115
+ colorize.red(`Plugin must define \`id\` property in ${colorize.blue(p.toString())}.`)
116
+ );
93
117
  }
94
118
 
95
119
  if (seenPluginIds.has(id)) {
96
120
  const pluginPath = seenPluginIds.get(id)!;
97
121
  throw new Error(
98
- red(
99
- `Plugin "id" must be unique. Plugin ${blue(p.toString())} uses id "${blue(
100
- id
101
- )}" already seen in ${blue(pluginPath)}`
122
+ colorize.red(
123
+ `Plugin "id" must be unique. Plugin ${colorize.blue(
124
+ p.toString()
125
+ )} uses id "${colorize.blue(id)}" already seen in ${colorize.blue(pluginPath)}`
102
126
  )
103
127
  );
104
128
  }
@@ -153,7 +177,7 @@ export function resolvePlugins(
153
177
 
154
178
  return plugin;
155
179
  })
156
- .filter(notUndefined);
180
+ .filter(isDefined);
157
181
  }
158
182
 
159
183
  export async function resolveApis({
@@ -166,7 +190,7 @@ export async function resolveApis({
166
190
  resolver?: BaseResolver;
167
191
  }): Promise<Record<string, ResolvedApi>> {
168
192
  const { apis = {}, styleguide: styleguideConfig = {} } = rawConfig;
169
- let resolvedApis: Record<string, ResolvedApi> = {};
193
+ const resolvedApis: Record<string, ResolvedApi> = {};
170
194
  for (const [apiName, apiContent] of Object.entries(apis || {})) {
171
195
  if (apiContent.styleguide?.extends?.some(isNotString)) {
172
196
  throw new Error(
@@ -284,17 +308,19 @@ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedSt
284
308
  const { pluginId, configName } = parsePresetName(presetName);
285
309
  const plugin = plugins.find((p) => p.id === pluginId);
286
310
  if (!plugin) {
287
- throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
311
+ throw new Error(
312
+ `Invalid config ${colorize.red(presetName)}: plugin ${pluginId} is not included.`
313
+ );
288
314
  }
289
315
 
290
- const preset = plugin.configs?.[configName]! as ResolvedStyleguideConfig;
316
+ const preset = plugin.configs?.[configName];
291
317
  if (!preset) {
292
318
  throw new Error(
293
319
  pluginId
294
- ? `Invalid config ${red(
320
+ ? `Invalid config ${colorize.red(
295
321
  presetName
296
322
  )}: plugin ${pluginId} doesn't export config with name ${configName}.`
297
- : `Invalid config ${red(presetName)}: there is no such built-in config.`
323
+ : `Invalid config ${colorize.red(presetName)}: there is no such built-in config.`
298
324
  );
299
325
  }
300
326
  return preset;
@@ -4,6 +4,7 @@ import { parseYaml, stringifyYaml } from '../js-yaml';
4
4
  import { slash, doesYamlFileExist } from '../utils';
5
5
  import { NormalizedProblem } from '../walk';
6
6
  import { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
7
+ import { env } from '../env';
7
8
 
8
9
  import type { NodeType } from '../types';
9
10
  import type {
@@ -19,9 +20,6 @@ import type {
19
20
  } from './types';
20
21
  import { getResolveConfig } from './utils';
21
22
 
22
- // Alias environment here so this file can work in browser environments too.
23
- export const env = typeof process !== 'undefined' ? process.env || {} : {};
24
-
25
23
  export const IGNORE_FILE = '.redocly.lint-ignore.yaml';
26
24
  const IGNORE_BANNER =
27
25
  `# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n` +
@@ -172,9 +170,11 @@ export class StyleguideConfig {
172
170
  case OasVersion.Version3_1:
173
171
  if (!plugin.typeExtension.oas3) continue;
174
172
  extendedTypes = plugin.typeExtension.oas3(extendedTypes, version);
173
+ break;
175
174
  case OasVersion.Version2:
176
175
  if (!plugin.typeExtension.oas2) continue;
177
176
  extendedTypes = plugin.typeExtension.oas2(extendedTypes, version);
177
+ break;
178
178
  default:
179
179
  throw new Error('Not implemented');
180
180
  }
@@ -192,7 +192,7 @@ export class StyleguideConfig {
192
192
  severity: settings,
193
193
  };
194
194
  } else {
195
- return { severity: 'error' as 'error', ...settings };
195
+ return { severity: 'error', ...settings };
196
196
  }
197
197
  }
198
198
 
@@ -203,10 +203,10 @@ export class StyleguideConfig {
203
203
  const settings = this.preprocessors[oasVersion][ruleId] || 'off';
204
204
  if (typeof settings === 'string') {
205
205
  return {
206
- severity: settings === 'on' ? ('error' as 'error') : settings,
206
+ severity: settings === 'on' ? 'error' : settings,
207
207
  };
208
208
  } else {
209
- return { severity: 'error' as 'error', ...settings };
209
+ return { severity: 'error', ...settings };
210
210
  }
211
211
  }
212
212
 
@@ -216,10 +216,10 @@ export class StyleguideConfig {
216
216
  const settings = this.decorators[oasVersion][ruleId] || 'off';
217
217
  if (typeof settings === 'string') {
218
218
  return {
219
- severity: settings === 'on' ? ('error' as 'error') : settings,
219
+ severity: settings === 'on' ? 'error' : settings,
220
220
  };
221
221
  } else {
222
- return { severity: 'error' as 'error', ...settings };
222
+ return { severity: 'error', ...settings };
223
223
  }
224
224
  }
225
225
 
@@ -250,12 +250,14 @@ export class StyleguideConfig {
250
250
  getRulesForOasVersion(version: OasMajorVersion) {
251
251
  switch (version) {
252
252
  case OasMajorVersion.Version3:
253
+ // eslint-disable-next-line no-case-declarations
253
254
  const oas3Rules: Oas3RuleSet[] = []; // default ruleset
254
255
  this.plugins.forEach((p) => p.preprocessors?.oas3 && oas3Rules.push(p.preprocessors.oas3));
255
256
  this.plugins.forEach((p) => p.rules?.oas3 && oas3Rules.push(p.rules.oas3));
256
257
  this.plugins.forEach((p) => p.decorators?.oas3 && oas3Rules.push(p.decorators.oas3));
257
258
  return oas3Rules;
258
259
  case OasMajorVersion.Version2:
260
+ // eslint-disable-next-line no-case-declarations
259
261
  const oas2Rules: Oas2RuleSet[] = []; // default ruleset
260
262
  this.plugins.forEach((p) => p.preprocessors?.oas2 && oas2Rules.push(p.preprocessors.oas2));
261
263
  this.plugins.forEach((p) => p.rules?.oas2 && oas2Rules.push(p.rules.oas2));
@@ -2,20 +2,24 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { RedoclyClient } from '../redocly';
4
4
  import { isEmptyObject, loadYaml, doesYamlFileExist } from '../utils';
5
+ import { parseYaml } from '../js-yaml';
5
6
  import { Config, DOMAINS } from './config';
6
7
  import { transformConfig } from './utils';
7
8
  import { resolveConfig } from './config-resolvers';
8
9
 
9
- import type { DeprecatedInRawConfig, RawConfig, Region } from './types';
10
+ import type { DeprecatedInRawConfig, FlatRawConfig, RawConfig, Region } from './types';
11
+ import { RegionalTokenWithValidity } from '../redocly/redocly-client-types';
10
12
 
11
13
  async function addConfigMetadata({
12
14
  rawConfig,
13
15
  customExtends,
14
16
  configPath,
17
+ tokens,
15
18
  }: {
16
19
  rawConfig: RawConfig;
17
20
  customExtends?: string[];
18
21
  configPath?: string;
22
+ tokens?: RegionalTokenWithValidity[];
19
23
  }): Promise<Config> {
20
24
  if (customExtends !== undefined) {
21
25
  rawConfig.styleguide = rawConfig.styleguide || {};
@@ -25,10 +29,7 @@ async function addConfigMetadata({
25
29
  // rawConfig.styleguide = { extends: ['recommended'], recommendedFallback: true };
26
30
  }
27
31
 
28
- const redoclyClient = new RedoclyClient();
29
- const tokens = await redoclyClient.getTokens();
30
-
31
- if (tokens.length) {
32
+ if (tokens?.length) {
32
33
  if (!rawConfig.resolve) rawConfig.resolve = {};
33
34
  if (!rawConfig.resolve.http) rawConfig.resolve.http = {};
34
35
  rawConfig.resolve.http.headers = [...(rawConfig.resolve.http.headers ?? [])];
@@ -69,10 +70,15 @@ export async function loadConfig(
69
70
  if (typeof processRawConfig === 'function') {
70
71
  await processRawConfig(rawConfig);
71
72
  }
72
- return await addConfigMetadata({
73
+
74
+ const redoclyClient = new RedoclyClient();
75
+ const tokens = await redoclyClient.getTokens();
76
+
77
+ return addConfigMetadata({
73
78
  rawConfig,
74
79
  customExtends,
75
80
  configPath,
81
+ tokens,
76
82
  });
77
83
  }
78
84
 
@@ -96,9 +102,27 @@ export function findConfig(dir?: string): string | undefined {
96
102
  export async function getConfig(configPath: string | undefined = findConfig()): Promise<RawConfig> {
97
103
  if (!configPath || !doesYamlFileExist(configPath)) return {};
98
104
  try {
99
- const rawConfig = (await loadYaml<RawConfig & DeprecatedInRawConfig>(configPath)) || {};
105
+ const rawConfig =
106
+ (await loadYaml<RawConfig & DeprecatedInRawConfig & FlatRawConfig>(configPath)) || {};
100
107
  return transformConfig(rawConfig);
101
108
  } catch (e) {
102
109
  throw new Error(`Error parsing config file at '${configPath}': ${e.message}`);
103
110
  }
104
111
  }
112
+
113
+ type CreateConfigOptions = {
114
+ extends?: string[];
115
+ tokens?: RegionalTokenWithValidity[];
116
+ };
117
+
118
+ export async function createConfig(
119
+ config: string | RawConfig,
120
+ options?: CreateConfigOptions
121
+ ): Promise<Config> {
122
+ return addConfigMetadata({
123
+ rawConfig: transformConfig(
124
+ typeof config === 'string' ? (parseYaml(config) as RawConfig) : config
125
+ ),
126
+ ...options,
127
+ });
128
+ }
@@ -24,7 +24,7 @@ export default {
24
24
  'operation-operationId-unique': 'warn',
25
25
  'operation-parameters-unique': 'warn',
26
26
  'operation-tag-defined': 'off',
27
- 'operation-security-defined': 'warn',
27
+ 'security-defined': 'warn',
28
28
  'operation-operationId-url-safe': 'warn',
29
29
  'operation-singular-tag': 'off',
30
30
  'no-unresolved-refs': 'error',
@@ -36,7 +36,7 @@ export default {
36
36
  oas3_0Rules: {
37
37
  'no-invalid-media-type-examples': {
38
38
  severity: 'warn',
39
- disallowAdditionalProperties: true,
39
+ allowAdditionalProperties: false,
40
40
  },
41
41
  'no-server-example.com': 'warn',
42
42
  'no-server-trailing-slash': 'error',
@@ -44,15 +44,18 @@ export default {
44
44
  'no-example-value-and-externalValue': 'warn',
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'warn',
47
- 'no-servers-empty-enum': 'error',
47
+ 'no-server-variables-empty-enum': 'error',
48
+ 'spec-components-invalid-map-name': 'warn',
48
49
  },
49
50
  oas3_1Rules: {
51
+ 'no-invalid-media-type-examples': 'warn',
50
52
  'no-server-example.com': 'warn',
51
53
  'no-server-trailing-slash': 'error',
52
54
  'no-empty-servers': 'warn',
53
55
  'no-example-value-and-externalValue': 'warn',
54
56
  'no-unused-components': 'warn',
55
57
  'no-undefined-server-variable': 'warn',
56
- 'no-servers-empty-enum': 'error',
58
+ 'no-server-variables-empty-enum': 'error',
59
+ 'spec-components-invalid-map-name': 'warn',
57
60
  },
58
61
  } as PluginStyleguideConfig;
@@ -25,7 +25,7 @@ export default {
25
25
  'operation-operationId-url-safe': 'error',
26
26
  'operation-parameters-unique': 'error',
27
27
  'operation-tag-defined': 'off',
28
- 'operation-security-defined': 'error',
28
+ 'security-defined': 'error',
29
29
  'operation-singular-tag': 'off',
30
30
  'no-unresolved-refs': 'error',
31
31
  'no-enum-type-mismatch': 'error',
@@ -36,7 +36,7 @@ export default {
36
36
  oas3_0Rules: {
37
37
  'no-invalid-media-type-examples': {
38
38
  severity: 'warn',
39
- disallowAdditionalProperties: true,
39
+ allowAdditionalProperties: false,
40
40
  },
41
41
  'no-server-example.com': 'warn',
42
42
  'no-server-trailing-slash': 'error',
@@ -44,15 +44,18 @@ export default {
44
44
  'no-example-value-and-externalValue': 'error',
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'error',
47
- 'no-servers-empty-enum': 'error',
47
+ 'no-server-variables-empty-enum': 'error',
48
+ 'spec-components-invalid-map-name': 'error',
48
49
  },
49
50
  oas3_1Rules: {
51
+ 'no-invalid-media-type-examples': 'warn',
50
52
  'no-server-example.com': 'warn',
51
53
  'no-server-trailing-slash': 'error',
52
54
  'no-empty-servers': 'error',
53
55
  'no-example-value-and-externalValue': 'error',
54
56
  'no-unused-components': 'warn',
55
57
  'no-undefined-server-variable': 'error',
56
- 'no-servers-empty-enum': 'error',
58
+ 'no-server-variables-empty-enum': 'error',
59
+ 'spec-components-invalid-map-name': 'error',
57
60
  },
58
61
  } as PluginStyleguideConfig;
@@ -1,6 +1,6 @@
1
1
  import { RuleSet, OasVersion } from '../oas-types';
2
2
  import { StyleguideConfig } from './config';
3
- import { notUndefined } from '../utils';
3
+ import { isDefined } from '../utils';
4
4
 
5
5
  export function initRules<T extends Function, P extends RuleSet<T>>(
6
6
  rules: P[],
@@ -42,5 +42,5 @@ export function initRules<T extends Function, P extends RuleSet<T>>(
42
42
  })
43
43
  )
44
44
  .flatMap((visitor) => visitor)
45
- .filter(notUndefined);
45
+ .filter(isDefined);
46
46
  }
@@ -110,6 +110,7 @@ export type ResolveHeader =
110
110
 
111
111
  export type RawResolveConfig = {
112
112
  http?: Partial<HttpResolveConfig>;
113
+ doNotResolveExamples?: boolean;
113
114
  };
114
115
 
115
116
  export type HttpResolveConfig = {
@@ -128,6 +129,7 @@ export type AccessTokens = { [region in Region]?: string };
128
129
  export type DeprecatedInRawConfig = {
129
130
  apiDefinitions?: Record<string, string>;
130
131
  lint?: StyleguideRawConfig;
132
+ styleguide?: StyleguideRawConfig;
131
133
  referenceDocs?: Record<string, any>;
132
134
  apis?: Record<string, Api & DeprecatedInApi>;
133
135
  };
@@ -153,6 +155,15 @@ export type RawConfig = {
153
155
  organization?: string;
154
156
  } & FeaturesConfig;
155
157
 
158
+ export type FlatApi = Omit<Api, 'styleguide'> &
159
+ Omit<ApiStyleguideRawConfig, 'doNotResolveExamples'>;
160
+
161
+ export type FlatRawConfig = Omit<RawConfig, 'styleguide' | 'resolve' | 'apis'> &
162
+ Omit<StyleguideRawConfig, 'doNotResolveExamples'> & {
163
+ resolve?: RawResolveConfig;
164
+ apis?: Record<string, FlatApi>;
165
+ };
166
+
156
167
  export type ResolvedConfig = Omit<RawConfig, 'apis' | 'styleguide'> & {
157
168
  apis: Record<string, ResolvedApi>;
158
169
  styleguide: ResolvedStyleguideConfig;
@@ -1,18 +1,26 @@
1
- import { green, yellow } from 'colorette';
2
- import { assignExisting } from '../utils';
1
+ import {
2
+ assignExisting,
3
+ isDefined,
4
+ isTruthy,
5
+ showErrorForDeprecatedField,
6
+ showWarningForDeprecatedField,
7
+ } from '../utils';
3
8
  import { Config } from './config';
4
-
5
9
  import type {
6
10
  Api,
7
11
  DeprecatedInApi,
8
12
  DeprecatedInRawConfig,
13
+ FlatApi,
14
+ FlatRawConfig,
9
15
  Plugin,
10
16
  RawConfig,
11
17
  RawResolveConfig,
12
18
  ResolveConfig,
13
19
  ResolvedStyleguideConfig,
14
20
  RulesFields,
21
+ StyleguideRawConfig,
15
22
  } from './types';
23
+ import { logger, colorize } from '../logger';
16
24
 
17
25
  export function parsePresetName(presetName: string): { pluginId: string; configName: string } {
18
26
  if (presetName.indexOf('/') > -1) {
@@ -34,13 +42,86 @@ export function transformApiDefinitionsToApis(
34
42
  return apis;
35
43
  }
36
44
 
45
+ function extractFlatConfig<
46
+ T extends Partial<
47
+ (Api & DeprecatedInApi & FlatApi) & (DeprecatedInRawConfig & RawConfig & FlatRawConfig)
48
+ >
49
+ >({
50
+ plugins,
51
+ extends: _extends,
52
+
53
+ rules,
54
+ oas2Rules,
55
+ oas3_0Rules,
56
+ oas3_1Rules,
57
+
58
+ preprocessors,
59
+ oas2Preprocessors,
60
+ oas3_0Preprocessors,
61
+ oas3_1Preprocessors,
62
+
63
+ decorators,
64
+ oas2Decorators,
65
+ oas3_0Decorators,
66
+ oas3_1Decorators,
67
+
68
+ ...rawConfigRest
69
+ }: T): {
70
+ styleguideConfig?: StyleguideRawConfig;
71
+ rawConfigRest: Omit<T, 'plugins' | 'extends' | RulesFields>;
72
+ } {
73
+ const styleguideConfig = {
74
+ plugins,
75
+ extends: _extends,
76
+
77
+ rules,
78
+ oas2Rules,
79
+ oas3_0Rules,
80
+ oas3_1Rules,
81
+
82
+ preprocessors,
83
+ oas2Preprocessors,
84
+ oas3_0Preprocessors,
85
+ oas3_1Preprocessors,
86
+
87
+ decorators,
88
+ oas2Decorators,
89
+ oas3_0Decorators,
90
+ oas3_1Decorators,
91
+
92
+ doNotResolveExamples: (rawConfigRest as FlatRawConfig).resolve?.doNotResolveExamples,
93
+ };
94
+
95
+ if (
96
+ (rawConfigRest.lint && rawConfigRest.styleguide) ||
97
+ (Object.values(styleguideConfig).some(isDefined) &&
98
+ (rawConfigRest.lint || rawConfigRest.styleguide))
99
+ ) {
100
+ throw new Error(
101
+ `Do not use 'lint', 'styleguide' and flat syntax together. \nSee more about the configuration in the docs: https://redocly.com/docs/cli/configuration/ \n`
102
+ );
103
+ }
104
+
105
+ return {
106
+ styleguideConfig: Object.values(styleguideConfig).some(isDefined)
107
+ ? styleguideConfig
108
+ : undefined,
109
+
110
+ rawConfigRest,
111
+ };
112
+ }
113
+
37
114
  function transformApis(
38
- legacyApis?: Record<string, Api & DeprecatedInApi>
115
+ legacyApis?: Record<string, Api & DeprecatedInApi & FlatApi>
39
116
  ): Record<string, Api> | undefined {
40
117
  if (!legacyApis) return undefined;
41
118
  const apis: Record<string, Api> = {};
42
119
  for (const [apiName, { lint, ...apiContent }] of Object.entries(legacyApis)) {
43
- apis[apiName] = { styleguide: lint, ...apiContent };
120
+ const { styleguideConfig, rawConfigRest } = extractFlatConfig(apiContent);
121
+ apis[apiName] = {
122
+ styleguide: styleguideConfig || lint,
123
+ ...rawConfigRest,
124
+ };
44
125
  }
45
126
  return apis;
46
127
  }
@@ -79,7 +160,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
79
160
  extendPaths: [],
80
161
  };
81
162
 
82
- for (let rulesConf of rulesConfList) {
163
+ for (const rulesConf of rulesConfList) {
83
164
  if (rulesConf.extends) {
84
165
  throw new Error(
85
166
  `'extends' is not supported in shared configs yet: ${JSON.stringify(rulesConf, null, 2)}.`
@@ -124,14 +205,14 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
124
205
  config.rawConfig?.styleguide?.extendPaths,
125
206
  ]
126
207
  .flat()
127
- .filter(Boolean) as string[];
208
+ .filter(isTruthy);
128
209
 
129
210
  const pluginPaths = [
130
211
  ...Object.values(config.apis).map((api) => api?.styleguide?.pluginPaths),
131
212
  config.rawConfig?.styleguide?.pluginPaths,
132
213
  ]
133
214
  .flat()
134
- .filter(Boolean) as string[];
215
+ .filter(isTruthy);
135
216
 
136
217
  return apiName
137
218
  ? new Config(
@@ -160,34 +241,41 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
160
241
  }
161
242
 
162
243
  function checkForDeprecatedFields(
163
- deprecatedField: keyof DeprecatedInRawConfig,
164
- updatedField: keyof RawConfig,
165
- rawConfig: DeprecatedInRawConfig & RawConfig
244
+ deprecatedField: keyof (DeprecatedInRawConfig & RawConfig),
245
+ updatedField: keyof FlatRawConfig | undefined,
246
+ rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
166
247
  ): void {
167
248
  const isDeprecatedFieldInApis =
168
249
  rawConfig.apis &&
169
250
  Object.values(rawConfig.apis).some(
170
- (api: Api & DeprecatedInApi & DeprecatedInRawConfig) => api[deprecatedField]
251
+ (api: Api & FlatApi & DeprecatedInApi & DeprecatedInRawConfig & RawConfig & FlatRawConfig) =>
252
+ api[deprecatedField]
171
253
  );
172
254
 
173
- if (rawConfig[deprecatedField] && rawConfig[updatedField]) {
174
- throw new Error(`Do not use '${deprecatedField}' field. Use '${updatedField}' instead.\n`);
255
+ if (rawConfig[deprecatedField] && updatedField === null) {
256
+ showWarningForDeprecatedField(deprecatedField);
257
+ }
258
+
259
+ if (rawConfig[deprecatedField] && updatedField && rawConfig[updatedField]) {
260
+ showErrorForDeprecatedField(deprecatedField, updatedField);
175
261
  }
176
262
 
177
263
  if (rawConfig[deprecatedField] || isDeprecatedFieldInApis) {
178
- process.stderr.write(
179
- `The ${yellow(deprecatedField)} field is deprecated. Use ${green(
180
- updatedField
181
- )} instead. Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`
182
- );
264
+ showWarningForDeprecatedField(deprecatedField, updatedField);
183
265
  }
184
266
  }
185
267
 
186
- export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): RawConfig {
187
- const migratedFields: [keyof DeprecatedInRawConfig, keyof RawConfig][] = [
268
+ export function transformConfig(
269
+ rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
270
+ ): RawConfig {
271
+ const migratedFields: [
272
+ keyof (DeprecatedInRawConfig & RawConfig),
273
+ keyof FlatRawConfig | undefined
274
+ ][] = [
188
275
  ['apiDefinitions', 'apis'],
189
276
  ['referenceDocs', 'features.openapi'],
190
- ['lint', 'styleguide'], // TODO: update docs
277
+ ['lint', undefined],
278
+ ['styleguide', undefined],
191
279
  ];
192
280
 
193
281
  for (const [deprecatedField, updatedField] of migratedFields) {
@@ -196,11 +284,13 @@ export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): R
196
284
 
197
285
  const { apis, apiDefinitions, referenceDocs, lint, ...rest } = rawConfig;
198
286
 
287
+ const { styleguideConfig, rawConfigRest } = extractFlatConfig(rest);
288
+
199
289
  return {
200
290
  'features.openapi': referenceDocs,
201
291
  apis: transformApis(apis) || transformApiDefinitionsToApis(apiDefinitions),
202
- styleguide: lint,
203
- ...rest,
292
+ styleguide: styleguideConfig || lint,
293
+ ...rawConfigRest,
204
294
  };
205
295
  }
206
296
 
@@ -221,7 +311,7 @@ export function getUniquePlugins(plugins: Plugin[]): Plugin[] {
221
311
  results.push(p);
222
312
  seen.add(p.id);
223
313
  } else if (p.id) {
224
- process.stderr.write(`Duplicate plugin id "${yellow(p.id)}".\n`);
314
+ logger.warn(`Duplicate plugin id "${colorize.red(p.id)}".\n`);
225
315
  }
226
316
  }
227
317
  return results;
@@ -4,10 +4,10 @@ import { isRedoclyRegistryURL } from '../../redocly';
4
4
  import { Oas3Decorator, Oas2Decorator } from '../../visitors';
5
5
 
6
6
  export const RegistryDependencies: Oas3Decorator | Oas2Decorator = () => {
7
- let registryDependencies = new Set<string>();
7
+ const registryDependencies = new Set<string>();
8
8
 
9
9
  return {
10
- DefinitionRoot: {
10
+ Root: {
11
11
  leave(_: any, ctx: UserContext) {
12
12
  const data = ctx.getVisitorData();
13
13
  data.links = Array.from(registryDependencies);