@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
package/lib/utils.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
12
+ exports.assignExisting = exports.isNotString = exports.isString = exports.isNotEmptyObject = exports.slash = exports.isPathParameter = exports.readFileAsStringSync = exports.isSingular = exports.validateMimeTypeOAS3 = exports.validateMimeType = exports.splitCamelCaseIntoWords = exports.omitObjectProps = exports.pickObjectProps = exports.match = exports.readFileFromUrl = exports.isEmptyArray = exports.isEmptyObject = exports.isPlainObject = exports.notUndefined = exports.loadYaml = exports.popStack = exports.pushStack = exports.stringifyYaml = exports.parseYaml = void 0;
13
13
  const fs = require("fs");
14
14
  const minimatch = require("minimatch");
15
15
  const node_fetch_1 = require("node-fetch");
@@ -156,3 +156,20 @@ function isNotEmptyObject(obj) {
156
156
  return !!obj && Object.keys(obj).length > 0;
157
157
  }
158
158
  exports.isNotEmptyObject = isNotEmptyObject;
159
+ // TODO: use it everywhere
160
+ function isString(value) {
161
+ return typeof value === 'string';
162
+ }
163
+ exports.isString = isString;
164
+ function isNotString(value) {
165
+ return !isString(value);
166
+ }
167
+ exports.isNotString = isNotString;
168
+ function assignExisting(target, obj) {
169
+ for (let k of Object.keys(obj)) {
170
+ if (target.hasOwnProperty(k)) {
171
+ target[k] = obj[k];
172
+ }
173
+ }
174
+ }
175
+ exports.assignExisting = assignExisting;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.92",
3
+ "version": "1.0.0-beta.95",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -13,7 +13,10 @@
13
13
  "url": "https://github.com/Redocly/openapi-cli.git"
14
14
  },
15
15
  "browser": {
16
- "fs": false
16
+ "fs": false,
17
+ "path": "path-browserify",
18
+ "os": false,
19
+ "node-fetch": false
17
20
  },
18
21
  "homepage": "https://github.com/Redocly/openapi-cli",
