@redocly/openapi-core 1.0.0-beta.92 → 1.0.0-beta.95

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 (122) hide show
  1. package/__tests__/bundle.test.ts +6 -6
  2. package/__tests__/fixtures/extension.js +1 -1
  3. package/__tests__/login.test.ts +2 -2
  4. package/__tests__/normalizeVisitors.test.ts +1 -1
  5. package/__tests__/ref-utils.test.ts +1 -1
  6. package/__tests__/utils.ts +30 -18
  7. package/__tests__/walk.test.ts +6 -6
  8. package/lib/benchmark/benches/recommended-oas3.bench.js +2 -3
  9. package/lib/benchmark/utils.d.ts +2 -1
  10. package/lib/benchmark/utils.js +10 -7
  11. package/lib/bundle.d.ts +2 -2
  12. package/lib/config/all.d.ts +2 -2
  13. package/lib/config/builtIn.d.ts +1 -1
  14. package/lib/config/config-resolvers.d.ts +16 -0
  15. package/lib/config/config-resolvers.js +213 -0
  16. package/lib/config/config.d.ts +14 -129
  17. package/lib/config/config.js +17 -234
  18. package/lib/config/index.d.ts +7 -0
  19. package/lib/config/index.js +19 -0
  20. package/lib/config/load.d.ts +2 -1
  21. package/lib/config/load.js +20 -13
  22. package/lib/config/minimal.d.ts +2 -2
  23. package/lib/config/recommended.d.ts +2 -2
  24. package/lib/config/types.d.ts +113 -0
  25. package/lib/config/types.js +2 -0
  26. package/lib/config/utils.d.ts +13 -0
  27. package/lib/config/utils.js +160 -0
  28. package/lib/format/format.d.ts +1 -1
  29. package/lib/format/format.js +28 -0
  30. package/lib/index.d.ts +1 -2
  31. package/lib/index.js +5 -6
  32. package/lib/lint.d.ts +1 -1
  33. package/lib/lint.js +5 -7
  34. package/lib/redocly/index.d.ts +1 -1
  35. package/lib/redocly/redocly-client-types.d.ts +1 -1
  36. package/lib/redocly/registry-api.d.ts +1 -1
  37. package/lib/resolve.d.ts +1 -1
  38. package/lib/resolve.js +1 -2
  39. package/lib/rules/common/assertions/utils.d.ts +1 -1
  40. package/lib/rules/common/assertions/utils.js +6 -2
  41. package/lib/types/index.js +2 -2
  42. package/lib/types/oas3_1.js +31 -5
  43. package/lib/utils.d.ts +4 -1
  44. package/lib/utils.js +18 -1
  45. package/package.json +5 -2
  46. package/src/__tests__/lint.test.ts +1 -1
  47. package/src/benchmark/benches/recommended-oas3.bench.ts +2 -3
  48. package/src/benchmark/utils.ts +13 -8
  49. package/src/bundle.ts +2 -1
  50. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +140 -0
  51. package/src/config/__tests__/config-resolvers.test.ts +398 -0
  52. package/src/config/__tests__/config.test.ts +244 -0
  53. package/src/config/__tests__/fixtures/plugin.js +1 -1
  54. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +7 -0
  55. package/src/config/__tests__/fixtures/resolve-config/api/plugin.js +67 -0
  56. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +8 -0
  57. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +12 -0
  58. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +10 -0
  59. package/src/config/__tests__/fixtures/resolve-config/plugin.js +66 -0
  60. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +4 -0
  61. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +5 -0
  62. package/src/config/__tests__/load.test.ts +8 -1
  63. package/src/config/all.ts +3 -2
  64. package/src/config/builtIn.ts +2 -1
  65. package/src/config/config-resolvers.ts +304 -0
  66. package/src/config/config.ts +40 -454
  67. package/src/config/index.ts +7 -0
  68. package/src/config/load.ts +37 -31
  69. package/src/config/minimal.ts +2 -2
  70. package/src/config/recommended.ts +2 -2
  71. package/src/config/types.ts +168 -0
  72. package/src/config/utils.ts +208 -0
  73. package/src/decorators/__tests__/remove-x-internal.test.ts +5 -5
  74. package/src/format/format.ts +36 -6
  75. package/src/index.ts +6 -2
  76. package/src/lint.ts +4 -5
  77. package/src/redocly/__tests__/redocly-client.test.ts +7 -0
  78. package/src/redocly/index.ts +3 -1
  79. package/src/redocly/redocly-client-types.ts +1 -1
  80. package/src/redocly/registry-api.ts +3 -1
  81. package/src/resolve.ts +2 -4
  82. package/src/rules/__tests__/no-unresolved-refs.test.ts +4 -4
  83. package/src/rules/common/__tests__/info-description.test.ts +3 -3
  84. package/src/rules/common/__tests__/info-license.test.ts +2 -2
  85. package/src/rules/common/__tests__/license-url.test.ts +2 -2
  86. package/src/rules/common/__tests__/no-ambiguous-paths.test.ts +1 -1
  87. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +5 -5
  88. package/src/rules/common/__tests__/no-identical-paths.test.ts +1 -1
  89. package/src/rules/common/__tests__/no-path-trailing-slash.test.ts +3 -3
  90. package/src/rules/common/__tests__/operation-2xx-response.test.ts +3 -3
  91. package/src/rules/common/__tests__/operation-4xx-response.test.ts +3 -3
  92. package/src/rules/common/__tests__/operation-operationId-unique.test.ts +2 -2
  93. package/src/rules/common/__tests__/operation-operationId-url-safe.test.ts +1 -1
  94. package/src/rules/common/__tests__/operation-parameters-unique.test.ts +4 -4
  95. package/src/rules/common/__tests__/operation-security-defined.test.ts +2 -2
  96. package/src/rules/common/__tests__/operation-singular-tag.test.ts +2 -2
  97. package/src/rules/common/__tests__/path-http-verbs-order.test.ts +2 -2
  98. package/src/rules/common/__tests__/path-not-include-query.test.ts +2 -2
  99. package/src/rules/common/__tests__/path-params-defined.test.ts +3 -3
  100. package/src/rules/common/__tests__/paths-kebab-case.test.ts +3 -3
  101. package/src/rules/common/__tests__/spec.test.ts +1 -1
  102. package/src/rules/common/__tests__/tag-description.test.ts +2 -2
  103. package/src/rules/common/__tests__/tags-alphabetical.test.ts +2 -2
  104. package/src/rules/common/assertions/utils.ts +5 -2
  105. package/src/rules/oas2/__tests__/boolean-parameter-prefixes.test.ts +3 -3
  106. package/src/rules/oas2/__tests__/spec/referenceableScalars.test.ts +1 -1
  107. package/src/rules/oas2/__tests__/spec/utils.ts +10 -7
  108. package/src/rules/oas3/__tests__/boolean-parameter-prefixes.test.ts +3 -3
  109. package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +6 -6
  110. package/src/rules/oas3/__tests__/no-example-value-and-externalValue.test.ts +2 -2
  111. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +8 -8
  112. package/src/rules/oas3/__tests__/no-server-example.com.test.ts +2 -2
  113. package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +3 -3
  114. package/src/rules/oas3/__tests__/no-unused-components.test.ts +1 -1
  115. package/src/rules/oas3/__tests__/spec/referenceableScalars.test.ts +23 -14
  116. package/src/rules/oas3/__tests__/spec/servers.test.ts +1 -1
  117. package/src/rules/oas3/__tests__/spec/spec.test.ts +4 -4
  118. package/src/rules/oas3/__tests__/spec/utils.ts +10 -7
  119. package/src/types/index.ts +2 -2
  120. package/src/types/oas3_1.ts +32 -7
  121. package/src/utils.ts +18 -2
  122. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,304 @@
