@redocly/openapi-core 1.0.0-beta.102 → 1.0.0-beta.105

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 (62) hide show
  1. package/__tests__/utils.ts +3 -1
  2. package/lib/config/config.d.ts +4 -3
  3. package/lib/config/config.js +23 -16
  4. package/lib/config/load.d.ts +1 -1
  5. package/lib/config/load.js +15 -3
  6. package/lib/config/rules.d.ts +1 -1
  7. package/lib/config/types.d.ts +4 -2
  8. package/lib/decorators/common/filters/filter-helper.d.ts +3 -0
  9. package/lib/decorators/common/filters/filter-helper.js +67 -0
  10. package/lib/decorators/common/filters/filter-in.d.ts +2 -0
  11. package/lib/decorators/common/filters/filter-in.js +17 -0
  12. package/lib/decorators/common/filters/filter-out.d.ts +2 -0
  13. package/lib/decorators/common/filters/filter-out.js +17 -0
  14. package/lib/decorators/oas2/index.d.ts +2 -0
  15. package/lib/decorators/oas2/index.js +5 -1
  16. package/lib/decorators/oas3/index.d.ts +2 -0
  17. package/lib/decorators/oas3/index.js +5 -1
  18. package/lib/index.d.ts +2 -2
  19. package/lib/index.js +2 -1
  20. package/lib/lint.d.ts +2 -0
  21. package/lib/lint.js +2 -2
  22. package/lib/redocly/registry-api-types.d.ts +2 -0
  23. package/lib/redocly/registry-api.d.ts +1 -1
  24. package/lib/redocly/registry-api.js +3 -1
  25. package/lib/rules/ajv.d.ts +1 -1
  26. package/lib/rules/ajv.js +1 -1
  27. package/lib/rules/common/assertions/asserts.d.ts +6 -1
  28. package/lib/rules/common/assertions/asserts.js +81 -51
  29. package/lib/rules/common/assertions/utils.d.ts +2 -1
  30. package/lib/rules/common/assertions/utils.js +27 -8
  31. package/lib/types/redocly-yaml.js +317 -27
  32. package/lib/utils.d.ts +5 -3
  33. package/lib/utils.js +15 -2
  34. package/lib/walk.d.ts +4 -14
  35. package/lib/walk.js +35 -26
  36. package/package.json +3 -2
  37. package/src/__tests__/fixtures/.redocly.lint-ignore.yaml +5 -0
  38. package/src/__tests__/lint.test.ts +70 -10
  39. package/src/__tests__/utils.test.ts +42 -1
  40. package/src/config/__tests__/load.test.ts +8 -2
  41. package/src/config/config.ts +31 -27
  42. package/src/config/load.ts +29 -9
  43. package/src/config/types.ts +6 -5
  44. package/src/decorators/__tests__/filter-in.test.ts +310 -0
  45. package/src/decorators/__tests__/filter-out.test.ts +331 -0
  46. package/src/decorators/common/filters/filter-helper.ts +72 -0
  47. package/src/decorators/common/filters/filter-in.ts +18 -0
  48. package/src/decorators/common/filters/filter-out.ts +18 -0
  49. package/src/decorators/oas2/index.ts +5 -1
  50. package/src/decorators/oas3/index.ts +5 -1
  51. package/src/index.ts +2 -1
  52. package/src/lint.ts +4 -3
  53. package/src/redocly/registry-api-types.ts +2 -0
  54. package/src/redocly/registry-api.ts +4 -0
  55. package/src/rules/ajv.ts +4 -4
  56. package/src/rules/common/assertions/__tests__/asserts.test.ts +149 -146
  57. package/src/rules/common/assertions/asserts.ts +97 -52
  58. package/src/rules/common/assertions/utils.ts +41 -16
  59. package/src/types/redocly-yaml.ts +322 -34
  60. package/src/utils.ts +28 -15
  61. package/src/walk.ts +59 -47
  62. package/tsconfig.tsbuildinfo +1 -1
