@redocly/openapi-core 1.5.0 → 1.6.0

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rootRedoclyConfigSchema = exports.environmentSchema = exports.redoclyConfigSchema = exports.apiConfigSchema = exports.ssoConfigSchema = void 0;
3
+ exports.rootRedoclyConfigSchema = exports.environmentSchema = exports.redoclyConfigSchema = exports.PortalConfigNodeTypes = exports.apiConfigSchema = exports.ssoConfigSchema = void 0;
4
4
  const config_1 = require("../config");
5
5
  const oidcIssuerMetadataSchema = {
6
6
  type: 'object',
@@ -80,6 +80,7 @@ const authProviderConfigSchema = {
80
80
  };
81
81
  exports.ssoConfigSchema = {
82
82
  type: 'object',
83
+ properties: {},
83
84
  additionalProperties: authProviderConfigSchema,
84
85
  };
85
86
  const redirectConfigSchema = {
@@ -89,7 +90,12 @@ const redirectConfigSchema = {
89
90
  type: { type: 'number', default: 301 },
90
91
  },
91
92
  required: ['to'],
92
- additionalProperties: false,
93
+ };
94
+ const redirectsConfigSchema = {
95
+ type: 'object',
96
+ properties: {},
97
+ additionalProperties: 'redirectConfigSchema',
98
+ default: {},
93
99
  };
94
100
  exports.apiConfigSchema = {
95
101
  type: 'object',
@@ -136,13 +142,16 @@ const seoConfigSchema = {
136
142
  },
137
143
  },
138
144
  },
139
- additionalProperties: false,
140
145
  };
141
- const rbacScopeItemsSchema = { type: 'object', additionalProperties: { type: 'string' } };
146
+ const rbacScopeItemsSchema = {
147
+ type: 'object',
148
+ properties: {},
149
+ additionalProperties: { type: 'string' },
150
+ };
142
151
  const rbacConfigSchema = {
143
152
  type: 'object',
144
153
  properties: {
145
- defaults: rbacScopeItemsSchema,
154
+ defaults: 'rbacScopeItemsSchema',
146
155
  },
147
156
  additionalProperties: rbacScopeItemsSchema,
148
157
  };
