@redocly/openapi-core 1.0.0-beta.94 → 1.0.0-beta.97

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 (123) hide show
  1. package/README.md +1 -1
  2. package/__tests__/bundle.test.ts +6 -6
  3. package/__tests__/fixtures/extension.js +1 -1
  4. package/__tests__/login.test.ts +2 -2
  5. package/__tests__/ref-utils.test.ts +1 -1
  6. package/__tests__/utils.ts +30 -18
  7. package/lib/benchmark/benches/recommended-oas3.bench.js +2 -3
  8. package/lib/benchmark/utils.d.ts +2 -1
  9. package/lib/benchmark/utils.js +10 -7
  10. package/lib/bundle.d.ts +2 -2
  11. package/lib/config/all.d.ts +2 -2
  12. package/lib/config/builtIn.d.ts +1 -1
  13. package/lib/config/config-resolvers.d.ts +16 -0
  14. package/lib/config/config-resolvers.js +242 -0
  15. package/lib/config/config.d.ts +18 -130
  16. package/lib/config/config.js +34 -245
  17. package/lib/config/index.d.ts +7 -0
  18. package/lib/config/index.js +19 -0
  19. package/lib/config/load.d.ts +2 -1
  20. package/lib/config/load.js +20 -13
  21. package/lib/config/minimal.d.ts +2 -2
  22. package/lib/config/recommended.d.ts +2 -2
  23. package/lib/config/types.d.ts +113 -0
  24. package/lib/config/types.js +2 -0
  25. package/lib/config/utils.d.ts +13 -0
  26. package/lib/config/utils.js +160 -0
  27. package/lib/format/format.d.ts +1 -1
  28. package/lib/format/format.js +30 -1
  29. package/lib/index.d.ts +1 -2
  30. package/lib/index.js +5 -6
  31. package/lib/lint.d.ts +1 -1
  32. package/lib/lint.js +5 -7
  33. package/lib/redocly/index.d.ts +1 -1
  34. package/lib/redocly/index.js +10 -26
  35. package/lib/redocly/redocly-client-types.d.ts +1 -1
  36. package/lib/redocly/registry-api-types.d.ts +1 -0
  37. package/lib/redocly/registry-api.d.ts +2 -2
  38. package/lib/redocly/registry-api.js +2 -1
  39. package/lib/resolve.d.ts +1 -1
  40. package/lib/resolve.js +1 -2
  41. package/lib/rules/common/assertions/index.js +1 -1
  42. package/lib/rules/common/assertions/utils.d.ts +1 -1
  43. package/lib/rules/common/assertions/utils.js +6 -2
  44. package/lib/utils.d.ts +4 -2
  45. package/lib/utils.js +20 -3
  46. package/package.json +9 -6
  47. package/src/__tests__/lint.test.ts +1 -1
  48. package/src/benchmark/benches/recommended-oas3.bench.ts +2 -3
  49. package/src/benchmark/benchmark.js +1 -1
  50. package/src/benchmark/utils.ts +13 -8
  51. package/src/bundle.ts +2 -1
  52. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +157 -0
  53. package/src/config/__tests__/config-resolvers.test.ts +429 -0
  54. package/src/config/__tests__/config.test.ts +17 -29
  55. package/src/config/__tests__/fixtures/plugin.js +1 -1
  56. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +12 -0
  57. package/src/config/__tests__/fixtures/resolve-config/api/plugin.js +67 -0
  58. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +8 -0
  59. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +19 -0
  60. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +10 -0
  61. package/src/config/__tests__/fixtures/resolve-config/plugin.js +66 -0
  62. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +4 -0
  63. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +5 -0
  64. package/src/config/__tests__/load.test.ts +8 -1
  65. package/src/config/all.ts +3 -2
  66. package/src/config/builtIn.ts +2 -1
  67. package/src/config/config-resolvers.ts +359 -0
  68. package/src/config/config.ts +60 -468
  69. package/src/config/index.ts +7 -0
  70. package/src/config/load.ts +37 -31
  71. package/src/config/minimal.ts +2 -2
  72. package/src/config/recommended.ts +2 -2
  73. package/src/config/types.ts +168 -0
  74. package/src/config/utils.ts +208 -0
  75. package/src/decorators/__tests__/remove-x-internal.test.ts +5 -5
  76. package/src/format/format.ts +38 -7
  77. package/src/index.ts +6 -2
  78. package/src/lint.ts +4 -5
  79. package/src/redocly/__tests__/redocly-client.test.ts +7 -0
  80. package/src/redocly/index.ts +14 -41
  81. package/src/redocly/redocly-client-types.ts +1 -1
  82. package/src/redocly/registry-api-types.ts +1 -0
  83. package/src/redocly/registry-api.ts +5 -1
  84. package/src/resolve.ts +2 -4
  85. package/src/rules/__tests__/no-unresolved-refs.test.ts +4 -4
  86. package/src/rules/common/__tests__/info-description.test.ts +3 -3
  87. package/src/rules/common/__tests__/info-license.test.ts +2 -2
  88. package/src/rules/common/__tests__/license-url.test.ts +2 -2
  89. package/src/rules/common/__tests__/no-ambiguous-paths.test.ts +1 -1
  90. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +5 -5
  91. package/src/rules/common/__tests__/no-identical-paths.test.ts +1 -1
  92. package/src/rules/common/__tests__/no-path-trailing-slash.test.ts +3 -3
  93. package/src/rules/common/__tests__/operation-2xx-response.test.ts +3 -3
  94. package/src/rules/common/__tests__/operation-4xx-response.test.ts +3 -3
  95. package/src/rules/common/__tests__/operation-operationId-unique.test.ts +2 -2
  96. package/src/rules/common/__tests__/operation-operationId-url-safe.test.ts +1 -1
  97. package/src/rules/common/__tests__/operation-parameters-unique.test.ts +4 -4
  98. package/src/rules/common/__tests__/operation-security-defined.test.ts +2 -2
  99. package/src/rules/common/__tests__/operation-singular-tag.test.ts +2 -2
  100. package/src/rules/common/__tests__/path-http-verbs-order.test.ts +2 -2
  101. package/src/rules/common/__tests__/path-not-include-query.test.ts +2 -2
  102. package/src/rules/common/__tests__/path-params-defined.test.ts +3 -3
  103. package/src/rules/common/__tests__/paths-kebab-case.test.ts +3 -3
  104. package/src/rules/common/__tests__/spec.test.ts +1 -1
  105. package/src/rules/common/__tests__/tag-description.test.ts +2 -2
  106. package/src/rules/common/__tests__/tags-alphabetical.test.ts +2 -2
  107. package/src/rules/common/assertions/index.ts +1 -1
  108. package/src/rules/common/assertions/utils.ts +5 -2
  109. package/src/rules/oas2/__tests__/boolean-parameter-prefixes.test.ts +3 -3
  110. package/src/rules/oas2/__tests__/spec/referenceableScalars.test.ts +1 -1
  111. package/src/rules/oas2/__tests__/spec/utils.ts +10 -7
  112. package/src/rules/oas3/__tests__/boolean-parameter-prefixes.test.ts +3 -3
  113. package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +6 -6
  114. package/src/rules/oas3/__tests__/no-example-value-and-externalValue.test.ts +2 -2
  115. package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +8 -8
  116. package/src/rules/oas3/__tests__/no-server-example.com.test.ts +2 -2
  117. package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +3 -3
  118. package/src/rules/oas3/__tests__/no-unused-components.test.ts +1 -1
  119. package/src/rules/oas3/__tests__/spec/referenceableScalars.test.ts +23 -14
  120. package/src/rules/oas3/__tests__/spec/spec.test.ts +4 -4
  121. package/src/rules/oas3/__tests__/spec/utils.ts +10 -7
  122. package/src/utils.ts +21 -4
  123. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,19 @@