package/lib/walk.js CHANGED
@@ -32,6 +32,26 @@ function walkDocument(opts) {
32
32
  walkNode(document.parsed, rootType, new ref_utils_1.Location(document.source, '#/'), undefined, '');
33
33
  function walkNode(node, type, location, parent, key) {
34
34
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
35
+ const resolve = (ref, from = currentLocation.source.absoluteRef) => {
36
+ if (!ref_utils_1.isRef(ref))
37
+ return { location, node: ref };
38
+ const refId = resolve_1.makeRefId(from, ref.$ref);
39
+ const resolvedRef = resolvedRefMap.get(refId);
40
+ if (!resolvedRef) {
41
+ return {
42
+ location: undefined,
43
+ node: undefined,
44
+ };
45
+ }
46
+ const { resolved, node, document, nodePointer, error } = resolvedRef;
47
+ const newLocation = resolved
48
+ ? new ref_utils_1.Location(document.source, nodePointer)
49
+ : error instanceof resolve_1.YamlParseError
50
+ ? new ref_utils_1.Location(error.source, '')
51
+ : undefined;
52
+ return { location: newLocation, node, error };
53
+ };
54
+ const rawLocation = location;
35
55
  let currentLocation = location;
36
56
  const { node: resolvedNode, location: resolvedLocation, error } = resolve(node);
37
57
  const enteredContexts = new Set();
@@ -44,13 +64,15 @@ function walkDocument(opts) {
44
64
  visitor(node, {
45
65
  report,
46
66
  resolve,
67
+ rawNode: node,
68
+ rawLocation,
47
69
  location,
48
70
  type,
49
71
  parent,
50
72
  key,
51
73
  parentLocations: {},
52
74
  oasVersion: ctx.oasVersion,
53
- getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
75
+ getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
54
76
  }, { node: resolvedNode, location: resolvedLocation, error });
55
77
  if ((resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef) && ctx.refTypes) {
56
78
  ctx.refTypes.set(resolvedLocation === null || resolvedLocation === void 0 ? void 0 : resolvedLocation.source.absoluteRef, type);
@@ -101,7 +123,7 @@ function walkDocument(opts) {
101
123
  if (!activatedOn.skipped) {
102
124
  visitedBySome = true;
103
125
  enteredContexts.add(context);
104
- const { ignoreNextVisitorsOnNode } = visitWithContext(visit, resolvedNode, context, ruleId, severity);
126
+ const { ignoreNextVisitorsOnNode } = visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
105
127
  if (ignoreNextVisitorsOnNode)
106
128
  break;
107
129
  }
@@ -173,7 +195,7 @@ function walkDocument(opts) {
173
195
  }
174
196
  for (const { context, visit, ruleId, severity } of currentLeaveVisitors) {
175
197
  if (!context.isSkippedLevel && enteredContexts.has(context)) {
176
- visitWithContext(visit, resolvedNode, context, ruleId, severity);
198
+ visitWithContext(visit, resolvedNode, node, context, ruleId, severity);
177
199
  }
178
200
  }
179
201
  }
@@ -186,54 +208,41 @@ function walkDocument(opts) {
186
208
  visitor(node, {
187
209
  report,
188
210
  resolve,
211
+ rawNode: node,
212
+ rawLocation,
189
213
  location,
190
214
  type,
191
215
  parent,
192
216
  key,
193
217
  parentLocations: {},
194
218
  oasVersion: ctx.oasVersion,
195
- getVisitorData: getVisitorDataFn.bind(undefined, ruleId)
219
+ getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
196
220
  }, { node: resolvedNode, location: resolvedLocation, error });
197
221
  }
198
222
  }
199
223
  }
200
224
  // returns true ignores all the next visitors on the specific node
201
- function visitWithContext(visit, node, context, ruleId, severity) {
225
+ function visitWithContext(visit, resolvedNode, node, context, ruleId, severity) {
202
226
  const report = reportFn.bind(undefined, ruleId, severity);
203
227
  let ignoreNextVisitorsOnNode = false;
204
- visit(node, {
228
+ visit(resolvedNode, {
205
229
  report,
206
230
  resolve,
231
+ rawNode: node,
207
232
  location: currentLocation,
233
+ rawLocation,
208
234
  type,
209
235
  parent,
210
236
  key,
211
237
  parentLocations: collectParentsLocations(context),
212
238
  oasVersion: ctx.oasVersion,
213
- ignoreNextVisitorsOnNode: () => { ignoreNextVisitorsOnNode = true; },
239
+ ignoreNextVisitorsOnNode: () => {
240
+ ignoreNextVisitorsOnNode = true;
241
+ },
214
242
  getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
215
243
  }, collectParents(context), context);
216
244
  return { ignoreNextVisitorsOnNode };
217
245
  }
218
- function resolve(ref, from = currentLocation.source.absoluteRef) {
219
- if (!ref_utils_1.isRef(ref))
220
- return { location, node: ref };
221
- const refId = resolve_1.makeRefId(from, ref.$ref);
222
- const resolvedRef = resolvedRefMap.get(refId);
223
- if (!resolvedRef) {
224
- return {
225
- location: undefined,
226
- node: undefined,
227
- };
228
- }
229
- const { resolved, node, document, nodePointer, error } = resolvedRef;
230
- const newLocation = resolved
231
- ? new ref_utils_1.Location(document.source, nodePointer)
232
- : error instanceof resolve_1.YamlParseError
233
- ? new ref_utils_1.Location(error.source, '')
234
- : undefined;
235
- return { location: newLocation, node, error };
236
- }
237
246
  function reportFn(ruleId, severity, opts) {
238
247
  const loc = opts.location
239
248
  ? Array.isArray(opts.location)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.0.0-beta.102",
3
+ "version": "1.0.0-beta.105",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -16,7 +16,8 @@
16
16
  "fs": false,
17
17
  "path": "path-browserify",
18
18
  "os": false,
19
- "node-fetch": false
19
+ "node-fetch": false,
20
+ "colorette": false
20
21
  },
21
22
  "homepage": "https://github.com/Redocly/redocly-cli",
22
23
  "keywords": [
@@ -0,0 +1,5 @@
1
+ # This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.
2
+ # See https://redoc.ly/docs/cli/ for more information.
3
+ openapi.yaml:
4
+ operation-operationId:
5
+ - '#/paths/~1pets~1{petId}/post/operationId'
@@ -1,3 +1,4 @@
1
+ import * as path from 'path';
1
2
  import { outdent } from 'outdent';
2
3
 
3
4
  import { lintFromString, lintConfig, lintDocument } from '../lint';
@@ -70,7 +71,7 @@ describe('lint', () => {
70
71
  links:
71
72
  color: '#6CC496'
72
73
  `,
73
- '',
74
+ ''
74
75
  );
75
76
  const results = await lintConfig({ document });
76
77
 
@@ -85,7 +86,7 @@ describe('lint', () => {
85
86
  },
86
87
  ],
87
88
  "message": "Expected type \`ConfigApis\` (object) but got \`string\`",
88
- "ruleId": "spec",
89
+ "ruleId": "configuration spec",
89
90
  "severity": "error",
90
91
  "suggest": Array [],
91
92
  },
@@ -97,8 +98,8 @@ describe('lint', () => {
97
98
  "source": "",
98
99
  },
99
100
  ],
100
- "message": "Expected type \`string\` but got \`object\`.",
101
- "ruleId": "spec",
101
+ "message": "\`layout\` can be one of the following only: \\"stacked\\", \\"three-panel\\".",
102
+ "ruleId": "configuration spec",
102
103
  "severity": "error",
103
104
  "suggest": Array [],
104
105
  },
@@ -117,12 +118,25 @@ describe('lint', () => {
117
118
  plugins:
118
119
  - './local-plugin.js'
119
120
  `,
120
- '',
121
+ ''
121
122
  );
122
123
  const results = await lintConfig({ document });
123
124
 
124
125
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
125
126
  Array [
127
+ Object {
128
+ "location": Array [
129
+ Object {
130
+ "pointer": "#/apis/lint",
131
+ "reportOnKey": true,
132
+ "source": "",
133
+ },
134
+ ],
135
+ "message": "The field \`root\` must be present on this level.",
136
+ "ruleId": "configuration spec",
137
+ "severity": "error",
138
+ "suggest": Array [],
139
+ },
126
140
  Object {
127
141
  "location": Array [
128
142
  Object {
@@ -132,7 +146,7 @@ describe('lint', () => {
132
146
  },
133
147
  ],
134
148
  "message": "Property \`plugins\` is not expected here.",
135
- "ruleId": "spec",
149
+ "ruleId": "configuration spec",
136
150
  "severity": "error",
137
151
  "suggest": Array [],
138
152
  },
@@ -169,7 +183,7 @@ describe('lint', () => {
169
183
  type: string
170
184
  const: ABC
171
185
  `,
172
- 'foobar.yaml',
186
+ 'foobar.yaml'
173
187
  );
174
188
 
175
189
  const results = await lintDocument({
@@ -186,10 +200,10 @@ describe('lint', () => {
186
200
  outdent`
187
201
  openapi: 3.0
188
202
  `,
189
- '',
203
+ ''
190
204
  );
191
205
  expect(() => detectOpenAPI(testDocument.parsed)).toThrow(
192
- `Invalid OpenAPI version: should be a string but got "number"`,
206
+ `Invalid OpenAPI version: should be a string but got "number"`
193
207
  );
194
208
  });
195
209
 
@@ -218,7 +232,7 @@ describe('lint', () => {
218
232
  '200':
219
233
  description: callback successfully processed
220
234
  `,
221
- 'foobar.yaml',
235
+ 'foobar.yaml'
222
236
  );
223
237
 
224
238
  const results = await lintDocument({
@@ -229,4 +243,50 @@ describe('lint', () => {
229
243
 
230
244
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
231
245
  });
246
+
247
+ it('should ignore error because ignore file passed', async () => {
248
+ const absoluteRef = path.join(__dirname, 'fixtures/openapi.yaml');
249
+ const document = parseYamlToDocument(
250
+ outdent`
251
+ openapi: 3.0.0
252
+ info:
253
+ version: 1.0.0
254
+ title: Example OpenAPI 3 definition.
255
+ description: Information about API
256
+ license:
257
+ name: MIT
258
+ url: 'https://opensource.org/licenses/MIT'
259
+ servers:
260
+ - url: 'https://redocly.com/v1'
261
+ paths:
262
+ '/pets/{petId}':
263
+ post:
264
+ responses:
265
+ '201':
266
+ summary: Exist
267
+ description: example description
268
+ `,
269
+ absoluteRef
270
+ );
271
+
272
+ const configFilePath = path.join(__dirname, 'fixtures');
273
+
274
+ const result = await lintDocument({
275
+ externalRefResolver: new BaseResolver(),
276
+ document,
277
+ config: await makeConfig({ 'operation-operationId': 'error' }, undefined, configFilePath),
278
+ });
279
+ expect(result).toHaveLength(1);
280
+ expect(result).toMatchObject([
281
+ {
282
+ ignored: true,
283
+ location: [{ pointer: '#/paths/~1pets~1{petId}/post/operationId' }],
284
+ message: 'Operation object should contain `operationId` field.',
285
+ ruleId: 'operation-operationId',
286
+ severity: 'error',
287
+ },
288
+ ]);
289
+ expect(result[0]).toHaveProperty('ignored', true);
290
+ expect(result[0]).toHaveProperty('ruleId', 'operation-operationId');
291
+ });
232
292
  });
@@ -1,4 +1,12 @@
1
- import { pickObjectProps, omitObjectProps, slash, getMatchingStatusCodeRange } from '../utils';
1
+ import {
2
+ pickObjectProps,
3
+ omitObjectProps,
4
+ slash,
5
+ getMatchingStatusCodeRange,
6
+ doesYamlFileExist,
7
+ } from '../utils';
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
2
10
 
3
11
  describe('utils', () => {
4
12
  const testObject = {
@@ -81,5 +89,38 @@ describe('utils', () => {
81
89
  expect(getMatchingStatusCodeRange('2002')).toEqual('2002');
82
90
  expect(getMatchingStatusCodeRange(4000)).toEqual('4000');
83
91
  });
92
+
93
+ describe('isConfigFileExist', () => {
94
+ beforeEach(() => {
95
+ jest
96
+ .spyOn(fs, 'existsSync')
97
+ .mockImplementation((path) => path === 'redocly.yaml' || path === 'redocly.yml');
98
+ jest.spyOn(path, 'extname').mockImplementation((path) => {
99
+ if (path.endsWith('.yaml')) {
100
+ return '.yaml';
101
+ } else if (path.endsWith('.yml')) {
102
+ return '.yml';
103
+ } else {
104
+ return '';
105
+ }
106
+ });
107
+ });
108
+
109
+ it('should return true because of valid path provided', () => {
110
+ expect(doesYamlFileExist('redocly.yaml')).toBe(true);
111
+ });
112
+
113
+ it('should return true because of valid path provided with yml', () => {
114
+ expect(doesYamlFileExist('redocly.yml')).toBe(true);
115
+ });
116
+
117
+ it('should return false because of fail do not exist', () => {
118
+ expect(doesYamlFileExist('redoccccly.yaml')).toBe(false);
119
+ });
120
+
121
+ it('should return false because of it is not yaml file', () => {
122
+ expect(doesYamlFileExist('redocly.yam')).toBe(false);
123
+ });
124
+ });
84
125
  });
85
126
  });
@@ -9,7 +9,7 @@ describe('loadConfig', () => {
9
9
  jest
10
10
  .spyOn(RedoclyClient.prototype, 'getTokens')
11
11
  .mockImplementation(() =>
12
- Promise.resolve([{ region: 'us', token: 'accessToken', valid: true }]),
12
+ Promise.resolve([{ region: 'us', token: 'accessToken', valid: true }])
13
13
  );
14
14
  const config = await loadConfig();
15
15
  expect(config.resolve.http.headers).toStrictEqual([
@@ -32,7 +32,7 @@ describe('loadConfig', () => {
32
32
  jest
33
33
  .spyOn(RedoclyClient.prototype, 'getTokens')
34
34
  .mockImplementation(() =>
35
- Promise.resolve([{ region: 'eu', token: 'accessToken', valid: true }]),
35
+ Promise.resolve([{ region: 'eu', token: 'accessToken', valid: true }])
36
36
  );
37
37
  const config = await loadConfig();
38
38
  expect(config.resolve.http.headers).toStrictEqual([
@@ -44,6 +44,12 @@ describe('loadConfig', () => {
44
44
  },
45
45
  ]);
46
46
  });
47
+
48
+ it('should call callback if such passed', async () => {
49
+ const mockFn = jest.fn();
50
+ await loadConfig(undefined, undefined, mockFn);
51
+ expect(mockFn).toHaveBeenCalled();
52
+ });
47
53
  });
48
54
 
49
55
  describe('findConfig', () => {
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { parseYaml, stringifyYaml } from '../js-yaml';
4
- import { slash } from '../utils';
4
+ import { slash, doesYamlFileExist } from '../utils';
5
5
  import { NormalizedProblem } from '../walk';
6
6
  import { OasVersion, OasMajorVersion, Oas2RuleSet, Oas3RuleSet } from '../oas-types';
7
7
 
@@ -46,6 +46,16 @@ function getDomains() {
46
46
  return domains;
47
47
  }
48
48
 
49
+ function getIgnoreFilePath(configFile?: string): string | undefined {
50
+ if (configFile) {
51
+ return doesYamlFileExist(configFile)
52
+ ? path.join(path.dirname(configFile), IGNORE_FILE)
53
+ : path.join(configFile, IGNORE_FILE);
54
+ } else {
55
+ return typeof process === 'undefined' ? undefined : path.join(process.cwd(), IGNORE_FILE);
56
+ }
57
+ }
58
+
49
59
  export const DOMAINS = getDomains();
50
60
  export const AVAILABLE_REGIONS = Object.keys(DOMAINS) as Region[];
51
61
 
@@ -69,7 +79,7 @@ export class LintConfig {
69
79
  this.plugins = rawConfig.plugins || [];
70
80
  this.doNotResolveExamples = !!rawConfig.doNotResolveExamples;
71
81
 
72
- this.recommendedFallback = rawConfig.recommendedFallback || false
82
+ this.recommendedFallback = rawConfig.recommendedFallback || false;
73
83
 
74
84
  this.rules = {
75
85
  [OasVersion.Version2]: { ...rawConfig.rules, ...rawConfig.oas2Rules },
@@ -91,29 +101,25 @@ export class LintConfig {
91
101
 
92
102
  this.extendPaths = rawConfig.extendPaths || [];
93
103
  this.pluginPaths = rawConfig.pluginPaths || [];
104
+ this.resolveIgnore(getIgnoreFilePath(configFile));
105
+ }
94
106
 
95
- const dir = this.configFile
96
- ? path.dirname(this.configFile)
97
- : (typeof process !== 'undefined' && process.cwd()) || '';
98
- const ignoreFile = path.join(dir, IGNORE_FILE);
107
+ resolveIgnore(ignoreFile?: string) {
108
+ if (!ignoreFile || !doesYamlFileExist(ignoreFile)) return;
99
109
 
100
- /* no crash when using it on the client */
101
- if (fs.hasOwnProperty('existsSync') && fs.existsSync(ignoreFile)) {
102
- // TODO: parse errors
103
- this.ignore =
104
- (parseYaml(fs.readFileSync(ignoreFile, 'utf-8')) as Record<
105
- string,
106
- Record<string, Set<string>>
107
- >) || {};
108
-
109
- // resolve ignore paths
110
- for (const fileName of Object.keys(this.ignore)) {
111
- this.ignore[path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
112
- for (const ruleId of Object.keys(this.ignore[fileName])) {
113
- this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
114
- }
115
- delete this.ignore[fileName];
110
+ this.ignore =
111
+ (parseYaml(fs.readFileSync(ignoreFile, 'utf-8')) as Record<
112
+ string,
113
+ Record<string, Set<string>>
114
+ >) || {};
115
+
116
+ // resolve ignore paths
117
+ for (const fileName of Object.keys(this.ignore)) {
118
+ this.ignore[path.resolve(path.dirname(ignoreFile), fileName)] = this.ignore[fileName];
119
+ for (const ruleId of Object.keys(this.ignore[fileName])) {
120
+ this.ignore[fileName][ruleId] = new Set(this.ignore[fileName][ruleId]);
116
121
  }
122
+ delete this.ignore[fileName];
117
123
  }
118
124
  }
119
125
 
@@ -224,15 +230,13 @@ export class LintConfig {
224
230
 
225
231
  for (const usedVersion of Array.from(this._usedVersions)) {
226
232
  rules.push(
227
- ...Object.keys(this.rules[usedVersion]).filter((name) => !this._usedRules.has(name)),
233
+ ...Object.keys(this.rules[usedVersion]).filter((name) => !this._usedRules.has(name))
228
234
  );
229
235
  decorators.push(
230
- ...Object.keys(this.decorators[usedVersion]).filter((name) => !this._usedRules.has(name)),
236
+ ...Object.keys(this.decorators[usedVersion]).filter((name) => !this._usedRules.has(name))
231
237
  );
232
238
  preprocessors.push(
233
- ...Object.keys(this.preprocessors[usedVersion]).filter(
234
- (name) => !this._usedRules.has(name),
235
- ),
239
+ ...Object.keys(this.preprocessors[usedVersion]).filter((name) => !this._usedRules.has(name))
236
240
  );
237
241
  }
238
242
 
@@ -1,18 +1,22 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { RedoclyClient } from '../redocly';
4
- import { isEmptyObject, loadYaml } from '../utils';
4
+ import { isEmptyObject, loadYaml, doesYamlFileExist } from '../utils';
5
5
  import { Config, DOMAINS } from './config';
6
6
  import { transformConfig } from './utils';
7
7
  import { resolveConfig } from './config-resolvers';
8
8
 
9
9
  import type { RawConfig, Region } from './types';
10
10
 
11
- export async function loadConfig(
12
- configPath: string | undefined = findConfig(),
13
- customExtends?: string[],
14
- ): Promise<Config> {
15
- const rawConfig = await getConfig(configPath);
11
+ async function addConfigMetadata({
12
+ rawConfig,
13
+ customExtends,
14
+ configPath,
15
+ }: {
16
+ rawConfig: RawConfig;
17
+ customExtends?: string[];
18
+ configPath?: string;
19
+ }): Promise<Config> {
16
20
  if (customExtends !== undefined) {
17
21
  rawConfig.lint = rawConfig.lint || {};
18
22
  rawConfig.lint.extends = customExtends;
@@ -48,7 +52,7 @@ export async function loadConfig(
48
52
  value: item.token,
49
53
  },
50
54
  ]
51
- : []),
55
+ : [])
52
56
  );
53
57
  }
54
58
  }
@@ -56,12 +60,28 @@ export async function loadConfig(
56
60
  return resolveConfig(rawConfig, configPath);
57
61
  }
58
62
 
63
+ export async function loadConfig(
64
+ configPath: string | undefined = findConfig(),
65
+ customExtends?: string[],
66
+ processRawConfig?: (rawConfig: RawConfig) => void | Promise<void>
67
+ ): Promise<Config> {
68
+ const rawConfig = await getConfig(configPath);
69
+ if (typeof processRawConfig === 'function') {
70
+ await processRawConfig(rawConfig);
71
+ }
72
+ return await addConfigMetadata({
73
+ rawConfig,
74
+ customExtends,
75
+ configPath,
76
+ });
77
+ }
78
+
59
79
  export const CONFIG_FILE_NAMES = ['redocly.yaml', 'redocly.yml', '.redocly.yaml', '.redocly.yml'];
60
80
 
61
81
  export function findConfig(dir?: string): string | undefined {
62
82
  if (!fs.hasOwnProperty('existsSync')) return;
63
83
  const existingConfigFiles = CONFIG_FILE_NAMES.map((name) =>
64
- dir ? path.resolve(dir, name) : name,
84
+ dir ? path.resolve(dir, name) : name
65
85
  ).filter(fs.existsSync);
66
86
  if (existingConfigFiles.length > 1) {
67
87
  throw new Error(`
@@ -74,7 +94,7 @@ export function findConfig(dir?: string): string | undefined {
74
94
  }
75
95
 
76
96
  export async function getConfig(configPath: string | undefined = findConfig()) {
77
- if (!configPath) return {};
97
+ if (!configPath || !doesYamlFileExist(configPath)) return {};
78
98
  try {
79
99
  const rawConfig = ((await loadYaml(configPath)) || {}) as RawConfig;
80
100
  return transformConfig(rawConfig);
@@ -11,17 +11,18 @@ import type {
11
11
  } from '../oas-types';
12
12
  import type { NodeType } from '../types';
13
13
 
14
+ export type RuleSeverity = ProblemSeverity | 'off';
15
+
16
+ export type PreprocessorSeverity = RuleSeverity | 'on';
17
+
14
18
  export type RuleConfig =
15
- | ProblemSeverity
16
- | 'off'
19
+ | RuleSeverity
17
20
  | ({
18
21
  severity?: ProblemSeverity;
19
22
  } & Record<string, any>);
20
23
 
21
24
  export type PreprocessorConfig =
22
- | ProblemSeverity
23
- | 'off'
24
- | 'on'
25
+ | PreprocessorSeverity
25
26
  | ({
26
27
  severity?: ProblemSeverity;
27
28
  } & Record<string, any>);