@@ -207,7 +216,6 @@ const devOnboardingAdapterConfigSchema = {
207
216
  const devOnboardingConfigSchema = {
208
217
  type: 'object',
209
218
  required: ['adapters'],
210
- additionalProperties: false,
211
219
  properties: {
212
220
  adapters: {
213
221
  type: 'array',
@@ -215,6 +223,30 @@ const devOnboardingConfigSchema = {
215
223
  },
216
224
  },
217
225
  };
226
+ const i18ConfigSchema = {
227
+ type: 'object',
228
+ properties: {
229
+ defaultLocale: {
230
+ type: 'string',
231
+ },
232
+ locales: {
233
+ type: 'array',
234
+ items: {
235
+ type: 'object',
236
+ properties: {
237
+ code: {
238
+ type: 'string',
239
+ },
240
+ name: {
241
+ type: 'string',
242
+ },
243
+ },
244
+ required: ['code'],
245
+ },
246
+ },
247
+ },
248
+ required: ['defaultLocale', 'locales'],
249
+ };
218
250
  const responseHeaderSchema = {
219
251
  type: 'object',
220
252
  properties: {
@@ -224,14 +256,24 @@ const responseHeaderSchema = {
224
256
  additionalProperties: false,
225
257
  required: ['name', 'value'],
226
258
  };
259
+ exports.PortalConfigNodeTypes = {
260
+ seoConfigSchema,
261
+ rbacConfigSchema,
262
+ rbacScopeItemsSchema,
263
+ ssoConfigSchema: exports.ssoConfigSchema,
264
+ devOnboardingConfigSchema,
265
+ i18ConfigSchema,
266
+ redirectsConfigSchema,
267
+ redirectConfigSchema,
268
+ // TODO: Extract other types that need to be linted in the config
269
+ };
227
270
  exports.redoclyConfigSchema = {
228
271
  type: 'object',
229
272
  properties: {
230
273
  licenseKey: { type: 'string' },
231
- theme: { type: 'object', default: {} },
232
- redirects: { type: 'object', additionalProperties: redirectConfigSchema, default: {} },
233
- seo: seoConfigSchema,
234
- rbac: rbacConfigSchema,
274
+ redirects: 'redirectsConfigSchema',
275
+ seo: 'seoConfigSchema',
276
+ rbac: 'rbacConfigSchema',
235
277
  responseHeaders: {
236
278
  type: 'object',
237
279
  additionalProperties: {
@@ -253,37 +295,12 @@ exports.redoclyConfigSchema = {
253
295
  type: 'object',
254
296
  additionalProperties: exports.apiConfigSchema,
255
297
  },
256
- sso: exports.ssoConfigSchema,
257
- developerOnboarding: devOnboardingConfigSchema,
258
- i18n: {
259
- type: 'object',
260
- properties: {
261
- defaultLocale: {
262
- type: 'string',
263
- },
264
- locales: {
265
- type: 'array',
266
- items: {
267
- type: 'object',
268
- properties: {
269
- code: {
270
- type: 'string',
271
- },
272
- name: {
273
- type: 'string',
274
- },
275
- },
276
- required: ['code'],
277
- },
278
- },
279
- },
280
- additionalProperties: false,
281
- required: ['defaultLocale', 'locales'],
282
- },
298
+ sso: 'ssoConfigSchema',
299
+ developerOnboarding: 'devOnboardingConfigSchema',
300
+ i18n: 'i18ConfigSchema',
283
301
  metadata: metadataConfigSchema,
284
302
  },
285
303
  default: {},
286
- additionalProperties: true,
287
304
  };
288
305
  exports.environmentSchema = {
289
306
  oneOf: [
@@ -300,5 +317,6 @@ exports.environmentSchema = {
300
317
  };
301
318
  exports.rootRedoclyConfigSchema = Object.assign(Object.assign({}, exports.redoclyConfigSchema), { properties: Object.assign(Object.assign({}, exports.redoclyConfigSchema.properties), { env: {
302
319
  type: 'object',
320
+ properties: {},
303
321
  additionalProperties: exports.environmentSchema,
304
322
  } }), default: {}, required: ['redirects'] });
@@ -4,8 +4,8 @@ exports.ConfigTypes = void 0;
4
4
  const portal_config_schema_1 = require("./portal-config-schema");
5
5
  const theme_config_1 = require("./theme-config");
6
6
  const _1 = require(".");
7
- const oas3_1_1 = require("./oas3_1");
8
7
  const utils_1 = require("../utils");
8
+ const portal_config_schema_2 = require("./portal-config-schema");
9
9
  const builtInCommonRules = [
10
10
  'spec',
11
11
  'info-contact',
@@ -196,7 +196,7 @@ const ConfigApisProperties = {
196
196
  items: {
197
197
  type: 'string',
198
198
  },
199
- }, lint: 'ConfigStyleguide', styleguide: 'ConfigStyleguide' }), ConfigStyleguide.properties), { 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', theme: 'ConfigRootTheme', files: {
199
+ } }), ConfigStyleguide.properties), { 'features.openapi': 'ConfigReferenceDocs', 'features.mockServer': 'ConfigMockServer', theme: 'ConfigRootTheme', files: {
200
200
  type: 'array',
201
201
  items: {
202
202
  type: 'string',
@@ -838,7 +838,7 @@ const ConfigMockServer = {
838
838
  errorIfForcedExampleNotFound: { type: 'boolean' },
839
839
  },
840
840
  };
841
- exports.ConfigTypes = Object.assign(Object.assign({}, oas3_1_1.Oas3_1Types), { Assert,
841
+ exports.ConfigTypes = Object.assign({ Assert,
842
842
  ConfigRoot,
843
843
  ConfigApis,
844
844
  ConfigApisProperties,
@@ -901,4 +901,4 @@ exports.ConfigTypes = Object.assign(Object.assign({}, oas3_1_1.Oas3_1Types), { A
901
901
  Heading,
902
902
  Typography,
903
903
  AssertionDefinitionAssertions,
904
- AssertionDefinitionSubject });
904
+ AssertionDefinitionSubject }, portal_config_schema_2.PortalConfigNodeTypes);
package/lib/visitors.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { Oas3Definition, Oas3ExternalDocs, Oas3Info, Oas3Contact, Oas3Components, Oas3License, Oas3Schema, Oas3Header, Oas3Parameter, Oas3Operation, Oas3PathItem, Oas3ServerVariable, Oas3Server, Oas3MediaType, Oas3Response, Oas3Example, Oas3RequestBody, Oas3Tag, OasRef, Oas3SecurityScheme, Oas3SecurityRequirement, Oas3Encoding, Oas3Link, Oas3Xml, Oas3Discriminator, Oas3Callback } from './typings/openapi';
2
- import type { Oas2Definition, Oas2Tag, Oas2ExternalDocs, Oas2SecurityRequirement, Oas2Info, Oas2Contact, Oas2License, Oas2PathItem, Oas2Operation, Oas2Header, Oas2Response, Oas2Schema, Oas2Xml, Oas2Parameter, Oas2SecurityScheme } from './typings/swagger';
3
- import { NormalizedNodeType } from './types';
1
+ import type { NormalizedNodeType } from './types';
4
2
  import type { Stack } from './utils';
5
3
  import type { UserContext, ResolveResult, ProblemSeverity } from './walk';
6
4
  import type { Location } from './ref-utils';
7
- import { Async2Definition } from './typings/asyncapi';
5
+ import type { Oas3Definition, Oas3ExternalDocs, Oas3Info, Oas3Contact, Oas3Components, Oas3License, Oas3Schema, Oas3Header, Oas3Parameter, Oas3Operation, Oas3PathItem, Oas3ServerVariable, Oas3Server, Oas3MediaType, Oas3Response, Oas3Example, Oas3RequestBody, Oas3Tag, OasRef, Oas3SecurityScheme, Oas3SecurityRequirement, Oas3Encoding, Oas3Link, Oas3Xml, Oas3Discriminator, Oas3Callback } from './typings/openapi';
6
+ import type { Oas2Definition, Oas2Tag, Oas2ExternalDocs, Oas2SecurityRequirement, Oas2Info, Oas2Contact, Oas2License, Oas2PathItem, Oas2Operation, Oas2Header, Oas2Response, Oas2Schema, Oas2Xml, Oas2Parameter, Oas2SecurityScheme } from './typings/swagger';
7
+ import type { Async2Definition } from './typings/asyncapi';
8
8
  export type SkipFunctionContext = Pick<UserContext, 'location' | 'rawNode' | 'resolve' | 'rawLocation'>;
9
9
  export type VisitFunction<T> = (node: T, ctx: UserContext & {
10
10
  ignoreNextVisitorsOnNode: () => void;
@@ -184,6 +184,6 @@ export type RuleInstanceConfig = {
184
184
  severity: ProblemSeverity;
185
185
  };
186
186
  export declare function normalizeVisitors<T extends BaseVisitor>(visitorsConfig: (RuleInstanceConfig & {
187
- visitor: NestedVisitObject<any, T>;
187
+ visitor: NestedVisitObject<unknown, T>;
188
188
  })[], types: Record<keyof T, NormalizedNodeType>): NormalizedOasVisitors<T>;
189
189
  export {};
package/lib/visitors.js CHANGED
@@ -77,12 +77,10 @@ function normalizeVisitors(visitorsConfig, types) {
77
77
  }
78
78
  function addWeakFromStack(ruleConf, stack) {
79
79
  for (const interType of stack.slice(1)) {
80
- normalizedVisitors[interType.name] =
81
- normalizedVisitors[interType.name] ||
82
- {
83
- enter: [],
84
- leave: [],
85
- };
80
+ normalizedVisitors[interType.name] = normalizedVisitors[interType.name] || {
81
+ enter: [],
82
+ leave: [],
83
+ };
86
84
  normalizedVisitors[interType.name].enter.push(Object.assign(Object.assign({}, ruleConf), { visit: () => undefined, depth: 0, context: {
87
85
  isSkippedLevel: true,
88
86
  seen: new Set(),
package/lib/walk.d.ts CHANGED
@@ -1,11 +1,11 @@
1
+ import { Location } from './ref-utils';
2
+ import { ResolveError, YamlParseError, Source } from './resolve';
3
+ import { SpecVersion } from './oas-types';
1
4
  import type { Referenced } from './typings/openapi';
2
5
  import type { NormalizedOasVisitors, BaseVisitor } from './visitors';
3
6
  import type { ResolvedRefMap, Document } from './resolve';
4
7
  import type { NormalizedNodeType } from './types';
5
8
  import type { RuleSeverity } from './config';
6
- import { Location } from './ref-utils';
7
- import { ResolveError, YamlParseError, Source } from './resolve';
8
- import { SpecVersion } from './oas-types';
9
9
  export type NonUndefined = string | number | boolean | symbol | bigint | object | Record<string, any>;
10
10
  export type ResolveResult<T extends NonUndefined> = {
11
11
  node: T;
package/lib/walk.js CHANGED
@@ -86,7 +86,7 @@ function walkDocument(opts) {
86
86
  const currentEnterVisitors = anyEnterVisitors.concat(((_c = normalizedVisitors[type.name]) === null || _c === void 0 ? void 0 : _c.enter) || []);
87
87
  const activatedContexts = [];
88
88
  for (const { context, visit, skip, ruleId, severity } of currentEnterVisitors) {
89
- if (ignoredNodes.has(currentLocation.pointer))
89
+ if (ignoredNodes.has(`${currentLocation.absolutePointer}${currentLocation.pointer}`))
90
90
  break;
91
91
  if (context.isSkippedLevel) {
92
92
  if (context.parent.activatedOn &&
@@ -248,7 +248,7 @@ function walkDocument(opts) {
248
248
  parentLocations: collectParentsLocations(context),
249
249
  oasVersion: ctx.oasVersion,
250
250
  ignoreNextVisitorsOnNode: () => {
251
- ignoredNodes.add(currentLocation.pointer);
251
+ ignoredNodes.add(`${currentLocation.absolutePointer}${currentLocation.pointer}`);
252
252
  },
253
253
  getVisitorData: getVisitorDataFn.bind(undefined, ruleId),
254
254
  }, collectParents(context), context);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redocly/openapi-core",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "engines": {
@@ -22,7 +22,7 @@ describe('lint', () => {
22
22
  servers:
23
23
  - url: http://redocly-example.com
24
24
  paths: {}
25
- `,
25
+ `,
26
26
  config: await loadConfig(),
27
27
  });
28
28
 
@@ -139,8 +139,8 @@ describe('lint', () => {
139
139
  "ruleId": "configuration spec",
140
140
  "severity": "error",
141
141
  "suggest": [
142
- "theme",
143
142
  "env",
143
+ "theme",
144
144
  "seo",
145
145
  "sso",
146
146
  ],
package/src/bundle.ts CHANGED
@@ -1,26 +1,30 @@
1
1
  import isEqual = require('lodash.isequal');
2
+ import { BaseResolver, resolveDocument, makeRefId, makeDocumentFromString } from './resolve';
3
+ import { normalizeVisitors } from './visitors';
4
+ import { normalizeTypes } from './types';
5
+ import { walkDocument } from './walk';
2
6
  import {
3
- BaseResolver,
4
- resolveDocument,
5
- Document,
6
- ResolvedRefMap,
7
- makeRefId,
8
- makeDocumentFromString,
9
- } from './resolve';
10
- import { Oas3Rule, normalizeVisitors, Oas3Visitor, Oas2Visitor } from './visitors';
11
- import { NormalizedNodeType, normalizeTypes, NodeType } from './types';
12
- import { WalkContext, walkDocument, UserContext, ResolveResult, NormalizedProblem } from './walk';
13
- import { detectSpec, getTypes, getMajorSpecVersion, SpecMajorVersion } from './oas-types';
7
+ detectSpec,
8
+ getTypes,
9
+ getMajorSpecVersion,
10
+ SpecMajorVersion,
11
+ SpecVersion,
12
+ } from './oas-types';
14
13
  import { isAbsoluteUrl, isRef, Location, refBaseName } from './ref-utils';
15
14
  import { initRules } from './config/rules';
16
15
  import { reportUnresolvedRef } from './rules/no-unresolved-refs';
17
16
  import { isPlainObject, isTruthy } from './utils';
18
- import { OasRef } from './typings/openapi';
19
17
  import { isRedoclyRegistryURL } from './redocly';
20
18
  import { RemoveUnusedComponents as RemoveUnusedComponentsOas2 } from './decorators/oas2/remove-unused-components';
21
19
  import { RemoveUnusedComponents as RemoveUnusedComponentsOas3 } from './decorators/oas3/remove-unused-components';
20
+ import { ConfigTypes } from './types/redocly-yaml';
22
21
 
22
+ import type { Oas3Rule, Oas3Visitor, Oas2Visitor } from './visitors';
23
+ import type { NormalizedNodeType, NodeType } from './types';
24
+ import type { WalkContext, UserContext, ResolveResult, NormalizedProblem } from './walk';
23
25
  import type { Config, StyleguideConfig } from './config';
26
+ import type { OasRef } from './typings/openapi';
27
+ import type { Document, ResolvedRefMap } from './resolve';
24
28
 
25
29
  export type Oas3RuleSet = Record<string, Oas3Rule>;
26
30
 
@@ -33,12 +37,50 @@ export type BundleOptions = {
33
37
  externalRefResolver?: BaseResolver;
34
38
  config: Config;
35
39
  dereference?: boolean;
36
- base?: string;
40
+ base?: string | null;
37
41
  skipRedoclyRegistryRefs?: boolean;
38
42
  removeUnusedComponents?: boolean;
39
43
  keepUrlRefs?: boolean;
40
44
  };
41
45
 
46
+ export async function bundleConfig(document: Document, resolvedRefMap: ResolvedRefMap) {
47
+ const types = normalizeTypes(ConfigTypes);
48
+
49
+ const ctx: BundleContext = {
50
+ problems: [],
51
+ oasVersion: SpecVersion.OAS3_0,
52
+ refTypes: new Map<string, NormalizedNodeType>(),
53
+ visitorsData: {},
54
+ };
55
+
56
+ const bundleVisitor = normalizeVisitors(
57
+ [
58
+ {
59
+ severity: 'error',
60
+ ruleId: 'configBundler',
61
+ visitor: {
62
+ ref: {
63
+ leave(node: OasRef, ctx: UserContext, resolved: ResolveResult<any>) {
64
+ replaceRef(node, resolved, ctx);
65
+ },
66
+ },
67
+ },
68
+ },
69
+ ],
70
+ types
71
+ );
72
+
73
+ walkDocument({
74
+ document,
75
+ rootType: types.ConfigRoot,
76
+ normalizedVisitors: bundleVisitor,
77
+ resolvedRefMap,
78
+ ctx,
79
+ });
80
+
81
+ return document.parsed ?? {};
82
+ }
83
+
42
84
  export async function bundle(
43
85
  opts: {
44
86
  ref?: string;
@@ -254,6 +296,18 @@ export function mapTypeToComponent(typeName: string, version: SpecMajorVersion)
254
296
  }
255
297
  }
256
298
 
299
+ function replaceRef(ref: OasRef, resolved: ResolveResult<any>, ctx: UserContext) {
300
+ if (!isPlainObject(resolved.node)) {
301
+ ctx.parent[ctx.key] = resolved.node;
302
+ } else {
303
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
304
+ // @ts-ignore
305
+ delete ref.$ref;
306
+ const obj = Object.assign({}, resolved.node, ref);
307
+ Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
308
+ }
309
+ }
310
+
257
311
  // function oas3Move
258
312
 
259
313
  function makeBundleVisitor(
@@ -351,19 +405,6 @@ function makeBundleVisitor(
351
405
  });
352
406
  }
353
407
 
354
- function replaceRef(ref: OasRef, resolved: ResolveResult<any>, ctx: UserContext) {
355
- if (!isPlainObject(resolved.node)) {
356
- ctx.parent[ctx.key] = resolved.node;
357
- } else {
358
- // TODO: why $ref isn't optional in OasRef?
359
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
360
- // @ts-ignore
361
- delete ref.$ref;
362
- const obj = Object.assign({}, resolved.node, ref);
363
- Object.assign(ref, obj); // assign ref itself again so ref fields take precedence
364
- }
365
- }
366
-
367
408
  function saveComponent(
368
409
  componentType: string,
369
410
  target: { node: any; location: Location },
@@ -0,0 +1,8 @@
1
+ rules:
2
+ $ref: rules.yaml
3
+
4
+ seo:
5
+ $ref: seo.yaml
6
+
7
+ theme:
8
+ $ref: wrong-ref.yaml
@@ -0,0 +1,2 @@
1
+ info-license: error
2
+ non-existing-rule: warn # fail
@@ -1,7 +1,10 @@
1
1
  import { loadConfig, findConfig, getConfig, createConfig } from '../load';
2
2
  import { RedoclyClient } from '../../redocly';
3
- import { RuleConfig, FlatRawConfig } from './../types';
4
3
  import { Config } from '../config';
4
+ import { lintConfig } from '../../lint';
5
+ import { replaceSourceWithRef } from '../../../__tests__/utils';
6
+ import type { RuleConfig, FlatRawConfig } from './../types';
7
+ import type { NormalizedProblem } from '../../walk';
5
8
 
6
9
  const fs = require('fs');
7
10
  const path = require('path');
@@ -91,6 +94,82 @@ describe('getConfig', () => {
91
94
  it('should return empty object if there is no configPath and config file is not found', () => {
92
95
  expect(getConfig()).toEqual(Promise.resolve({}));
93
96
  });
97
+
98
+ it('should resolve refs in config', async () => {
99
+ let problems: NormalizedProblem[];
100
+ const result = await getConfig({
101
+ configPath: path.join(__dirname, './fixtures/resolve-refs-in-config/config-with-refs.yaml'),
102
+ processRawConfig: async (config, resolvedRefMap) => {
103
+ problems = await lintConfig({
104
+ document: config,
105
+ severity: 'warn',
106
+ resolvedRefMap,
107
+ });
108
+ },
109
+ });
110
+ expect(result).toEqual({
111
+ seo: {
112
+ title: 1,
113
+ },
114
+ styleguide: {
115
+ rules: {
116
+ 'info-license': 'error',
117
+ 'non-existing-rule': 'warn',
118
+ },
119
+ },
120
+ });
121
+ expect(replaceSourceWithRef(problems!, __dirname)).toMatchInlineSnapshot(`
122
+ [
123
+ {
124
+ "from": {
125
+ "pointer": "#/seo",
126
+ "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
127
+ },
128
+ "location": [
129
+ {
130
+ "pointer": "#/title",
131
+ "reportOnKey": false,
132
+ "source": "fixtures/resolve-refs-in-config/seo.yaml",
133
+ },
134
+ ],
135
+ "message": "Expected type \`string\` but got \`integer\`.",
136
+ "ruleId": "configuration spec",
137
+ "severity": "warn",
138
+ "suggest": [],
139
+ },
140
+ {
141
+ "from": {
142
+ "pointer": "#/rules",
143
+ "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
144
+ },
145
+ "location": [
146
+ {
147
+ "pointer": "#/non-existing-rule",
148
+ "reportOnKey": true,
149
+ "source": "fixtures/resolve-refs-in-config/rules.yaml",
150
+ },
151
+ ],
152
+ "message": "Property \`non-existing-rule\` is not expected here.",
153
+ "ruleId": "configuration spec",
154
+ "severity": "warn",
155
+ "suggest": [],
156
+ },
157
+ {
158
+ "location": [
159
+ {
160
+ "pointer": "#/theme",
161
+ "reportOnKey": false,
162
+ "source": "fixtures/resolve-refs-in-config/config-with-refs.yaml",
163
+ },
164
+ ],
165
+ "message": "Can't resolve $ref: ENOENT: no such file or directory 'fixtures/resolve-refs-in-config/wrong-ref.yaml'",
166
+ "ruleId": "configuration no-unresolved-refs",
167
+ "severity": "warn",
168
+ "suggest": [],
169
+ },
170
+ ]
171
+ `);
172
+ });
94
173
  });
95
174
 
96
175
  describe('createConfig', () => {
@@ -1,7 +1,7 @@
1
1
  import * as path from 'path';
2
2
  import { isAbsoluteUrl } from '../ref-utils';
3
3
  import { pickDefined } from '../utils';
4
- import { BaseResolver } from '../resolve';
4
+ import { resolveDocument, BaseResolver } from '../resolve';
5
5
  import { defaultPlugin } from './builtIn';
6
6
  import {
7
7
  getResolveConfig,
@@ -11,6 +11,14 @@ import {
11
11
  prefixRules,
12
12
  transformConfig,
13
13
  } from './utils';
14
+ import { isBrowser } from '../env';
15
+ import { isNotString, isString, isDefined, parseYaml, keysOf } from '../utils';
16
+ import { Config } from './config';
17
+ import { colorize, logger } from '../logger';
18
+ import { asserts, buildAssertCustomFunction } from '../rules/common/assertions/asserts';
19
+ import { normalizeTypes } from '../types';
20
+ import { ConfigTypes } from '../types/redocly-yaml';
21
+
14
22
  import type {
15
23
  StyleguideRawConfig,
16
24
  ApiStyleguideRawConfig,
@@ -21,17 +29,39 @@ import type {
21
29
  RuleConfig,
22
30
  DeprecatedInRawConfig,
23
31
  } from './types';
24
- import { isBrowser } from '../env';
25
- import { isNotString, isString, isDefined, parseYaml, keysOf } from '../utils';
26
- import { Config } from './config';
27
- import { colorize, logger } from '../logger';
28
- import {
29
- Asserts,
30
- AssertionFn,
31
- asserts,
32
- buildAssertCustomFunction,
33
- } from '../rules/common/assertions/asserts';
34
32
  import type { Assertion, AssertionDefinition, RawAssertion } from '../rules/common/assertions';
33
+ import type { Asserts, AssertionFn } from '../rules/common/assertions/asserts';
34
+ import type { BundleOptions } from '../bundle';
35
+ import type { Document, ResolvedRefMap } from '../resolve';
36
+
37
+ export async function resolveConfigFileAndRefs({
38
+ configPath,
39
+ externalRefResolver = new BaseResolver(),
40
+ base = null,
41
+ }: Omit<BundleOptions, 'config'> & { configPath?: string }): Promise<{
42
+ document: Document;
43
+ resolvedRefMap: ResolvedRefMap;
44
+ }> {
45
+ if (!configPath) {
46
+ throw new Error('Reference to a config is required.\n');
47
+ }
48
+
49
+ const document = await externalRefResolver.resolveDocument(base, configPath, true);
50
+
51
+ if (document instanceof Error) {
52
+ throw document;
53
+ }
54
+
55
+ const types = normalizeTypes(ConfigTypes);
56
+
57
+ const resolvedRefMap = await resolveDocument({
58
+ rootDocument: document,
59
+ rootType: types.ConfigRoot,
60
+ externalRefResolver,
61
+ });
62
+
63
+ return { document, resolvedRefMap };
64
+ }
35
65
 
36
66
  export async function resolveConfig(rawConfig: RawConfig, configPath?: string): Promise<Config> {
37
67
  if (rawConfig.styleguide?.extends?.some(isNotString)) {