1
+ lint:
2
+ rules:
3
+ no-invalid-media-type-examples: warn
4
+ operation-4xx-response: off
5
+ assert/tag-description:
6
+ subject: Tag
7
+ property: description
8
+ message: Tag description must be at least 13 characters and end with a full stop.
9
+ severity: error
10
+ minLength: 13
11
+ pattern: /\.$/
12
+ plugins:
13
+ - plugin.js
14
+ - api/plugin.js
15
+ extends:
16
+ - recommended
17
+ - api/nested-config.yaml
18
+ - test-plugin-nested/all
19
+ - test-plugin/all
@@ -0,0 +1,10 @@
1
+ lint:
2
+ plugins:
3
+ - plugin.js
4
+ extends:
5
+ - test-plugin/all
6
+ rules:
7
+ no-invalid-media-type-examples: error
8
+ operation-description: error
9
+ path-http-verbs-order: error
10
+ operation-2xx-response: off
@@ -0,0 +1,66 @@
1
+ const id = 'test-plugin';
2
+
3
+ /** @type {import('../../config').PreprocessorsConfig} */
4
+ const preprocessors = {
5
+ oas2: {
6
+ 'description-preprocessor': () => {
7
+ return {
8
+ Info(info) {
9
+ const title = info.title || 'API title';
10
+ info.description = `# ${title}\n\n${info.description || ''}`;
11
+ },
12
+ };
13
+ },
14
+ },
15
+ };
16
+
17
+ /** @type {import('../../config').CustomRulesConfig} */
18
+ const rules = {
19
+ oas3: {
20
+ 'openid-connect-url-well-known': () => {
21
+ return {
22
+ SecurityScheme(scheme, { location, report }) {
23
+ if (scheme.type === 'openIdConnect') {
24
+ if (scheme.openIdConnectUrl && !scheme.openIdConnectUrl.endsWith('/.well-known/openid-configuration')) {
25
+ report({
26
+ message:
27
+ 'openIdConnectUrl must be a URL that ends with /.well-known/openid-configuration',
28
+ location: location.child('openIdConnectUrl'),
29
+ });
30
+ }
31
+ }
32
+ },
33
+ };
34
+ },
35
+ },
36
+ };
37
+
38
+ /** @type {import('../../config').DecoratorsConfig} */
39
+ const decorators = {
40
+ oas3: {
41
+ 'inject-x-stats': () => {
42
+ return {
43
+ Info(info) {
44
+ info['x-stats'] = { test: 1 };
45
+ },
46
+ };
47
+ },
48
+ },
49
+ };
50
+
51
+ const configs = {
52
+ all: {
53
+ rules: {
54
+ 'local/operation-id-not-test': 'error',
55
+ 'boolean-parameter-prefixes': 'error',
56
+ },
57
+ },
58
+ };
59
+
60
+ module.exports = {
61
+ id,
62
+ preprocessors,
63
+ rules,
64
+ decorators,
65
+ configs,
66
+ };
@@ -0,0 +1,4 @@
1
+ lint:
2
+ rules:
3
+ operation-2xx-response: off
4
+ operation-4xx-response: error
@@ -0,0 +1,5 @@
1
+ lint:
2
+ extends:
3
+ - nested-remote-config.yaml
4
+ rules:
5
+ operation-2xx-response: error
@@ -1,4 +1,4 @@
1
- import { loadConfig, findConfig } from '../load';
1
+ import { loadConfig, findConfig, getConfig } from '../load';
2
2
  import { RedoclyClient } from '../../redocly';