19
22
  "keywords": [
@@ -174,7 +174,7 @@ describe('lint', () => {
174
174
  const results = await lintDocument({
175
175
  externalRefResolver: new BaseResolver(),
176
176
  document,
177
- config: makeConfig({ spec: 'error' }),
177
+ config: await makeConfig({ spec: 'error' }),
178
178
  });
179
179
 
180
180
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
@@ -1,10 +1,9 @@
1
1
  import { readFileSync } from 'fs';
2
2
  import { join as pathJoin, resolve as pathResolve } from 'path';
3
3
  import { lintDocument } from '../../lint';
4
- import { LintConfig } from '../../config/config';
4
+ import { LintConfig, defaultPlugin, resolvePreset } from '../../config';
5
5
  import { BaseResolver } from '../../resolve';
6
6
  import { parseYamlToDocument } from '../utils';
7
- import { defaultPlugin } from '../../config/builtIn';
8
7
 
9
8
  export const name = 'Validate with recommended rules';
10
9
  export const count = 10;
@@ -18,6 +17,6 @@ export function measureAsync() {
18
17
  return lintDocument({
19
18
  externalRefResolver: new BaseResolver(),
20
19
  document: rebillyDocument,
21
- config: new LintConfig({ plugins: [defaultPlugin] }),
20
+ config: new LintConfig(resolvePreset('recommended', [defaultPlugin])),
22
21
  });
23
22
  }
@@ -1,7 +1,9 @@
1
1
  import { parseYaml } from '../js-yaml';
2
2
  import { Document, Source } from '../resolve';
3
3
  import { Oas3RuleSet } from '../oas-types';
4
- import { RuleConfig, LintConfig, Plugin } from '../config/config';
4
+ import { LintConfig, mergeExtends, resolvePlugins } from '../config';
5
+
6
+ import type { RuleConfig, Plugin, ResolvedLintConfig } from '../config/types';
5
7
 
6
8
  export function parseYamlToDocument(body: string, absoluteRef: string = ''): Document {
7
9
  return {
@@ -16,16 +18,19 @@ export function makeConfigForRuleset(rules: Oas3RuleSet, plugin?: Partial<Plugin
16
18
  Object.keys(rules).forEach((name) => {
17
19
  rulesConf[`${ruleId}/${name}`] = 'error';
18
20
  });
19
-
20
- return new LintConfig({
21
- plugins: [
21
+ const extendConfigs = [
22
+ resolvePlugins([
22
23
  {
23
24
  ...plugin,
24
25
  id: ruleId,
25
26
  rules: { oas3: rules },
26
27
  },
27
- ],
28
- extends: [],
29
- rules: rulesConf,
30
- });
28
+ ]) as ResolvedLintConfig,
29
+ ];
30
+ if (rules) {
31
+ extendConfigs.push({ rules });
32
+ }
33
+ const lint = mergeExtends(extendConfigs);
34
+
35
+ return new LintConfig(lint);
31
36
  }
package/src/bundle.ts CHANGED
@@ -8,7 +8,6 @@ import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
8
8
  import { WalkContext, walkDocument, UserContext, ResolveResult } from './walk';
9
9
  import { detectOpenAPI, openAPIMajor, OasMajorVersion } from './oas-types';
10
10
  import { isRef, Location, refBaseName } from './ref-utils';
11
- import type { Config, LintConfig } from './config/config';
12
11
  import { initRules } from './config/rules';
13
12
  import { reportUnresolvedRef } from './rules/no-unresolved-refs';
14
13
  import { isPlainObject } from './utils';
@@ -17,6 +16,8 @@ import { isRedoclyRegistryURL } from './redocly';
17
16
  import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './rules/oas2/remove-unused-components';
18
17
  import { RemoveUnusedComponents as RemoveUnusedComponentsOas3 } from './rules/oas3/remove-unused-components';
19
18
 
19
+ import type { Config, LintConfig } from './config';
20
+
20
21
  export type Oas3RuleSet = Record<string, Oas3Rule>;
21
22
 
22
23
  export enum OasVersion {
@@ -0,0 +1,140 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`resolveConfig should ignore minimal from the root and read local file 1`] = `
4
+ Object {
5
+ "decorators": Object {},
6
+ "doNotResolveExamples": undefined,
7
+ "oas2Decorators": Object {},
8
+ "oas2Preprocessors": Object {},
9
+ "oas2Rules": Object {},
10
+ "oas3_0Decorators": Object {},
11
+ "oas3_0Preprocessors": Object {},
12
+ "oas3_0Rules": Object {
13
+ "no-empty-servers": "error",
14
+ "no-example-value-and-externalValue": "error",
15
+ "no-invalid-media-type-examples": "error",
16
+ "no-server-example.com": "warn",
17
+ "no-server-trailing-slash": "error",
18
+ "no-servers-empty-enum": "error",
19
+ "no-undefined-server-variable": "error",
20
+ "no-unused-components": "warn",
21
+ },
22
+ "oas3_1Decorators": Object {},
23
+ "oas3_1Preprocessors": Object {},
24
+ "oas3_1Rules": Object {
25
+ "no-empty-servers": "error",
26
+ "no-example-value-and-externalValue": "error",
27
+ "no-server-example.com": "warn",
28
+ "no-server-trailing-slash": "error",
29
+ "no-servers-empty-enum": "error",
30
+ "no-undefined-server-variable": "error",
31
+ "no-unused-components": "warn",
32
+ },
33
+ "preprocessors": Object {},
34
+ "recommendedFallback": false,
35
+ "rules": Object {
36
+ "assertions": "warn",
37
+ "boolean-parameter-prefixes": "error",
38
+ "info-contact": "off",
39
+ "info-description": "warn",
40
+ "info-license": "warn",
41
+ "info-license-url": "warn",
42
+ "local/operation-id-not-test": "error",
43
+ "no-ambiguous-paths": "warn",
44
+ "no-enum-type-mismatch": "error",
45
+ "no-identical-paths": "error",
46
+ "no-invalid-media-type-examples": "error",
47
+ "no-path-trailing-slash": "error",
48
+ "no-unresolved-refs": "error",
49
+ "operation-2xx-response": "warn",
50
+ "operation-4xx-response": "error",
51
+ "operation-description": "error",
52
+ "operation-operationId": "warn",
53
+ "operation-operationId-unique": "error",
54
+ "operation-operationId-url-safe": "error",
55
+ "operation-parameters-unique": "error",
56
+ "operation-security-defined": "error",
57
+ "operation-singular-tag": "off",
58
+ "operation-summary": "error",
59
+ "operation-tag-defined": "off",
60
+ "parameter-description": "off",
61
+ "path-declaration-must-exist": "error",
62
+ "path-http-verbs-order": "error",
63
+ "path-not-include-query": "error",
64
+ "path-parameters-defined": "error",
65
+ "paths-kebab-case": "off",
66
+ "spec": "error",
67
+ "tag-description": "warn",
68
+ "tags-alphabetical": "off",
69
+ },
70
+ }
71
+ `;
72
+
73
+ exports[`resolveLint should resolve extends with local file config witch contains path to nested config 1`] = `
74
+ Object {
75
+ "decorators": Object {},
76
+ "doNotResolveExamples": undefined,
77
+ "oas2Decorators": Object {},
78
+ "oas2Preprocessors": Object {},
79
+ "oas2Rules": Object {},
80
+ "oas3_0Decorators": Object {},
81
+ "oas3_0Preprocessors": Object {},
82
+ "oas3_0Rules": Object {
83
+ "no-empty-servers": "error",
84
+ "no-example-value-and-externalValue": "error",
85
+ "no-invalid-media-type-examples": "warn",
86
+ "no-server-example.com": "warn",
87
+ "no-server-trailing-slash": "error",
88
+ "no-servers-empty-enum": "error",
89
+ "no-undefined-server-variable": "error",
90
+ "no-unused-components": "warn",
91
+ },
92
+ "oas3_1Decorators": Object {},
93
+ "oas3_1Preprocessors": Object {},
94
+ "oas3_1Rules": Object {
95
+ "no-empty-servers": "error",
96
+ "no-example-value-and-externalValue": "error",
97
+ "no-server-example.com": "warn",
98
+ "no-server-trailing-slash": "error",
99
+ "no-servers-empty-enum": "error",
100
+ "no-undefined-server-variable": "error",
101
+ "no-unused-components": "warn",
102
+ },
103
+ "preprocessors": Object {},
104
+ "recommendedFallback": undefined,
105
+ "rules": Object {
106
+ "assertions": "warn",
107
+ "boolean-parameter-prefixes": "error",
108
+ "info-contact": "off",
109
+ "info-description": "warn",
110
+ "info-license": "warn",
111
+ "info-license-url": "warn",
112
+ "local/operation-id-not-test": "error",
113
+ "no-ambiguous-paths": "warn",
114
+ "no-enum-type-mismatch": "error",
115
+ "no-identical-paths": "error",
116
+ "no-invalid-media-type-examples": "warn",
117
+ "no-path-trailing-slash": "error",
118
+ "no-unresolved-refs": "error",
119
+ "operation-2xx-response": "error",
120
+ "operation-4xx-response": "off",
121
+ "operation-description": "off",
122
+ "operation-operationId": "warn",
123
+ "operation-operationId-unique": "error",
124
+ "operation-operationId-url-safe": "error",
125
+ "operation-parameters-unique": "error",
126
+ "operation-security-defined": "error",
127
+ "operation-singular-tag": "off",
128
+ "operation-summary": "error",
129
+ "operation-tag-defined": "off",
130
+ "parameter-description": "off",
131
+ "path-declaration-must-exist": "error",
132
+ "path-not-include-query": "error",
133
+ "path-parameters-defined": "error",
134
+ "paths-kebab-case": "off",
135
+ "spec": "error",
136
+ "tag-description": "warn",
137
+ "tags-alphabetical": "off",
138
+ },
139
+ }
140
+ `;
@@ -0,0 +1,398 @@
1
+ import { resolveLint, resolveApis, resolveConfig } from '../config-resolvers';
2
+ const path = require('path');
3
+
4
+ import type { LintRawConfig, RawConfig } from '../types';
5
+
6
+ const configPath = path.join(__dirname, 'fixtures/resolve-config/.redocly.yaml');
7
+ const baseLintConfig: LintRawConfig = {
8
+ rules: {
9
+ 'operation-2xx-response': 'warn',
10
+ },
11
+ };
12
+
13
+ const minimalLintPreset = resolveLint({
14
+ lintConfig: { ...baseLintConfig, extends: ['minimal'] },
15
+ });
16
+
17
+ const recommendedLintPreset = resolveLint({
18
+ lintConfig: { ...baseLintConfig, extends: ['recommended'] },
19
+ });
20
+
21
+ const removeAbsolutePath = (item: string) =>
22
+ item.match(/^.*\/packages\/core\/src\/config\/__tests__\/fixtures\/(.*)$/)![1];
23
+
24
+ describe('resolveLint', () => {
25
+ it('should return the config with no recommended', async () => {
26
+ const lint = await resolveLint({ lintConfig: baseLintConfig });
27
+ expect(lint.plugins?.length).toEqual(1);
28
+ expect(lint.plugins?.[0].id).toEqual('');
29
+ expect(lint.rules).toEqual({
30
+ 'operation-2xx-response': 'warn',
31
+ });
32
+ });
33
+
34
+ it('should return the config with correct order by preset', async () => {
35
+ expect(
36
+ await resolveLint({
37
+ lintConfig: { ...baseLintConfig, extends: ['minimal', 'recommended'] },
38
+ }),
39
+ ).toEqual(await recommendedLintPreset);
40
+ expect(
41
+ await resolveLint({
42
+ lintConfig: { ...baseLintConfig, extends: ['recommended', 'minimal'] },
43
+ }),
44
+ ).toEqual(await minimalLintPreset);
45
+ });
46
+
47
+ it('should return the same lintConfig when extends is empty array', async () => {
48
+ const configWithEmptyExtends = await resolveLint({
49
+ lintConfig: { ...baseLintConfig, extends: [] },
50
+ });
51
+ expect(configWithEmptyExtends.plugins?.length).toEqual(1);
52
+ expect(configWithEmptyExtends.plugins?.[0].id).toEqual('');
53
+ expect(configWithEmptyExtends.rules).toEqual({
54
+ 'operation-2xx-response': 'warn',
55
+ });
56
+ });
57
+
58
+ it('should resolve extends with local file config', async () => {
59
+ const config = {
60
+ ...baseLintConfig,
61
+ extends: ['local-config.yaml'],
62
+ };
63
+
64
+ const { plugins, ...lint } = await resolveLint({
65
+ lintConfig: config,
66
+ configPath,
67
+ });
68
+
69
+ expect(lint?.rules?.['operation-2xx-response']).toEqual('warn');
70
+ expect(plugins).toBeDefined();
71
+ expect(plugins?.length).toBe(2);
72
+
73
+ expect(lint.extendPaths!.map(removeAbsolutePath)).toEqual([
74
+ 'resolve-config/.redocly.yaml',
75
+ 'resolve-config/local-config.yaml',
76
+ 'resolve-config/.redocly.yaml',
77
+ ]);
78
+ expect(lint.pluginPaths!.map(removeAbsolutePath)).toEqual(['resolve-config/plugin.js']);
79
+
80
+ expect(lint.rules).toEqual({
81
+ 'boolean-parameter-prefixes': 'error',
82
+ 'local/operation-id-not-test': 'error',
83
+ 'no-invalid-media-type-examples': 'error',
84
+ 'operation-2xx-response': 'warn',
85
+ 'operation-description': 'error',
86
+ 'path-http-verbs-order': 'error',
87
+ });
88
+ });
89
+
90
+ // TODO: fix circular test
91
+ it.skip('should throw circular error', () => {
92
+ const config = {
93
+ ...baseLintConfig,
94
+ extends: ['local-config-with-circular.yaml'],
95
+ };
96
+ expect(() => {
97
+ resolveLint({ lintConfig: config, configPath });
98
+ }).toThrow('Circular dependency in config file');
99
+ });
100
+
101
+ it('should resolve extends with local file config witch contains path to nested config', async () => {
102
+ const lintConfig = {
103
+ extends: ['local-config-with-file.yaml'],
104
+ };
105
+ const { plugins, ...lint } = await resolveLint({
106
+ lintConfig,
107
+ configPath,
108
+ });
109
+
110
+ expect(lint?.rules?.['no-invalid-media-type-examples']).toEqual('warn');
111
+ expect(lint?.rules?.['operation-4xx-response']).toEqual('off');
112
+ expect(lint?.rules?.['operation-2xx-response']).toEqual('error');
113
+ expect(plugins).toBeDefined();
114
+ expect(plugins?.length).toBe(3);
115
+
116
+ expect(lint.extendPaths!.map(removeAbsolutePath)).toEqual([
117
+ 'resolve-config/.redocly.yaml',
118
+ 'resolve-config/local-config-with-file.yaml',
119
+ 'resolve-config/api/nested-config.yaml',
120
+ 'resolve-config/.redocly.yaml',
121
+ ]);
122
+ expect(lint.pluginPaths!.map(removeAbsolutePath)).toEqual([
123
+ 'resolve-config/api/plugin.js',
124
+ 'resolve-config/plugin.js',
125
+ 'resolve-config/api/plugin.js',
126
+ ]);
127
+
128
+ delete lint.extendPaths;
129
+ delete lint.pluginPaths;
130
+ expect(lint).toMatchSnapshot();
131
+ });
132
+
133
+ it('should resolve extends with url file config witch contains path to nested config', async () => {
134
+ const lintConfig = {
135
+ // This points to ./fixtures/resolve-remote-configs/remote-config.yaml
136
+ extends: [
137
+ 'https://raw.githubusercontent.com/Redocly/openapi-cli/master/packages/core/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml',
138
+ ],
139
+ };
140
+
141
+ const { plugins, ...lint } = await resolveLint({
142
+ lintConfig,
143
+ configPath,
144
+ });
145
+
146
+ expect(lint?.rules?.['operation-4xx-response']).toEqual('error');
147
+ expect(lint?.rules?.['operation-2xx-response']).toEqual('error');
148
+ expect(Object.keys(lint.rules || {}).length).toBe(2);
149
+
150
+ expect(lint.extendPaths!.map(removeAbsolutePath)).toEqual([
151
+ 'resolve-config/.redocly.yaml',
152
+ 'resolve-config/.redocly.yaml',
153
+ ]);
154
+ expect(lint.pluginPaths!.map(removeAbsolutePath)).toEqual([]);
155
+ });
156
+ });
157
+
158
+ describe('resolveApis', () => {
159
+ it('should resolve apis lintConfig and merge minimal extends', async () => {
160
+ const rawConfig: RawConfig = {
161
+ apis: {
162
+ petstore: {
163
+ root: 'some/path',
164
+ lint: {},
165
+ },
166
+ },
167
+ lint: {
168
+ extends: ['minimal'],
169
+ },
170
+ };
171
+ const apisResult = await resolveApis({ rawConfig });
172
+ expect(apisResult['petstore'].lint).toEqual(await minimalLintPreset);
173
+ });
174
+
175
+ it('should not merge recommended extends by default by every level', async () => {
176
+ const rawConfig: RawConfig = {
177
+ apis: {
178
+ petstore: {
179
+ root: 'some/path',
180
+ lint: {},
181
+ },
182
+ },
183
+ lint: {},
184
+ };
185
+
186
+ const apisResult = await resolveApis({ rawConfig, configPath });
187
+
188
+ expect(apisResult['petstore'].lint.extendPaths!.map(removeAbsolutePath)).toEqual([
189
+ 'resolve-config/.redocly.yaml',
190
+ ]);
191
+ expect(apisResult['petstore'].lint.pluginPaths!.map(removeAbsolutePath)).toEqual([]);
192
+
193
+ expect(apisResult['petstore'].lint.rules).toEqual({});
194
+ //@ts-ignore
195
+ expect(apisResult['petstore'].lint.plugins.length).toEqual(1);
196
+ //@ts-ignore
197
+ expect(apisResult['petstore'].lint.plugins[0].id).toEqual('');
198
+ });
199
+
200
+ it('should resolve apis lintConfig when it contains file and not set recommended', async () => {
201
+ const rawConfig: RawConfig = {
202
+ apis: {
203
+ petstore: {
204
+ root: 'some/path',
205
+ lint: {
206
+ rules: {
207
+ 'operation-4xx-response': 'error',
208
+ },
209
+ },
210
+ },
211
+ },
212
+ lint: {
213
+ rules: {
214
+ 'operation-2xx-response': 'warn',
215
+ },
216
+ },
217
+ };
218
+
219
+ const apisResult = await resolveApis({ rawConfig, configPath });
220
+ expect(apisResult['petstore'].lint.rules).toEqual({
221
+ 'operation-2xx-response': 'warn',
222
+ 'operation-4xx-response': 'error',
223
+ });
224
+ //@ts-ignore
225
+ expect(apisResult['petstore'].lint.plugins.length).toEqual(1);
226
+ //@ts-ignore
227
+ expect(apisResult['petstore'].lint.plugins[0].id).toEqual('');
228
+
229
+ expect(apisResult['petstore'].lint.extendPaths!.map(removeAbsolutePath)).toEqual([
230
+ 'resolve-config/.redocly.yaml',
231
+ ]);
232
+ expect(apisResult['petstore'].lint.pluginPaths!.map(removeAbsolutePath)).toEqual([]);
233
+ });
234
+
235
+ it('should resolve apis lintConfig when it contains file', async () => {
236
+ const rawConfig: RawConfig = {
237
+ apis: {
238
+ petstore: {
239
+ root: 'some/path',
240
+ lint: {
241
+ extends: ['local-config.yaml'],
242
+ rules: {
243
+ 'operation-4xx-response': 'error',
244
+ },
245
+ },
246
+ },
247
+ },
248
+ lint: {
249
+ extends: ['minimal'],
250
+ rules: {
251
+ 'operation-2xx-response': 'warn',
252
+ },
253
+ },
254
+ };
255
+
256
+ const apisResult = await resolveApis({ rawConfig, configPath });
257
+ expect(apisResult['petstore'].lint.rules).toBeDefined();
258
+ expect(apisResult['petstore'].lint.rules?.['operation-2xx-response']).toEqual('warn'); // think about prioritize in merge ???
259
+ expect(apisResult['petstore'].lint.rules?.['operation-4xx-response']).toEqual('error');
260
+ expect(apisResult['petstore'].lint.rules?.['local/operation-id-not-test']).toEqual('error');
261
+ //@ts-ignore
262
+ expect(apisResult['petstore'].lint.plugins.length).toEqual(2);
263
+
264
+ expect(apisResult['petstore'].lint.extendPaths!.map(removeAbsolutePath)).toEqual([
265
+ 'resolve-config/.redocly.yaml',
266
+ 'resolve-config/local-config.yaml',
267
+ 'resolve-config/.redocly.yaml',
268
+ ]);
269
+ expect(apisResult['petstore'].lint.pluginPaths!.map(removeAbsolutePath)).toEqual([
270
+ 'resolve-config/plugin.js',
271
+ ]);
272
+ });
273
+ });
274
+
275
+ describe('resolveConfig', () => {
276
+ it('should add recommended to top level by default', async () => {
277
+ const rawConfig: RawConfig = {
278
+ apis: {
279
+ petstore: {
280
+ root: 'some/path',
281
+ lint: {
282
+ rules: {
283
+ 'operation-4xx-response': 'error',
284
+ },
285
+ },
286
+ },
287
+ },
288
+ lint: {
289
+ rules: {
290
+ 'operation-2xx-response': 'warn',
291
+ },
292
+ },
293
+ };
294
+
295
+ const { apis } = await resolveConfig(rawConfig, configPath);
296
+ //@ts-ignore
297
+ expect(apis['petstore'].lint.plugins.length).toEqual(1);
298
+ //@ts-ignore
299
+ expect(apis['petstore'].lint.plugins[0].id).toEqual('');
300
+
301
+ expect(apis['petstore'].lint.extendPaths!.map(removeAbsolutePath)).toEqual([
302
+ 'resolve-config/.redocly.yaml',
303
+ ]);
304
+ expect(apis['petstore'].lint.pluginPaths!.map(removeAbsolutePath)).toEqual([]);
305
+
306
+ expect(apis['petstore'].lint.rules).toEqual({
307
+ ...(await recommendedLintPreset).rules,
308
+ 'operation-2xx-response': 'warn',
309
+ 'operation-4xx-response': 'error',
310
+ });
311
+ });
312
+
313
+ it('should not add recommended to top level by default when apis have extends file', async () => {
314
+ const rawConfig: RawConfig = {
315
+ apis: {
316
+ petstore: {
317
+ root: 'some/path',
318
+ lint: {
319
+ extends: ['local-config.yaml'],
320
+ rules: {
321
+ 'operation-4xx-response': 'error',
322
+ },
323
+ },
324
+ },
325
+ },
326
+ lint: {
327
+ rules: {
328
+ 'operation-2xx-response': 'warn',
329
+ },
330
+ },
331
+ };
332
+
333
+ const { apis } = await resolveConfig(rawConfig, configPath);
334
+ expect(apis['petstore'].lint.rules).toBeDefined();
335
+ expect(Object.keys(apis['petstore'].lint.rules || {}).length).toEqual(7);
336
+ expect(apis['petstore'].lint.rules?.['operation-2xx-response']).toEqual('warn');
337
+ expect(apis['petstore'].lint.rules?.['operation-4xx-response']).toEqual('error');
338
+ expect(apis['petstore'].lint.rules?.['operation-description']).toEqual('error'); // from extends file config
339
+ //@ts-ignore
340
+ expect(apis['petstore'].lint.plugins.length).toEqual(2);
341
+
342
+ expect(apis['petstore'].lint.extendPaths!.map(removeAbsolutePath)).toEqual([
343
+ 'resolve-config/.redocly.yaml',
344
+ 'resolve-config/local-config.yaml',
345
+ 'resolve-config/.redocly.yaml',
346
+ ]);
347
+ expect(apis['petstore'].lint.pluginPaths!.map(removeAbsolutePath)).toEqual([
348
+ 'resolve-config/plugin.js',
349
+ ]);
350
+
351
+ expect(apis['petstore'].lint.recommendedFallback).toBe(false);
352
+ });
353
+
354
+ it('should ignore minimal from the root and read local file', async () => {
355
+ const rawConfig: RawConfig = {
356
+ apis: {
357
+ petstore: {
358
+ root: 'some/path',
359
+ lint: {
360
+ extends: ['recommended', 'local-config.yaml'],
361
+ rules: {
362
+ 'operation-4xx-response': 'error',
363
+ },
364
+ },
365
+ },
366
+ },
367
+ lint: {
368
+ extends: ['minimal'],
369
+ rules: {
370
+ 'operation-2xx-response': 'warn',
371
+ },
372
+ },
373
+ };
374
+
375
+ const { apis } = await resolveConfig(rawConfig, configPath);
376
+ expect(apis['petstore'].lint.rules).toBeDefined();
377
+ expect(apis['petstore'].lint.rules?.['operation-2xx-response']).toEqual('warn');
378
+ expect(apis['petstore'].lint.rules?.['operation-4xx-response']).toEqual('error');
379
+ expect(apis['petstore'].lint.rules?.['operation-description']).toEqual('error'); // from extends file config
380
+ //@ts-ignore
381
+ expect(apis['petstore'].lint.plugins.length).toEqual(2);
382
+ //@ts-ignore
383
+ delete apis['petstore'].lint.plugins;
384
+
385
+ expect(apis['petstore'].lint.extendPaths!.map(removeAbsolutePath)).toEqual([
386
+ 'resolve-config/.redocly.yaml',
387
+ 'resolve-config/local-config.yaml',
388
+ 'resolve-config/.redocly.yaml',
389
+ ]);
390
+ expect(apis['petstore'].lint.pluginPaths!.map(removeAbsolutePath)).toEqual([
391
+ 'resolve-config/plugin.js',
392
+ ]);
393
+
394
+ delete apis['petstore'].lint.extendPaths;
395
+ delete apis['petstore'].lint.pluginPaths;
396
+ expect(apis['petstore'].lint).toMatchSnapshot();
397
+ });
398
+ });