@redocly/openapi-core 1.0.0-beta.93 → 1.0.0-beta.96
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.
- package/README.md +1 -1
- package/__tests__/bundle.test.ts +6 -6
- package/__tests__/fixtures/extension.js +1 -1
- package/__tests__/login.test.ts +2 -2
- package/__tests__/normalizeVisitors.test.ts +1 -1
- package/__tests__/ref-utils.test.ts +1 -1
- package/__tests__/utils.ts +30 -18
- package/__tests__/walk.test.ts +6 -6
- package/lib/benchmark/benches/recommended-oas3.bench.js +2 -3
- package/lib/benchmark/utils.d.ts +2 -1
- package/lib/benchmark/utils.js +10 -7
- package/lib/bundle.d.ts +2 -2
- package/lib/config/all.d.ts +2 -2
- package/lib/config/builtIn.d.ts +1 -1
- package/lib/config/config-resolvers.d.ts +16 -0
- package/lib/config/config-resolvers.js +213 -0
- package/lib/config/config.d.ts +14 -129
- package/lib/config/config.js +17 -234
- package/lib/config/index.d.ts +7 -0
- package/lib/config/index.js +19 -0
- package/lib/config/load.d.ts +2 -1
- package/lib/config/load.js +20 -13
- package/lib/config/minimal.d.ts +2 -2
- package/lib/config/recommended.d.ts +2 -2
- package/lib/config/types.d.ts +113 -0
- package/lib/config/types.js +2 -0
- package/lib/config/utils.d.ts +13 -0
- package/lib/config/utils.js +160 -0
- package/lib/format/format.d.ts +1 -1
- package/lib/format/format.js +28 -0
- package/lib/index.d.ts +1 -2
- package/lib/index.js +5 -6
- package/lib/lint.d.ts +1 -1
- package/lib/lint.js +5 -7
- package/lib/redocly/index.d.ts +1 -1
- package/lib/redocly/index.js +1 -1
- package/lib/redocly/redocly-client-types.d.ts +1 -1
- package/lib/redocly/registry-api.d.ts +1 -1
- package/lib/resolve.d.ts +1 -1
- package/lib/resolve.js +1 -2
- package/lib/rules/common/assertions/index.js +1 -1
- package/lib/rules/common/assertions/utils.d.ts +1 -1
- package/lib/rules/common/assertions/utils.js +6 -2
- package/lib/types/index.js +2 -2
- package/lib/types/oas3_1.js +31 -5
- package/lib/utils.d.ts +4 -1
- package/lib/utils.js +18 -1
- package/package.json +7 -4
- package/src/__tests__/lint.test.ts +1 -1
- package/src/benchmark/benches/recommended-oas3.bench.ts +2 -3
- package/src/benchmark/benchmark.js +1 -1
- package/src/benchmark/utils.ts +13 -8
- package/src/bundle.ts +2 -1
- package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +140 -0
- package/src/config/__tests__/config-resolvers.test.ts +398 -0
- package/src/config/__tests__/config.test.ts +244 -0
- package/src/config/__tests__/fixtures/plugin.js +1 -1
- package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +7 -0
- package/src/config/__tests__/fixtures/resolve-config/api/plugin.js +67 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +8 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +12 -0
- package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +10 -0
- package/src/config/__tests__/fixtures/resolve-config/plugin.js +66 -0
- package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +4 -0
- package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +5 -0
- package/src/config/__tests__/load.test.ts +8 -1
- package/src/config/all.ts +3 -2
- package/src/config/builtIn.ts +2 -1
- package/src/config/config-resolvers.ts +304 -0
- package/src/config/config.ts +40 -457
- package/src/config/index.ts +7 -0
- package/src/config/load.ts +37 -31
- package/src/config/minimal.ts +2 -2
- package/src/config/recommended.ts +2 -2
- package/src/config/types.ts +168 -0
- package/src/config/utils.ts +208 -0
- package/src/decorators/__tests__/remove-x-internal.test.ts +5 -5
- package/src/format/format.ts +36 -6
- package/src/index.ts +6 -2
- package/src/lint.ts +4 -5
- package/src/redocly/__tests__/redocly-client.test.ts +7 -0
- package/src/redocly/index.ts +4 -2
- package/src/redocly/redocly-client-types.ts +1 -1
- package/src/redocly/registry-api.ts +3 -1
- package/src/resolve.ts +2 -4
- package/src/rules/__tests__/no-unresolved-refs.test.ts +4 -4
- package/src/rules/common/__tests__/info-description.test.ts +3 -3
- package/src/rules/common/__tests__/info-license.test.ts +2 -2
- package/src/rules/common/__tests__/license-url.test.ts +2 -2
- package/src/rules/common/__tests__/no-ambiguous-paths.test.ts +1 -1
- package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +5 -5
- package/src/rules/common/__tests__/no-identical-paths.test.ts +1 -1
- package/src/rules/common/__tests__/no-path-trailing-slash.test.ts +3 -3
- package/src/rules/common/__tests__/operation-2xx-response.test.ts +3 -3
- package/src/rules/common/__tests__/operation-4xx-response.test.ts +3 -3
- package/src/rules/common/__tests__/operation-operationId-unique.test.ts +2 -2
- package/src/rules/common/__tests__/operation-operationId-url-safe.test.ts +1 -1
- package/src/rules/common/__tests__/operation-parameters-unique.test.ts +4 -4
- package/src/rules/common/__tests__/operation-security-defined.test.ts +2 -2
- package/src/rules/common/__tests__/operation-singular-tag.test.ts +2 -2
- package/src/rules/common/__tests__/path-http-verbs-order.test.ts +2 -2
- package/src/rules/common/__tests__/path-not-include-query.test.ts +2 -2
- package/src/rules/common/__tests__/path-params-defined.test.ts +3 -3
- package/src/rules/common/__tests__/paths-kebab-case.test.ts +3 -3
- package/src/rules/common/__tests__/spec.test.ts +1 -1
- package/src/rules/common/__tests__/tag-description.test.ts +2 -2
- package/src/rules/common/__tests__/tags-alphabetical.test.ts +2 -2
- package/src/rules/common/assertions/index.ts +1 -1
- package/src/rules/common/assertions/utils.ts +5 -2
- package/src/rules/oas2/__tests__/boolean-parameter-prefixes.test.ts +3 -3
- package/src/rules/oas2/__tests__/spec/referenceableScalars.test.ts +1 -1
- package/src/rules/oas2/__tests__/spec/utils.ts +10 -7
- package/src/rules/oas3/__tests__/boolean-parameter-prefixes.test.ts +3 -3
- package/src/rules/oas3/__tests__/no-empty-enum-servers.com.test.ts +6 -6
- package/src/rules/oas3/__tests__/no-example-value-and-externalValue.test.ts +2 -2
- package/src/rules/oas3/__tests__/no-invalid-media-type-examples.test.ts +8 -8
- package/src/rules/oas3/__tests__/no-server-example.com.test.ts +2 -2
- package/src/rules/oas3/__tests__/no-server-trailing-slash.test.ts +3 -3
- package/src/rules/oas3/__tests__/no-unused-components.test.ts +1 -1
- package/src/rules/oas3/__tests__/spec/referenceableScalars.test.ts +23 -14
- package/src/rules/oas3/__tests__/spec/servers.test.ts +1 -1
- package/src/rules/oas3/__tests__/spec/spec.test.ts +4 -4
- package/src/rules/oas3/__tests__/spec/utils.ts +10 -7
- package/src/types/index.ts +2 -2
- package/src/types/oas3_1.ts +32 -7
- package/src/utils.ts +18 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const id = 'test-plugin-nested';
|
|
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
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
id,
|
|
63
|
+
preprocessors,
|
|
64
|
+
rules,
|
|
65
|
+
decorators,
|
|
66
|
+
configs,
|
|
67
|
+
};
|
|
@@ -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
|
+
};
|
|
@@ -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 {
|
|
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
|
|
65
|
+
} as PluginLintConfig;
|
package/src/config/builtIn.ts
CHANGED
|
@@ -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,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
|
+
}
|