3
3
 
4
4
  const fs = require('fs');
@@ -74,3 +74,10 @@ describe('findConfig', () => {
74
74
  expect(configName).toStrictEqual('dir/redocly.yaml');
75
75
  });
76
76
  });
77
+
78
+ describe('getConfig', () => {
79
+ jest.spyOn(fs, 'hasOwnProperty').mockImplementation(() => false);
80
+ it('should return empty object if there is no configPath and config file is not found', () => {
81
+ expect(getConfig()).toEqual(Promise.resolve({}));
82
+ });
83
+ });
package/src/config/all.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { LintRawConfig } from './config';
1
+ import type { PluginLintConfig } from "./types";
2
+
2
3
 
3
4
  export default {
4
5
  rules: {
@@ -61,4 +62,4 @@ export default {
61
62
  'no-undefined-server-variable': 'error',
62
63
  'no-servers-empty-enum': 'error',
63
64
  },
64
- } as LintRawConfig;
65
+ } as PluginLintConfig;
@@ -1,7 +1,6 @@
1
1
  import recommended from './recommended';
2
2
  import all from './all';
3
3
  import minimal from './minimal';
4
- import { CustomRulesConfig, LintRawConfig, Plugin } from './config';
5
4
  import { rules as oas3Rules } from '../rules/oas3';
