@redocly/config 0.1.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.
package/src/index.ts ADDED
@@ -0,0 +1,13 @@
1
+ export { rbacConfigSchema, rootRedoclyConfigSchema } from './root-config-schema';
2
+ export {
3
+ productThemeOverrideSchema,
4
+ productConfigOverrideSchema,
5
+ } from './default-theme-config-schema';
6
+ export * from './types';
7
+ export * from './portal-shared-types';
8
+ export {
9
+ ApigeeDevOnboardingIntegrationAuthType,
10
+ AuthProviderType,
11
+ LayoutVariant,
12
+ REDOCLY_TEAMS_RBAC,
13
+ } from './constants';
@@ -0,0 +1,297 @@
1
+ import type { Node } from '@markdoc/markdoc/dist/src/types';
2
+ import type { LayoutVariant, REDOCLY_TEAMS_RBAC } from './constants';
3
+ import type {
4
+ ProductConfig,
5
+ ProductGoogleAnalyticsConfig,
6
+ RbacScopeItems,
7
+ SeoConfig,
8
+ ThemeConfig,
9
+ } from './types';
10
+
11
+ export type ThemeUIConfig = ThemeConfig & {
12
+ auth?: {
13
+ // used by portal dev login emulator
14
+ idpsInfo?: {
15
+ idpId: string;
16
+ type: string; // AuthProviderType
17
+ title: string | undefined;
18
+ }[];
19
+ devLogin?: boolean;
20
+ loginUrls?: Record<string, string>;
21
+ };
22
+ search?: {
23
+ shortcuts?: string[];
24
+ suggestedPages?: any[];
25
+ };
26
+ breadcrumbs?: {
27
+ prefixItems?: ResolvedNavLinkItem[];
28
+ };
29
+ products?: {
30
+ [key: string]: ProductUiConfig;
31
+ };
32
+ };
33
+
34
+ export type ResolvedNavLinkItem = {
35
+ type: 'link';
36
+ fsPath?: string;
37
+ metadata?: Record<string, unknown>;
38
+ link: string;
39
+ label: string;
40
+ labelTranslationKey?: string;
41
+ items?: ResolvedNavItem[];
42
+ sidebar?: ResolvedNavItem[]; // for API catalog it contains the corresponding sidebar first item
43
+ external?: boolean;
44
+ target?: '_self' | '_blank';
45
+
46
+ version?: string;
47
+ isDefault?: boolean;
48
+ versionFolderId?: string;
49
+
50
+ httpVerb?: string;
51
+ separatorLine?: boolean;
52
+ routeSlug?: string;
53
+ active?: boolean;
54
+ icon?: string;
55
+ srcSet?: string;
56
+ [REDOCLY_TEAMS_RBAC]?: RbacScopeItems;
57
+
58
+ linkedSidebars?: string[];
59
+ languageInsensitive?: boolean;
60
+ };
61
+
62
+ export type ResolvedNavGroupItem = {
63
+ type: 'group';
64
+ fsPath?: string;
65
+ metadata?: Record<string, unknown>;
66
+ link?: string;
67
+ label?: string;
68
+ labelTranslationKey?: string;
69
+ items?: ResolvedNavItem[];
70
+ sidebar?: ResolvedNavItem[]; // for API catalog it contains the corresponding sidebar first item
71
+ external?: boolean;
72
+ target?: '_self' | '_blank';
73
+ expanded?: string;
74
+ selectFirstItemOnExpand?: boolean;
75
+
76
+ version?: string;
77
+ isDefault?: boolean;
78
+ versionFolderId?: string;
79
+
80
+ menuStyle?: MenuStyle;
81
+ separatorLine?: boolean;
82
+ routeSlug?: string;
83
+ active?: boolean;
84
+ icon?: string;
85
+ srcSet?: string;
86
+ [REDOCLY_TEAMS_RBAC]?: RbacScopeItems;
87
+
88
+ linkedSidebars?: string[];
89
+ languageInsensitive?: boolean;
90
+ };
91
+
92
+ export type ResolvedNavItem =
93
+ | ResolvedNavLinkItem
94
+ | ResolvedNavGroupItem
95
+ | {
96
+ type: 'separator';
97
+ fsPath?: never;
98
+ metadata?: Record<string, unknown>;
99
+ label?: string;
100
+ labelTranslationKey?: string;
101
+ routeSlug?: never;
102
+
103
+ version?: string;
104
+ isDefault?: boolean;
105
+ versionFolderId?: string;
106
+
107
+ separatorLine?: boolean;
108
+ linePosition?: 'top' | 'bottom';
109
+ link?: undefined;
110
+ items?: ResolvedNavItem[]; // for typescript
111
+ sidebar?: ResolvedNavItem[]; // for typescript
112
+ linkedSidebars?: string[]; // for typescript
113
+ icon?: string;
114
+ srcSet?: string;
115
+ [REDOCLY_TEAMS_RBAC]?: RbacScopeItems;
116
+
117
+ languageInsensitive?: boolean;
118
+ }
119
+ | {
120
+ type: 'error';
121
+
122
+ fsPath?: never;
123
+ version?: string;
124
+ isDefault?: boolean;
125
+ versionFolderId?: string;
126
+ metadata?: Record<string, unknown>;
127
+ routeSlug?: never;
128
+
129
+ label: string;
130
+ labelTranslationKey?: string;
131
+ link?: undefined;
132
+ items?: ResolvedNavItem[]; // for typescript
133
+ sidebar?: ResolvedNavItem[]; // for typescript
134
+ linkedSidebars?: string[]; // for typescript
135
+ [REDOCLY_TEAMS_RBAC]?: RbacScopeItems;
136
+
137
+ icon?: string; // for typescript
138
+ srcSet?: string;
139
+ languageInsensitive?: boolean;
140
+ };
141
+
142
+ export type ResolvedNavItemWithLink = (ResolvedNavLinkItem | ResolvedNavGroupItem) & {
143
+ link: string;
144
+ };
145
+
146
+ export type ResolvedSidebar = {
147
+ relatedNavbarItem?: Breadcrumb;
148
+ items: ResolvedNavItem[];
149
+ };
150
+
151
+ export interface PageProps {
152
+ metadata?: Record<string, unknown>;
153
+ seo?: SeoConfig;
154
+ frontmatter?: Omit<PageProps, 'frontmatter'> & {
155
+ theme?: any;
156
+ settings?: any;
157
+ };
158
+ disableAutoScroll?: boolean;
159
+ lastModified?: string | null;
160
+ [k: string]: unknown;
161
+ dynamicMarkdocComponents?: string[];
162
+ markdown?: MdOptions;
163
+ openapiOptions?: OpenAPIOptions;
164
+ definitionId?: string;
165
+ variables?: {
166
+ lang?: string;
167
+ rbac?: {
168
+ teams: string[];
169
+ };
170
+ };
171
+ }
172
+
173
+ export interface MdOptions {
174
+ partials: Record<string, Node>;
175
+ variables?: Record<string, any>;
176
+ }
177
+
178
+ export interface PageStaticData {
179
+ props?: PageProps;
180
+ [k: string]: unknown;
181
+ }
182
+
183
+ export type UserData = {
184
+ isAuthenticated: boolean;
185
+ name: string;
186
+ picture: string;
187
+ logoutDisabled?: boolean;
188
+ };
189
+
190
+ export interface PageData {
191
+ templateId: string;
192
+ redirectTo?: string;
193
+ sharedDataIds: Record<string, string>;
194
+ props: PageProps;
195
+ versions?: Version[];
196
+ userData: UserData;
197
+ }
198
+
199
+ export type NavItem = {
200
+ page?: string;
201
+ directory?: string;
202
+ group?: string;
203
+ groupTranslationKey?: string;
204
+ label?: string;
205
+ labelTranslationKey?: string;
206
+ href?: string;
207
+ items?: NavItem[];
208
+ separator?: string;
209
+ separatorTranslationKey?: string;
210
+ separatorLine?: boolean;
211
+ linePosition?: 'top' | 'bottom';
212
+ version?: string;
213
+ menuStyle?: MenuStyle;
214
+ external?: boolean;
215
+ target?: '_self' | '_blank';
216
+ expanded?: boolean | 'always';
217
+ selectFirstItemOnExpand?: boolean;
218
+ flatten?: boolean; // for API catalog only
219
+ icon?: string | { srcSet: string };
220
+ rbac?: RbacScopeItems;
221
+ linkedSidebars?: string[];
222
+ $ref?: string;
223
+ disconnect?: boolean;
224
+ };
225
+
226
+ export interface LogoConfig {
227
+ image?: string;
228
+ srcSet?: string;
229
+ altText?: string;
230
+ link?: string;
231
+ favicon?: string;
232
+ }
233
+
234
+ export type Version = {
235
+ version: string;
236
+ label: string;
237
+ link: string;
238
+ default: boolean;
239
+ active: boolean;
240
+ notExists?: boolean;
241
+ folderId: string;
242
+ [REDOCLY_TEAMS_RBAC]?: RbacScopeItems;
243
+ };
244
+
245
+ export type VersionConfigItem = {
246
+ version: string;
247
+ name?: string;
248
+ };
249
+
250
+ export type VersionsConfigType = {
251
+ versions: VersionConfigItem[];
252
+ default?: string;
253
+ };
254
+
255
+ export type VersionedFolderConfig = {
256
+ // key is a relative path to file inside of a versioned folder, value is an array of versions
257
+ versionedFiles: Map<string, Set<string>>;
258
+
259
+ defaultVersion?: string;
260
+ versions: VersionConfigItem[];
261
+ hasVersionsConfig?: boolean; // if versions.yaml file exists
262
+ };
263
+
264
+ export type NavGroup = ResolvedNavItem[] | undefined | string | boolean | number;
265
+
266
+ export type NavGroupRecord = Record<string, NavGroup>;
267
+
268
+ export type ResolvedConfigLinks = NavGroup | NavGroupRecord;
269
+
270
+ export type RawNavConfigItem = NavItem | NavItem[] | string | undefined | boolean | number;
271
+
272
+ export type RawNavConfig = RawNavConfigItem | Record<string, RawNavConfigItem>;
273
+
274
+ export type OpenAPIOptions = {
275
+ showRightPanelToggle?: boolean;
276
+ layout?: LayoutVariant;
277
+ collapsedSidebar?: boolean;
278
+ };
279
+
280
+ export type MenuStyle = 'drilldown';
281
+
282
+ export type Breadcrumb = {
283
+ label: string;
284
+ link?: string;
285
+ };
286
+
287
+ export type ProductThemeOverrideConfig = Pick<
288
+ ThemeUIConfig,
289
+ 'logo' | 'navbar' | 'footer' | 'sidebar' | 'search' | 'codeSnippet' | 'breadcrumbs'
290
+ > & { analytics?: { ga?: ProductGoogleAnalyticsConfig } };
291
+
292
+ export type ProductUiConfig = ProductConfig & {
293
+ slug: string;
294
+ link: string;
295
+ [REDOCLY_TEAMS_RBAC]?: { [key: string]: string };
296
+ themeOverride?: ProductThemeOverrideConfig;
297
+ };
@@ -0,0 +1,389 @@
1
+ import {
2
+ DEFAULT_TEAM_CLAIM_NAME,
3
+ AuthProviderType,
4
+ ApigeeDevOnboardingIntegrationAuthType,
5
+ } from './constants';
6
+ import { themeConfigSchema } from './default-theme-config-schema';
7
+
8
+ export const oidcIssuerMetadataSchema = {
9
+ type: 'object',
10
+ properties: {
11
+ end_session_endpoint: { type: 'string' },
12
+ token_endpoint: { type: 'string' },
13
+ authorization_endpoint: { type: 'string' },
14
+ jwks_uri: { type: 'string' },
15
+ },
16
+ required: ['token_endpoint', 'authorization_endpoint'],
17
+ additionalProperties: true,
18
+ } as const;
19
+
20
+ export const oidcProviderConfigSchema = {
21
+ type: 'object',
22
+ properties: {
23
+ type: { type: 'string', const: AuthProviderType.OIDC },
24
+ title: { type: 'string' },
25
+ pkce: { type: 'boolean', default: false },
26
+ configurationUrl: { type: 'string', minLength: 1 },
27
+ configuration: oidcIssuerMetadataSchema,
28
+ clientId: { type: 'string', minLength: 1 },
29
+ clientSecret: { type: 'string', minLength: 0 },
30
+ teamsClaimName: { type: 'string' },
31
+ teamsClaimMap: { type: 'object', additionalProperties: { type: 'string' } },
32
+ defaultTeams: { type: 'array', items: { type: 'string' } },
33
+ scopes: { type: 'array', items: { type: 'string' } },
34
+ tokenExpirationTime: { type: 'number' },
35
+ authorizationRequestCustomParams: { type: 'object', additionalProperties: { type: 'string' } },
36
+ tokenRequestCustomParams: { type: 'object', additionalProperties: { type: 'string' } },
37
+ audience: { type: 'array', items: { type: 'string' } },
38
+ },
39
+ required: ['type', 'clientId'],
40
+ oneOf: [{ required: ['configurationUrl'] }, { required: ['configuration'] }],
41
+ additionalProperties: false,
42
+ } as const;
43
+
44
+ export const saml2ProviderConfigSchema = {
45
+ type: 'object',
46
+ properties: {
47
+ type: { type: 'string', const: AuthProviderType.SAML2 },
48
+ title: { type: 'string' },
49
+ issuerId: { type: 'string' },
50
+ entityId: { type: 'string' },
51
+ ssoUrl: { type: 'string' },
52
+ x509PublicCert: { type: 'string' },
53
+ teamsAttributeName: { type: 'string', default: DEFAULT_TEAM_CLAIM_NAME },
54
+ teamsAttributeMap: { type: 'object', additionalProperties: { type: 'string' } },
55
+ defaultTeams: { type: 'array', items: { type: 'string' } },
56
+ },
57
+ additionalProperties: false,
58
+ required: ['type', 'issuerId', 'ssoUrl', 'x509PublicCert'],
59
+ } as const;
60
+
61
+ export const basicAuthProviderConfigSchema = {
62
+ type: 'object',
63
+ properties: {
64
+ type: { type: 'string', const: AuthProviderType.BASIC },
65
+ title: { type: 'string' },
66
+ credentials: {
67
+ type: 'array',
68
+ items: {
69
+ type: 'object',
70
+ properties: {
71
+ username: { type: 'string' },
72
+ password: { type: 'string' },
73
+ passwordHash: { type: 'string' },
74
+ teams: { type: 'array', items: { type: 'string' } },
75
+ },
76
+ required: ['username'],
77
+ additionalProperties: false,
78
+ },
79
+ },
80
+ },
81
+ required: ['type', 'credentials'],
82
+ additionalProperties: false,
83
+ } as const;
84
+
85
+ export const authProviderConfigSchema = {
86
+ oneOf: [oidcProviderConfigSchema, saml2ProviderConfigSchema, basicAuthProviderConfigSchema],
87
+ discriminator: { propertyName: 'type' },
88
+ } as const;
89
+
90
+ export const ssoOnPremConfigSchema = {
91
+ type: 'object',
92
+ additionalProperties: authProviderConfigSchema,
93
+ } as const;
94
+
95
+ export const ssoConfigSchema = {
96
+ oneOf: [
97
+ {
98
+ type: 'array',
99
+ items: {
100
+ type: 'string',
101
+ enum: ['REDOCLY', 'CORPORATE', 'GUEST'],
102
+ },
103
+ uniqueItems: true,
104
+ },
105
+ {
106
+ type: 'string',
107
+ enum: ['REDOCLY', 'CORPORATE', 'GUEST'],
108
+ },
109
+ ],
110
+ } as const;
111
+
112
+ export const redirectConfigSchema = {
113
+ type: 'object',
114
+ properties: {
115
+ to: { type: 'string' },
116
+ type: { type: 'number', default: 301 },
117
+ },
118
+ additionalProperties: false,
119
+ } as const;
120
+
121
+ export const redirectsConfigSchema = {
122
+ type: 'object',
123
+ additionalProperties: redirectConfigSchema,
124
+ default: {},
125
+ } as const;
126
+
127
+ export const apiConfigSchema = {
128
+ type: 'object',
129
+ properties: {
130
+ root: { type: 'string' },
131
+ output: { type: 'string', pattern: '(.ya?ml|.json)$' },
132
+ rbac: { type: 'object', additionalProperties: true },
133
+ theme: {
134
+ type: 'object',
135
+ properties: {
136
+ openapi: themeConfigSchema.properties.openapi,
137
+ graphql: themeConfigSchema.properties.graphql,
138
+ },
139
+ additionalProperties: false,
140
+ },
141
+ title: { type: 'string' },
142
+ metadata: { type: 'object', additionalProperties: true },
143
+ rules: { type: 'object', additionalProperties: true },
144
+ decorators: { type: 'object', additionalProperties: true },
145
+ preprocessors: { type: 'object', additionalProperties: true },
146
+ },
147
+ required: ['root'],
148
+ } as const;
149
+
150
+ const metadataConfigSchema = {
151
+ type: 'object',
152
+ additionalProperties: true,
153
+ } as const;
154
+
155
+ export const seoConfigSchema = {
156
+ type: 'object',
157
+ properties: {
158
+ title: { type: 'string' },
159
+ description: { type: 'string' },
160
+ siteUrl: { type: 'string' },
161
+ image: { type: 'string' },
162
+ keywords: {
163
+ oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }],
164
+ },
165
+ lang: { type: 'string' },
166
+ jsonLd: { type: 'object' },
167
+ meta: {
168
+ type: 'array',
169
+ items: {
170
+ type: 'object',
171
+ properties: {
172
+ name: { type: 'string' },
173
+ content: { type: 'string' },
174
+ },
175
+ required: ['name', 'content'],
176
+ additionalProperties: false,
177
+ },
178
+ },
179
+ },
180
+ additionalProperties: false,
181
+ } as const;
182
+
183
+ export const rbacScopeItemsSchema = {
184
+ type: 'object',
185
+ additionalProperties: { type: 'string' },
186
+ } as const;
187
+
188
+ export const rbacConfigSchema = {
189
+ type: 'object',
190
+ properties: {
191
+ cms: rbacScopeItemsSchema, // deprecated in favor of reunite
192
+ reunite: rbacScopeItemsSchema,
193
+ content: {
194
+ type: 'object',
195
+ properties: {
196
+ '**': rbacScopeItemsSchema,
197
+ },
198
+ additionalProperties: rbacScopeItemsSchema,
199
+ },
200
+ },
201
+ additionalProperties: rbacScopeItemsSchema,
202
+ } as const;
203
+
204
+ export const graviteeAdapterConfigSchema = {
205
+ type: 'object',
206
+ properties: {
207
+ type: { type: 'string', const: 'GRAVITEE' },
208
+ apiBaseUrl: { type: 'string' },
209
+ env: { type: 'string' },
210
+ allowApiProductsOutsideCatalog: { type: 'boolean', default: false },
211
+ stage: { type: 'string', default: 'non-production' },
212
+
213
+ auth: { type: 'object', properties: { static: { type: 'string' } } },
214
+ },
215
+ additionalProperties: false,
216
+ required: ['type', 'apiBaseUrl'],
217
+ } as const;
218
+
219
+ export const apigeeAdapterAuthOauth2Schema = {
220
+ type: 'object',
221
+ properties: {
222
+ type: { type: 'string', const: ApigeeDevOnboardingIntegrationAuthType.OAUTH2 },
223
+ tokenEndpoint: { type: 'string' },
224
+ clientId: { type: 'string' },
225
+ clientSecret: { type: 'string' },
226
+ },
227
+ additionalProperties: false,
228
+ required: ['type', 'tokenEndpoint', 'clientId', 'clientSecret'],
229
+ } as const;
230
+
231
+ export const apigeeAdapterAuthServiceAccountSchema = {
232
+ type: 'object',
233
+ properties: {
234
+ type: { type: 'string', const: ApigeeDevOnboardingIntegrationAuthType.SERVICE_ACCOUNT },
235
+ serviceAccountEmail: { type: 'string' },
236
+ serviceAccountPrivateKey: { type: 'string' },
237
+ },
238
+ additionalProperties: false,
239
+ required: ['type', 'serviceAccountEmail', 'serviceAccountPrivateKey'],
240
+ } as const;
241
+
242
+ export const apigeeXAdapterConfigSchema = {
243
+ type: 'object',
244
+ properties: {
245
+ type: { type: 'string', const: 'APIGEE_X' },
246
+ apiUrl: { type: 'string' },
247
+ stage: { type: 'string', default: 'non-production' },
248
+ organizationName: { type: 'string' },
249
+ ignoreApiProducts: { type: 'array', items: { type: 'string' } },
250
+ allowApiProductsOutsideCatalog: { type: 'boolean', default: false },
251
+ auth: {
252
+ type: 'object',
253
+ oneOf: [apigeeAdapterAuthOauth2Schema, apigeeAdapterAuthServiceAccountSchema],
254
+ discriminator: { propertyName: 'type' },
255
+ },
256
+ },
257
+ additionalProperties: false,
258
+ required: ['type', 'organizationName', 'auth'],
259
+ } as const;
260
+
261
+ export const apigeeEdgeAdapterConfigSchema = {
262
+ ...apigeeXAdapterConfigSchema,
263
+ properties: {
264
+ ...apigeeXAdapterConfigSchema.properties,
265
+ type: { type: 'string', const: 'APIGEE_EDGE' },
266
+ },
267
+ } as const;
268
+
269
+ export const devOnboardingAdapterConfigSchema = {
270
+ type: 'object',
271
+ oneOf: [apigeeXAdapterConfigSchema, apigeeEdgeAdapterConfigSchema, graviteeAdapterConfigSchema],
272
+ discriminator: { propertyName: 'type' },
273
+ } as const;
274
+
275
+ const devOnboardingConfigSchema = {
276
+ type: 'object',
277
+ required: ['adapters'],
278
+ additionalProperties: false,
279
+ properties: {
280
+ adapters: {
281
+ type: 'array',
282
+ items: devOnboardingAdapterConfigSchema,
283
+ },
284
+ },
285
+ } as const;
286
+
287
+ export const i18ConfigSchema = {
288
+ type: 'object',
289
+ properties: {
290
+ defaultLocale: {
291
+ type: 'string',
292
+ },
293
+ locales: {
294
+ type: 'array',
295
+ items: {
296
+ type: 'object',
297
+ properties: {
298
+ code: {
299
+ type: 'string',
300
+ },
301
+ name: {
302
+ type: 'string',
303
+ },
304
+ },
305
+ required: ['code'],
306
+ },
307
+ },
308
+ },
309
+ additionalProperties: false,
310
+ required: ['defaultLocale'],
311
+ } as const;
312
+
313
+ const responseHeaderSchema = {
314
+ type: 'object',
315
+ properties: {
316
+ name: { type: 'string' },
317
+ value: { type: 'string' },
318
+ },
319
+ additionalProperties: false,
320
+ required: ['name', 'value'],
321
+ } as const;
322
+
323
+ export const redoclyConfigSchema = {
324
+ type: 'object',
325
+ properties: {
326
+ licenseKey: { type: 'string' },
327
+ redirects: redirectsConfigSchema,
328
+ seo: seoConfigSchema,
329
+ rbac: rbacConfigSchema,
330
+ responseHeaders: {
331
+ type: 'object',
332
+ additionalProperties: {
333
+ type: 'array',
334
+ items: responseHeaderSchema,
335
+ },
336
+ },
337
+ mockServer: {
338
+ type: 'object',
339
+ properties: {
340
+ off: { type: 'boolean', default: false },
341
+ position: { type: 'string', enum: ['first', 'last', 'replace', 'off'], default: 'first' },
342
+ strictExamples: { type: 'boolean', default: false },
343
+ errorIfForcedExampleNotFound: { type: 'boolean', default: false },
344
+ description: { type: 'string' },
345
+ },
346
+ },
347
+ apis: {
348
+ type: 'object',
349
+ additionalProperties: apiConfigSchema,
350
+ },
351
+ ssoOnPrem: ssoOnPremConfigSchema,
352
+ sso: ssoConfigSchema,
353
+ residency: { type: 'string' },
354
+ developerOnboarding: devOnboardingConfigSchema,
355
+ i18n: i18ConfigSchema,
356
+ metadata: metadataConfigSchema,
357
+ ignore: {
358
+ type: 'array',
359
+ items: {
360
+ type: 'string',
361
+ },
362
+ },
363
+ theme: themeConfigSchema,
364
+ },
365
+ default: { redirects: {} },
366
+ additionalProperties: true,
367
+ } as const;
368
+
369
+ const environmentSchema = {
370
+ ...redoclyConfigSchema,
371
+ additionalProperties: false,
372
+ } as const;
373
+
374
+ export const rootRedoclyConfigSchema = {
375
+ ...redoclyConfigSchema,
376
+ properties: {
377
+ plugins: {
378
+ type: 'array',
379
+ items: { type: 'string' },
380
+ },
381
+ ...redoclyConfigSchema.properties,
382
+ env: {
383
+ type: 'object',
384
+ additionalProperties: environmentSchema, // TODO: if we want full validation we need to override apis, theme and the root
385
+ },
386
+ },
387
+ default: {},
388
+ additionalProperties: false,
389
+ } as const;