1
+ import * as path from 'path';
2
+ import { blue, red } from 'colorette';
3
+ import { isAbsoluteUrl } from '../ref-utils';
4
+ import { BaseResolver } from '../resolve';
5
+ import { defaultPlugin } from './builtIn';
6
+ import {
7
+ getResolveConfig,
8
+ getUniquePlugins,
9
+ mergeExtends,
10
+ parsePresetName,
11
+ prefixRules,
12
+ transformConfig,
13
+ } from './utils';
14
+ import type { LintRawConfig, Plugin, RawConfig, ResolvedApi, ResolvedLintConfig } from './types';
15
+ import { isNotString, isString, notUndefined, parseYaml } from '../utils';
16
+ import { Config } from './config';
17
+
18
+ export async function resolveConfig(rawConfig: RawConfig, configPath?: string) {
19
+ if (rawConfig.lint?.extends?.some(isNotString)) {
20
+ throw new Error(
21
+ `Error configuration format not detected in extends value must contain strings`,
22
+ );
23
+ }
24
+
25
+ const resolver = new BaseResolver(getResolveConfig(rawConfig.resolve));
26
+ const configExtends = rawConfig?.lint?.extends ?? ['recommended'];
27
+ const recommendedFallback = !rawConfig?.lint?.extends;
28
+ const lintConfig = {
29
+ ...rawConfig?.lint,
30
+ extends: configExtends,
31
+ recommendedFallback,
32
+ };
33
+
34
+ const apis = await resolveApis({
35
+ rawConfig: {
36
+ ...rawConfig,
37
+ lint: lintConfig,
38
+ },
39
+ configPath,
40
+ resolver,
41
+ });
42
+
43
+ const lint = await resolveLint({
44
+ lintConfig,
45
+ configPath,
46
+ resolver,
47
+ });
48
+
49
+ return new Config(
50
+ {
51
+ ...rawConfig,
52
+ apis,
53
+ lint,
54
+ },
55
+ configPath,
56
+ );
57
+ }
58
+
59
+ export function resolvePlugins(
60
+ plugins: (string | Plugin)[] | null,
61
+ configPath: string = '',
62
+ ): Plugin[] {
63
+ if (!plugins) return [];
64
+
65
+ // @ts-ignore
66
+ const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
67
+
68
+ const seenPluginIds = new Map<string, string>();
69
+
70
+ return plugins
71
+ .map((p) => {
72
+ if (isString(p) && isAbsoluteUrl(p)) {
73
+ throw new Error(red(`We don't support remote plugins yet.`));
74
+ }
75
+
76
+ // TODO: resolve npm packages similar to eslint
77
+ const pluginModule = isString(p)
78
+ ? (requireFunc(path.resolve(path.dirname(configPath), p)) as Plugin)
79
+ : p;
80
+
81
+ const id = pluginModule.id;
82
+ if (typeof id !== 'string') {
83
+ throw new Error(red(`Plugin must define \`id\` property in ${blue(p.toString())}.`));
84
+ }
85
+
86
+ if (seenPluginIds.has(id)) {
87
+ const pluginPath = seenPluginIds.get(id)!;
88
+ throw new Error(
89
+ red(
90
+ `Plugin "id" must be unique. Plugin ${blue(p.toString())} uses id "${blue(
91
+ id,
92
+ )}" already seen in ${blue(pluginPath)}`,
93
+ ),
94
+ );
95
+ }
96
+
97
+ seenPluginIds.set(id, p.toString());
98
+
99
+ const plugin: Plugin = {
100
+ id,
101
+ ...(pluginModule.configs ? { configs: pluginModule.configs } : {}),
102
+ ...(pluginModule.typeExtension ? { typeExtension: pluginModule.typeExtension } : {}),
103
+ };
104
+
105
+ if (pluginModule.rules) {
106
+ if (!pluginModule.rules.oas3 && !pluginModule.rules.oas2) {
107
+ throw new Error(`Plugin rules must have \`oas3\` or \`oas2\` rules "${p}.`);
108
+ }
109
+ plugin.rules = {};
110
+ if (pluginModule.rules.oas3) {
111
+ plugin.rules.oas3 = prefixRules(pluginModule.rules.oas3, id);
112
+ }
113
+ if (pluginModule.rules.oas2) {
114
+ plugin.rules.oas2 = prefixRules(pluginModule.rules.oas2, id);
115
+ }
116
+ }
117
+ if (pluginModule.preprocessors) {
118
+ if (!pluginModule.preprocessors.oas3 && !pluginModule.preprocessors.oas2) {
119
+ throw new Error(
120
+ `Plugin \`preprocessors\` must have \`oas3\` or \`oas2\` preprocessors "${p}.`,
121
+ );
122
+ }
123
+ plugin.preprocessors = {};
124
+ if (pluginModule.preprocessors.oas3) {
125
+ plugin.preprocessors.oas3 = prefixRules(pluginModule.preprocessors.oas3, id);
126
+ }
127
+ if (pluginModule.preprocessors.oas2) {
128
+ plugin.preprocessors.oas2 = prefixRules(pluginModule.preprocessors.oas2, id);
129
+ }
130
+ }
131
+
132
+ if (pluginModule.decorators) {
133
+ if (!pluginModule.decorators.oas3 && !pluginModule.decorators.oas2) {
134
+ throw new Error(`Plugin \`decorators\` must have \`oas3\` or \`oas2\` decorators "${p}.`);
135
+ }
136
+ plugin.decorators = {};
137
+ if (pluginModule.decorators.oas3) {
138
+ plugin.decorators.oas3 = prefixRules(pluginModule.decorators.oas3, id);
139
+ }
140
+ if (pluginModule.decorators.oas2) {
141
+ plugin.decorators.oas2 = prefixRules(pluginModule.decorators.oas2, id);
142
+ }
143
+ }
144
+
145
+ return plugin;
146
+ })
147
+ .filter(notUndefined);
148
+ }
149
+
150
+ export async function resolveApis({
151
+ rawConfig,
152
+ configPath = '',
153
+ resolver,
154
+ }: {
155
+ rawConfig: RawConfig;
156
+ configPath?: string;
157
+ resolver?: BaseResolver;
158
+ }): Promise<Record<string, ResolvedApi>> {
159
+ const { apis = {}, lint: lintConfig = {} } = rawConfig;
160
+ let resolvedApis: Record<string, ResolvedApi> = {};
161
+ for (const [apiName, apiContent] of Object.entries(apis || {})) {
162
+ if (apiContent.lint?.extends?.some(isNotString)) {
163
+ throw new Error(
164
+ `Error configuration format not detected in extends value must contain strings`,
165
+ );
166
+ }
167
+ const rawLintConfig = getMergedLintRawConfig(lintConfig, apiContent.lint);
168
+ const apiLint = await resolveLint({
169
+ lintConfig: rawLintConfig,
170
+ configPath,
171
+ resolver,
172
+ });
173
+ resolvedApis[apiName] = { ...apiContent, lint: apiLint };
174
+ }
175
+ return resolvedApis;
176
+ }
177
+
178
+ export async function resolveLint(
179
+ {
180
+ lintConfig,
181
+ configPath = '',
182
+ resolver = new BaseResolver(),
183
+ }: {
184
+ lintConfig?: LintRawConfig;
185
+ configPath?: string;
186
+ resolver?: BaseResolver;
187
+ },
188
+ parentConfigPaths: string[] = [],
189
+ extendPaths: string[] = [],
190
+ ): Promise<ResolvedLintConfig> {
191
+ if (parentConfigPaths.includes(configPath)) {
192
+ throw new Error(`Circular dependency in config file: "${configPath}"`);
193
+ }
194
+ const plugins = getUniquePlugins(
195
+ resolvePlugins([...(lintConfig?.plugins || []), defaultPlugin], configPath),
196
+ );
197
+ const pluginPaths = lintConfig?.plugins
198
+ ?.filter(isString)
199
+ .map((p) => path.resolve(path.dirname(configPath), p));
200
+
201
+ const resolvedConfigPath = isAbsoluteUrl(configPath)
202
+ ? configPath
203
+ : configPath && path.resolve(configPath);
204
+
205
+ const extendConfigs: ResolvedLintConfig[] = await Promise.all(
206
+ lintConfig?.extends?.map(async (presetItem) => {
207
+ if (!isAbsoluteUrl(presetItem) && !path.extname(presetItem)) {
208
+ return resolvePreset(presetItem, plugins);
209
+ }
210
+ const pathItem = isAbsoluteUrl(presetItem)
211
+ ? presetItem
212
+ : isAbsoluteUrl(configPath)
213
+ ? new URL(presetItem, configPath).href
214
+ : path.resolve(path.dirname(configPath), presetItem);
215
+ const extendedLintConfig = await loadExtendLintConfig(pathItem, resolver);
216
+ return await resolveLint(
217
+ {
218
+ lintConfig: extendedLintConfig,
219
+ configPath: pathItem,
220
+ resolver: resolver,
221
+ },
222
+ [...parentConfigPaths, resolvedConfigPath],
223
+ extendPaths,
224
+ );
225
+ }) || [],
226
+ );
227
+
228
+ const { plugins: mergedPlugins = [], ...lint } = mergeExtends([
229
+ ...extendConfigs,
230
+ {
231
+ ...lintConfig,
232
+ plugins,
233
+ extends: undefined,
234
+ extendPaths: [...parentConfigPaths, resolvedConfigPath],
235
+ pluginPaths,
236
+ },
237
+ ]);
238
+
239
+ return {
240
+ ...lint,
241
+ extendPaths: lint.extendPaths?.filter((path) => path && !isAbsoluteUrl(path)),
242
+ plugins: getUniquePlugins(mergedPlugins),
243
+ recommendedFallback: lintConfig?.recommendedFallback,
244
+ doNotResolveExamples: lintConfig?.doNotResolveExamples,
245
+ };
246
+ }
247
+
248
+ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedLintConfig {
249
+ const { pluginId, configName } = parsePresetName(presetName);
250
+ const plugin = plugins.find((p) => p.id === pluginId);
251
+ if (!plugin) {
252
+ throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
253
+ }
254
+
255
+ const preset = plugin.configs?.[configName]! as ResolvedLintConfig;
256
+ if (!preset) {
257
+ throw new Error(
258
+ pluginId
259
+ ? `Invalid config ${red(
260
+ presetName,
261
+ )}: plugin ${pluginId} doesn't export config with name ${configName}.`
262
+ : `Invalid config ${red(presetName)}: there is no such built-in config.`,
263
+ );
264
+ }
265
+ return preset;
266
+ }
267
+
268
+ async function loadExtendLintConfig(
269
+ filePath: string,
270
+ resolver: BaseResolver,
271
+ ): Promise<LintRawConfig> {
272
+ try {
273
+ const fileSource = await resolver.loadExternalRef(filePath);
274
+ const rawConfig = transformConfig(parseYaml(fileSource.body) as RawConfig);
275
+ if (!rawConfig.lint) {
276
+ throw new Error(`Lint configuration format not detected: "${filePath}"`);
277
+ }
278
+
279
+ return rawConfig.lint;
280
+ } catch (error) {
281
+ throw new Error(`Failed to load "${filePath}": ${error.message}`);
282
+ }
283
+ }
284
+
285
+ function getMergedLintRawConfig(configLint: LintRawConfig, apiLint?: LintRawConfig) {
286
+ const resultLint = {
287
+ ...configLint,
288
+ ...apiLint,
289
+ rules: { ...configLint?.rules, ...apiLint?.rules },
290
+ oas2Rules: { ...configLint?.oas2Rules, ...apiLint?.oas2Rules },
291
+ oas3_0Rules: { ...configLint?.oas3_0Rules, ...apiLint?.oas3_0Rules },
292
+ oas3_1Rules: { ...configLint?.oas3_1Rules, ...apiLint?.oas3_1Rules },
293
+ preprocessors: { ...configLint?.preprocessors, ...apiLint?.preprocessors },
294
+ oas2Preprocessors: { ...configLint?.oas2Preprocessors, ...apiLint?.oas2Preprocessors },
295
+ oas3_0Preprocessors: { ...configLint?.oas3_0Preprocessors, ...apiLint?.oas3_0Preprocessors },
296
+ oas3_1Preprocessors: { ...configLint?.oas3_1Preprocessors, ...apiLint?.oas3_1Preprocessors },
297
+ decorators: { ...configLint?.decorators, ...apiLint?.decorators },
298
+ oas2Decorators: { ...configLint?.oas2Decorators, ...apiLint?.oas2Decorators },
299
+ oas3_0Decorators: { ...configLint?.oas3_0Decorators, ...apiLint?.oas3_0Decorators },
300
+ oas3_1Decorators: { ...configLint?.oas3_1Decorators, ...apiLint?.oas3_1Decorators },
301
+ recommendedFallback: apiLint?.extends ? false : configLint.recommendedFallback,
302
+ };
303
+ return resultLint;
304
+ }