6
5
  import { rules as oas2Rules } from '../rules/oas2';
7
6
  import { preprocessors as oas3Preprocessors } from '../rules/oas3';
@@ -9,6 +8,8 @@ import { preprocessors as oas2Preprocessors } from '../rules/oas2';
9
8
  import { decorators as oas3Decorators } from '../decorators/oas3';
10
9
  import { decorators as oas2Decorators } from '../decorators/oas2';
11
10
 
11
+ import type { CustomRulesConfig, LintRawConfig, Plugin } from './types';
12
+
12
13
  export const builtInConfigs: Record<string, LintRawConfig> = {
13
14
  recommended,
14
15
  minimal,
@@ -0,0 +1,359 @@
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 {
15
+ LintRawConfig,
16
+ Plugin,
17
+ RawConfig,
18
+ ResolvedApi,
19
+ ResolvedLintConfig,
20
+ RuleConfig,
21
+ } from './types';
22
+ import { isNotString, isString, notUndefined, parseYaml } from '../utils';
23
+ import { Config } from './config';
24
+
25
+ export async function resolveConfig(rawConfig: RawConfig, configPath?: string) {
26
+ if (rawConfig.lint?.extends?.some(isNotString)) {
27
+ throw new Error(
28
+ `Error configuration format not detected in extends value must contain strings`,
29
+ );
30
+ }
31
+
32
+ const resolver = new BaseResolver(getResolveConfig(rawConfig.resolve));
33
+ const configExtends = rawConfig?.lint?.extends ?? ['recommended'];
34
+ const recommendedFallback = !rawConfig?.lint?.extends;
35
+ const lintConfig = {
36
+ ...rawConfig?.lint,
37
+ extends: configExtends,
38
+ recommendedFallback,
39
+ };
40
+
41
+ const apis = await resolveApis({
42
+ rawConfig: {
43
+ ...rawConfig,
44
+ lint: lintConfig,
45
+ },
46
+ configPath,
47
+ resolver,
48
+ });
49
+
50
+ const lint = await resolveLint({
51
+ lintConfig,
52
+ configPath,
53
+ resolver,
54
+ });
55
+
56
+ return new Config(
57
+ {
58
+ ...rawConfig,
59
+ apis,
60
+ lint,
61
+ },
62
+ configPath,
63
+ );
64
+ }
65
+
66
+ export function resolvePlugins(
67
+ plugins: (string | Plugin)[] | null,
68
+ configPath: string = '',
69
+ ): Plugin[] {
70
+ if (!plugins) return [];
71
+
72
+ // @ts-ignore
73
+ const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require;
74
+
75
+ const seenPluginIds = new Map<string, string>();
76
+
77
+ return plugins
78
+ .map((p) => {
79
+ if (isString(p) && isAbsoluteUrl(p)) {
80
+ throw new Error(red(`We don't support remote plugins yet.`));
81
+ }
82
+
83
+ // TODO: resolve npm packages similar to eslint
84
+ const pluginModule = isString(p)
85
+ ? (requireFunc(path.resolve(path.dirname(configPath), p)) as Plugin)
86
+ : p;
87
+
88
+ const id = pluginModule.id;
89
+ if (typeof id !== 'string') {
90
+ throw new Error(red(`Plugin must define \`id\` property in ${blue(p.toString())}.`));
91
+ }
92
+
93
+ if (seenPluginIds.has(id)) {
94
+ const pluginPath = seenPluginIds.get(id)!;
95
+ throw new Error(
96
+ red(
97
+ `Plugin "id" must be unique. Plugin ${blue(p.toString())} uses id "${blue(
98
+ id,
99
+ )}" already seen in ${blue(pluginPath)}`,
100
+ ),
101
+ );
102
+ }
103
+
104
+ seenPluginIds.set(id, p.toString());
105
+
106
+ const plugin: Plugin = {
107
+ id,
108
+ ...(pluginModule.configs ? { configs: pluginModule.configs } : {}),
109
+ ...(pluginModule.typeExtension ? { typeExtension: pluginModule.typeExtension } : {}),
110
+ };
111
+
112
+ if (pluginModule.rules) {
113
+ if (!pluginModule.rules.oas3 && !pluginModule.rules.oas2) {
114
+ throw new Error(`Plugin rules must have \`oas3\` or \`oas2\` rules "${p}.`);
115
+ }
116
+ plugin.rules = {};
117
+ if (pluginModule.rules.oas3) {
118
+ plugin.rules.oas3 = prefixRules(pluginModule.rules.oas3, id);
119
+ }
120
+ if (pluginModule.rules.oas2) {
121
+ plugin.rules.oas2 = prefixRules(pluginModule.rules.oas2, id);
122
+ }
123
+ }
124
+ if (pluginModule.preprocessors) {
125
+ if (!pluginModule.preprocessors.oas3 && !pluginModule.preprocessors.oas2) {
126
+ throw new Error(
127
+ `Plugin \`preprocessors\` must have \`oas3\` or \`oas2\` preprocessors "${p}.`,
128
+ );
129
+ }
130
+ plugin.preprocessors = {};
131
+ if (pluginModule.preprocessors.oas3) {
132
+ plugin.preprocessors.oas3 = prefixRules(pluginModule.preprocessors.oas3, id);
133
+ }
134
+ if (pluginModule.preprocessors.oas2) {
135
+ plugin.preprocessors.oas2 = prefixRules(pluginModule.preprocessors.oas2, id);
136
+ }
137
+ }
138
+
139
+ if (pluginModule.decorators) {
140
+ if (!pluginModule.decorators.oas3 && !pluginModule.decorators.oas2) {
141
+ throw new Error(`Plugin \`decorators\` must have \`oas3\` or \`oas2\` decorators "${p}.`);
142
+ }
143
+ plugin.decorators = {};
144
+ if (pluginModule.decorators.oas3) {
145
+ plugin.decorators.oas3 = prefixRules(pluginModule.decorators.oas3, id);
146
+ }
147
+ if (pluginModule.decorators.oas2) {
148
+ plugin.decorators.oas2 = prefixRules(pluginModule.decorators.oas2, id);
149
+ }
150
+ }
151
+
152
+ return plugin;
153
+ })
154
+ .filter(notUndefined);
155
+ }
156
+
157
+ export async function resolveApis({
158
+ rawConfig,
159
+ configPath = '',
160
+ resolver,
161
+ }: {
162
+ rawConfig: RawConfig;
163
+ configPath?: string;
164
+ resolver?: BaseResolver;
165
+ }): Promise<Record<string, ResolvedApi>> {
166
+ const { apis = {}, lint: lintConfig = {} } = rawConfig;
167
+ let resolvedApis: Record<string, ResolvedApi> = {};
168
+ for (const [apiName, apiContent] of Object.entries(apis || {})) {
169
+ if (apiContent.lint?.extends?.some(isNotString)) {
170
+ throw new Error(
171
+ `Error configuration format not detected in extends value must contain strings`,
172
+ );
173
+ }
174
+ const rawLintConfig = getMergedLintRawConfig(lintConfig, apiContent.lint);
175
+ const apiLint = await resolveLint({
176
+ lintConfig: rawLintConfig,
177
+ configPath,
178
+ resolver,
179
+ });
180
+ resolvedApis[apiName] = { ...apiContent, lint: apiLint };
181
+ }
182
+ return resolvedApis;
183
+ }
184
+
185
+ async function resolveAndMergeNestedLint(
186
+ {
187
+ lintConfig,
188
+ configPath = '',
189
+ resolver = new BaseResolver(),
190
+ }: {
191
+ lintConfig?: LintRawConfig;
192
+ configPath?: string;
193
+ resolver?: BaseResolver;
194
+ },
195
+ parentConfigPaths: string[] = [],
196
+ extendPaths: string[] = [],
197
+ ): Promise<ResolvedLintConfig> {
198
+ if (parentConfigPaths.includes(configPath)) {
199
+ throw new Error(`Circular dependency in config file: "${configPath}"`);
200
+ }
201
+ const plugins = getUniquePlugins(
202
+ resolvePlugins([...(lintConfig?.plugins || []), defaultPlugin], configPath),
203
+ );
204
+ const pluginPaths = lintConfig?.plugins
205
+ ?.filter(isString)
206
+ .map((p) => path.resolve(path.dirname(configPath), p));
207
+
208
+ const resolvedConfigPath = isAbsoluteUrl(configPath)
209
+ ? configPath
210
+ : configPath && path.resolve(configPath);
211
+
212
+ const extendConfigs: ResolvedLintConfig[] = await Promise.all(
213
+ lintConfig?.extends?.map(async (presetItem) => {
214
+ if (!isAbsoluteUrl(presetItem) && !path.extname(presetItem)) {
215
+ return resolvePreset(presetItem, plugins);
216
+ }
217
+ const pathItem = isAbsoluteUrl(presetItem)
218
+ ? presetItem
219
+ : isAbsoluteUrl(configPath)
220
+ ? new URL(presetItem, configPath).href
221
+ : path.resolve(path.dirname(configPath), presetItem);
222
+ const extendedLintConfig = await loadExtendLintConfig(pathItem, resolver);
223
+ return await resolveAndMergeNestedLint(
224
+ {
225
+ lintConfig: extendedLintConfig,
226
+ configPath: pathItem,
227
+ resolver: resolver,
228
+ },
229
+ [...parentConfigPaths, resolvedConfigPath],
230
+ extendPaths,
231
+ );
232
+ }) || [],
233
+ );
234
+
235
+ const { plugins: mergedPlugins = [], ...lint } = mergeExtends([
236
+ ...extendConfigs,
237
+ {
238
+ ...lintConfig,
239
+ plugins,
240
+ extends: undefined,
241
+ extendPaths: [...parentConfigPaths, resolvedConfigPath],
242
+ pluginPaths,
243
+ },
244
+ ]);
245
+
246
+ return {
247
+ ...lint,
248
+ extendPaths: lint.extendPaths?.filter((path) => path && !isAbsoluteUrl(path)),
249
+ plugins: getUniquePlugins(mergedPlugins),
250
+ recommendedFallback: lintConfig?.recommendedFallback,
251
+ doNotResolveExamples: lintConfig?.doNotResolveExamples,
252
+ };
253
+ }
254
+
255
+ export async function resolveLint(
256
+ lintOpts: {
257
+ lintConfig?: LintRawConfig;
258
+ configPath?: string;
259
+ resolver?: BaseResolver;
260
+ },
261
+ parentConfigPaths: string[] = [],
262
+ extendPaths: string[] = [],
263
+ ): Promise<ResolvedLintConfig> {
264
+ const resolvedLint = await resolveAndMergeNestedLint(lintOpts, parentConfigPaths, extendPaths);
265
+
266
+ return {
267
+ ...resolvedLint,
268
+ rules: resolvedLint.rules && groupLintAssertionRules(resolvedLint.rules),
269
+ };
270
+ }
271
+
272
+ export function resolvePreset(presetName: string, plugins: Plugin[]): ResolvedLintConfig {
273
+ const { pluginId, configName } = parsePresetName(presetName);
274
+ const plugin = plugins.find((p) => p.id === pluginId);
275
+ if (!plugin) {
276
+ throw new Error(`Invalid config ${red(presetName)}: plugin ${pluginId} is not included.`);
277
+ }
278
+
279
+ const preset = plugin.configs?.[configName]! as ResolvedLintConfig;
280
+ if (!preset) {
281
+ throw new Error(
282
+ pluginId
283
+ ? `Invalid config ${red(
284
+ presetName,
285
+ )}: plugin ${pluginId} doesn't export config with name ${configName}.`
286
+ : `Invalid config ${red(presetName)}: there is no such built-in config.`,
287
+ );
288
+ }
289
+ return preset;
290
+ }
291
+
292
+ async function loadExtendLintConfig(
293
+ filePath: string,
294
+ resolver: BaseResolver,
295
+ ): Promise<LintRawConfig> {
296
+ try {
297
+ const fileSource = await resolver.loadExternalRef(filePath);
298
+ const rawConfig = transformConfig(parseYaml(fileSource.body) as RawConfig);
299
+ if (!rawConfig.lint) {
300
+ throw new Error(`Lint configuration format not detected: "${filePath}"`);
301
+ }
302
+
303
+ return rawConfig.lint;
304
+ } catch (error) {
305
+ throw new Error(`Failed to load "${filePath}": ${error.message}`);
306
+ }
307
+ }
308
+
309
+ function getMergedLintRawConfig(configLint: LintRawConfig, apiLint?: LintRawConfig) {
310
+ const resultLint = {
311
+ ...configLint,
312
+ ...apiLint,
313
+ rules: { ...configLint?.rules, ...apiLint?.rules },
314
+ oas2Rules: { ...configLint?.oas2Rules, ...apiLint?.oas2Rules },
315
+ oas3_0Rules: { ...configLint?.oas3_0Rules, ...apiLint?.oas3_0Rules },
316
+ oas3_1Rules: { ...configLint?.oas3_1Rules, ...apiLint?.oas3_1Rules },
317
+ preprocessors: { ...configLint?.preprocessors, ...apiLint?.preprocessors },
318
+ oas2Preprocessors: { ...configLint?.oas2Preprocessors, ...apiLint?.oas2Preprocessors },
319
+ oas3_0Preprocessors: { ...configLint?.oas3_0Preprocessors, ...apiLint?.oas3_0Preprocessors },
320
+ oas3_1Preprocessors: { ...configLint?.oas3_1Preprocessors, ...apiLint?.oas3_1Preprocessors },
321
+ decorators: { ...configLint?.decorators, ...apiLint?.decorators },
322
+ oas2Decorators: { ...configLint?.oas2Decorators, ...apiLint?.oas2Decorators },
323
+ oas3_0Decorators: { ...configLint?.oas3_0Decorators, ...apiLint?.oas3_0Decorators },
324
+ oas3_1Decorators: { ...configLint?.oas3_1Decorators, ...apiLint?.oas3_1Decorators },
325
+ recommendedFallback: apiLint?.extends ? false : configLint.recommendedFallback,
326
+ };
327
+ return resultLint;
328
+ }
329
+
330
+ function groupLintAssertionRules(
331
+ rules: Record<string, RuleConfig> | undefined,
332
+ ): Record<string, RuleConfig> | undefined {
333
+ if (!rules) {
334
+ return rules;
335
+ }
336
+
337
+ // Create a new record to avoid mutating original
338
+ const transformedRules: Record<string, RuleConfig> = {};
339
+
340
+ // Collect assertion rules
341
+ const assertions = [];
342
+ for (const [ruleKey, rule] of Object.entries(rules)) {
343
+ if (ruleKey.startsWith('assert/') && typeof rule === 'object' && rule !== null) {
344
+ const assertion = rule;
345
+ assertions.push({
346
+ ...assertion,
347
+ assertionId: ruleKey.replace('assert/', ''),
348
+ });
349
+ } else {
350
+ // If it's not an assertion, keep it as is
351
+ transformedRules[ruleKey] = rule;
352
+ }
353
+ }
354
+ if (assertions.length > 0) {
355
+ transformedRules.assertions = assertions;
356
+ }
357
+
358
+ return transformedRules;
359
+ }