diff --git a/__tests__/lint-config/config-structure/snapshot.js b/__tests__/lint-config/config-structure/snapshot.js index e595bd88..a093dcb7 100644 --- a/__tests__/lint-config/config-structure/snapshot.js +++ b/__tests__/lint-config/config-structure/snapshot.js @@ -580,21 +580,7 @@ Expected type \`string\` but got \`integer\`. Error was generated by the configuration spec rule. -[41] .redocly.yaml:192:13 at #/theme/footer/items/0/items/0/href - -Property \`href\` is not expected here. - -190 | items: -191 | - label: Terms of Use -192 | href: 'https://test.com/' # Not expected - | ^^^^ -193 | -194 | markdown: - -Error was generated by the configuration spec rule. - - -[42] .redocly.yaml:141:12 at #/env/some-env/mockServer/off +[41] .redocly.yaml:141:12 at #/env/some-env/mockServer/off Expected type \`boolean\` but got \`string\`. @@ -608,7 +594,7 @@ Expected type \`boolean\` but got \`string\`. Error was generated by the configuration spec rule. -[43] .redocly.yaml:142:7 at #/env/some-env/mockServer/not-expected +[42] .redocly.yaml:142:7 at #/env/some-env/mockServer/not-expected Property \`not-expected\` is not expected here. @@ -622,7 +608,7 @@ Property \`not-expected\` is not expected here. Error was generated by the configuration spec rule. -[44] .redocly.yaml:144:7 at #/env/some-env/apis/no-root +[43] .redocly.yaml:144:7 at #/env/some-env/apis/no-root The field \`root\` must be present on this level. @@ -636,7 +622,7 @@ The field \`root\` must be present on this level. Error was generated by the configuration spec rule. -[45] .redocly.yaml:148:15 at #/env/some-env/apis/wrong-root/root +[44] .redocly.yaml:148:15 at #/env/some-env/apis/wrong-root/root Expected type \`string\` but got \`integer\`. @@ -650,7 +636,7 @@ Expected type \`string\` but got \`integer\`. Error was generated by the configuration spec rule. -[46] env.yaml:2:5 at #/ignore/0 +[45] env.yaml:2:5 at #/ignore/0 Expected type \`string\` but got \`integer\`. @@ -665,7 +651,7 @@ referenced from .redocly.yaml:151:5 at #/env/referenced-env Error was generated by the configuration spec rule. -❌ Your config has 46 errors. +❌ Your config has 45 errors. `; diff --git a/package-lock.json b/package-lock.json index e48e07b0..13f9ea2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3501,6 +3501,11 @@ "resolved": "packages/cli", "link": true }, + "node_modules/@redocly/config": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.1.1.tgz", + "integrity": "sha512-nliB8VES4OED+HtfYf0Z4U72i2fj++xfqkDcYEinU3bjDERL2gvUgeqCUoavEmLU39+CKo1fV7saTSze6trBNA==" + }, "node_modules/@redocly/openapi-core": { "resolved": "packages/core", "link": true @@ -12944,6 +12949,7 @@ "license": "MIT", "dependencies": { "@redocly/ajv": "^8.11.0", + "@redocly/config": "^0.1.1", "colorette": "^1.2.0", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", @@ -15598,10 +15604,16 @@ } } }, + "@redocly/config": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.1.1.tgz", + "integrity": "sha512-nliB8VES4OED+HtfYf0Z4U72i2fj++xfqkDcYEinU3bjDERL2gvUgeqCUoavEmLU39+CKo1fV7saTSze6trBNA==" + }, "@redocly/openapi-core": { "version": "file:packages/core", "requires": { "@redocly/ajv": "^8.11.0", + "@redocly/config": "^0.1.1", "@types/js-levenshtein": "^1.1.0", "@types/js-yaml": "^4.0.3", "@types/lodash.isequal": "^4.5.5", diff --git a/packages/core/package.json b/packages/core/package.json index e9bf0c79..d6aea9f9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,6 +35,7 @@ ], "dependencies": { "@redocly/ajv": "^8.11.0", + "@redocly/config": "^0.1.1", "colorette": "^1.2.0", "js-levenshtein": "^1.1.6", "js-yaml": "^4.1.0", diff --git a/packages/core/src/__tests__/lint.test.ts b/packages/core/src/__tests__/lint.test.ts index 4ce2e1f2..0b17df85 100644 --- a/packages/core/src/__tests__/lint.test.ts +++ b/packages/core/src/__tests__/lint.test.ts @@ -6,7 +6,7 @@ import { BaseResolver } from '../resolve'; import { loadConfig } from '../config/load'; import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../__tests__/utils'; import { detectSpec } from '../oas-types'; -import { themeConfigSchema } from '../types/theme-config'; +import { rootRedoclyConfigSchema } from '@redocly/config'; import { createConfigTypes } from '../types/redocly-yaml'; const testPortalConfig = parseYamlToDocument( @@ -1124,20 +1124,6 @@ describe('lint', () => { "severity": "error", "suggest": [], }, - { - "from": undefined, - "location": [ - { - "pointer": "#/theme/footer/items/0/items/0/href", - "reportOnKey": true, - "source": "", - }, - ], - "message": "Property \`href\` is not expected here.", - "ruleId": "configuration spec", - "severity": "error", - "suggest": [], - }, { "from": undefined, "location": [ @@ -1204,7 +1190,7 @@ describe('lint', () => { document, externalConfigTypes: createConfigTypes({ type: 'object', - properties: { theme: themeConfigSchema }, + properties: { theme: rootRedoclyConfigSchema.properties.theme }, additionalProperties: false, }), }); @@ -1379,20 +1365,6 @@ describe('lint', () => { "severity": "error", "suggest": [], }, - { - "from": undefined, - "location": [ - { - "pointer": "#/theme/footer/items/0/items/0/href", - "reportOnKey": true, - "source": "", - }, - ], - "message": "Property \`href\` is not expected here.", - "ruleId": "configuration spec", - "severity": "error", - "suggest": [], - }, { "from": undefined, "location": [ diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts index 84bf330b..828c17b6 100644 --- a/packages/core/src/config/types.ts +++ b/packages/core/src/config/types.ts @@ -243,16 +243,3 @@ export type RulesFields = | 'oas3_0Decorators' | 'oas3_1Decorators' | 'async2Decorators'; - -export enum AuthProviderType { - OIDC = 'OIDC', - SAML2 = 'SAML2', - BASIC = 'BASIC', -} - -export enum ApigeeDevOnboardingIntegrationAuthType { - SERVICE_ACCOUNT = 'SERVICE_ACCOUNT', - OAUTH2 = 'OAUTH2', -} - -export const DEFAULT_TEAM_CLAIM_NAME = 'https://redocly.com/sso/teams'; diff --git a/packages/core/src/types/portal-config-schema.ts b/packages/core/src/types/portal-config-schema.ts deleted file mode 100644 index ab1e2b2d..00000000 --- a/packages/core/src/types/portal-config-schema.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { - ApigeeDevOnboardingIntegrationAuthType, - AuthProviderType, - DEFAULT_TEAM_CLAIM_NAME, -} from '../config'; -import { themeConfigSchema } from './theme-config'; - -import type { FromSchema } from 'json-schema-to-ts'; -import type { ThemeConfig } from './theme-config'; - -const oidcIssuerMetadataSchema = { - type: 'object', - properties: { - end_session_endpoint: { type: 'string' }, - token_endpoint: { type: 'string' }, - authorization_endpoint: { type: 'string' }, - jwks_uri: { type: 'string' }, - }, - required: ['token_endpoint', 'authorization_endpoint'], - additionalProperties: true, -} as const; - -const oidcProviderConfigSchema = { - type: 'object', - properties: { - type: { type: 'string', const: AuthProviderType.OIDC }, - title: { type: 'string' }, - pkce: { type: 'boolean', default: false }, - configurationUrl: { type: 'string', minLength: 1 }, - configuration: oidcIssuerMetadataSchema, - clientId: { type: 'string', minLength: 1 }, - clientSecret: { type: 'string', minLength: 0 }, - teamsClaimName: { type: 'string' }, - teamsClaimMap: { type: 'object', additionalProperties: { type: 'string' } }, - defaultTeams: { type: 'array', items: { type: 'string' } }, - scopes: { type: 'array', items: { type: 'string' } }, - tokenExpirationTime: { type: 'number' }, - authorizationRequestCustomParams: { type: 'object', additionalProperties: { type: 'string' } }, - tokenRequestCustomParams: { type: 'object', additionalProperties: { type: 'string' } }, - audience: { type: 'array', items: { type: 'string' } }, - }, - required: ['type', 'clientId'], - oneOf: [{ required: ['configurationUrl'] }, { required: ['configuration'] }], - additionalProperties: false, -} as const; - -const saml2ProviderConfigSchema = { - type: 'object', - properties: { - type: { type: 'string', const: AuthProviderType.SAML2 }, - title: { type: 'string' }, - issuerId: { type: 'string' }, - entityId: { type: 'string' }, - ssoUrl: { type: 'string' }, - x509PublicCert: { type: 'string' }, - teamsAttributeName: { type: 'string', default: DEFAULT_TEAM_CLAIM_NAME }, - teamsAttributeMap: { type: 'object', additionalProperties: { type: 'string' } }, - defaultTeams: { type: 'array', items: { type: 'string' } }, - }, - additionalProperties: false, - required: ['type', 'issuerId', 'ssoUrl', 'x509PublicCert'], -} as const; - -const basicAuthProviderConfigSchema = { - type: 'object', - properties: { - type: { type: 'string', const: AuthProviderType.BASIC }, - title: { type: 'string' }, - credentials: { - type: 'array', - items: { - type: 'object', - properties: { - username: { type: 'string' }, - password: { type: 'string' }, - passwordHash: { type: 'string' }, - teams: { type: 'array', items: { type: 'string' } }, - }, - required: ['username'], - additionalProperties: false, - }, - }, - }, - required: ['type', 'credentials'], - additionalProperties: false, -} as const; - -const authProviderConfigSchema = { - oneOf: [oidcProviderConfigSchema, saml2ProviderConfigSchema, basicAuthProviderConfigSchema], - discriminator: { propertyName: 'type' }, -} as const; - -const ssoOnPremConfigSchema = { - type: 'object', - additionalProperties: authProviderConfigSchema, -} as const; - -const ssoConfigSchema = { - oneOf: [ - { - type: 'array', - items: { - type: 'string', - enum: ['REDOCLY', 'CORPORATE', 'GUEST'], - }, - uniqueItems: true, - }, - { - type: 'string', - enum: ['REDOCLY', 'CORPORATE', 'GUEST'], - }, - ], -} as const; - -const redirectConfigSchema = { - type: 'object', - properties: { - to: { type: 'string' }, - type: { type: 'number', default: 301 }, - }, - additionalProperties: false, -} as const; - -const redirectsConfigSchema = { - type: 'object', - additionalProperties: redirectConfigSchema, - default: {}, -} as const; - -const apiConfigSchema = { - type: 'object', - properties: { - root: { type: 'string' }, - output: { type: 'string', pattern: '(.ya?ml|.json)$' }, - rbac: { type: 'object', additionalProperties: true }, - theme: { - type: 'object', - properties: { - openapi: themeConfigSchema.properties.openapi, - graphql: themeConfigSchema.properties.graphql, - }, - additionalProperties: false, - }, - title: { type: 'string' }, - metadata: { type: 'object', additionalProperties: true }, - rules: { type: 'object', additionalProperties: true }, - decorators: { type: 'object', additionalProperties: true }, - }, - required: ['root'], -} as const; - -const metadataConfigSchema = { - type: 'object', - additionalProperties: true, -} as const; - -const seoConfigSchema = { - type: 'object', - properties: { - title: { type: 'string' }, - description: { type: 'string' }, - siteUrl: { type: 'string' }, - image: { type: 'string' }, - keywords: { - oneOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }], - }, - lang: { type: 'string' }, - jsonLd: { type: 'object' }, - meta: { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - content: { type: 'string' }, - }, - required: ['name', 'content'], - additionalProperties: false, - }, - }, - }, - additionalProperties: false, -} as const; - -const rbacScopeItemsSchema = { type: 'object', additionalProperties: { type: 'string' } } as const; - -const rbacConfigSchema = { - type: 'object', - properties: { - cms: rbacScopeItemsSchema, - content: { - type: 'object', - properties: { - '**': rbacScopeItemsSchema, - }, - additionalProperties: rbacScopeItemsSchema, - }, - }, - additionalProperties: rbacScopeItemsSchema, -} as const; - -const graviteeAdapterConfigSchema = { - type: 'object', - properties: { - type: { type: 'string', const: 'GRAVITEE' }, - apiBaseUrl: { type: 'string' }, - env: { type: 'string' }, - allowApiProductsOutsideCatalog: { type: 'boolean', default: false }, - stage: { type: 'string', default: 'non-production' }, - - auth: { type: 'object', properties: { static: { type: 'string' } } }, - }, - additionalProperties: false, - required: ['type', 'apiBaseUrl'], -} as const; - -const apigeeAdapterAuthOauth2Schema = { - type: 'object', - properties: { - type: { type: 'string', const: ApigeeDevOnboardingIntegrationAuthType.OAUTH2 }, - tokenEndpoint: { type: 'string' }, - clientId: { type: 'string' }, - clientSecret: { type: 'string' }, - }, - additionalProperties: false, - required: ['type', 'tokenEndpoint', 'clientId', 'clientSecret'], -} as const; - -const apigeeAdapterAuthServiceAccountSchema = { - type: 'object', - properties: { - type: { type: 'string', const: ApigeeDevOnboardingIntegrationAuthType.SERVICE_ACCOUNT }, - serviceAccountEmail: { type: 'string' }, - serviceAccountPrivateKey: { type: 'string' }, - }, - additionalProperties: false, - required: ['type', 'serviceAccountEmail', 'serviceAccountPrivateKey'], -} as const; - -const apigeeXAdapterConfigSchema = { - type: 'object', - properties: { - type: { type: 'string', const: 'APIGEE_X' }, - apiUrl: { type: 'string' }, - stage: { type: 'string', default: 'non-production' }, - organizationName: { type: 'string' }, - ignoreApiProducts: { type: 'array', items: { type: 'string' } }, - allowApiProductsOutsideCatalog: { type: 'boolean', default: false }, - auth: { - type: 'object', - oneOf: [apigeeAdapterAuthOauth2Schema, apigeeAdapterAuthServiceAccountSchema], - discriminator: { propertyName: 'type' }, - }, - }, - additionalProperties: false, - required: ['type', 'organizationName', 'auth'], -} as const; - -const apigeeEdgeAdapterConfigSchema = { - ...apigeeXAdapterConfigSchema, - properties: { - ...apigeeXAdapterConfigSchema.properties, - type: { type: 'string', const: 'APIGEE_EDGE' }, - }, -} as const; - -const devOnboardingAdapterConfigSchema = { - type: 'object', - oneOf: [apigeeXAdapterConfigSchema, apigeeEdgeAdapterConfigSchema, graviteeAdapterConfigSchema], - discriminator: { propertyName: 'type' }, -} as const; - -const devOnboardingConfigSchema = { - type: 'object', - required: ['adapters'], - additionalProperties: false, - properties: { - adapters: { - type: 'array', - items: devOnboardingAdapterConfigSchema, - }, - }, -} as const; - -const i18ConfigSchema = { - type: 'object', - properties: { - defaultLocale: { - type: 'string', - }, - locales: { - type: 'array', - items: { - type: 'object', - properties: { - code: { - type: 'string', - }, - name: { - type: 'string', - }, - }, - required: ['code'], - }, - }, - }, - additionalProperties: false, - required: ['defaultLocale'], -} as const; - -const responseHeaderSchema = { - type: 'object', - properties: { - name: { type: 'string' }, - value: { type: 'string' }, - }, - additionalProperties: false, - required: ['name', 'value'], -} as const; - -const redoclyConfigSchema = { - type: 'object', - properties: { - licenseKey: { type: 'string' }, - redirects: redirectsConfigSchema, - seo: seoConfigSchema, - rbac: rbacConfigSchema, - responseHeaders: { - type: 'object', - additionalProperties: { - type: 'array', - items: responseHeaderSchema, - }, - }, - mockServer: { - type: 'object', - properties: { - off: { type: 'boolean', default: false }, - position: { type: 'string', enum: ['first', 'last', 'replace', 'off'], default: 'first' }, - strictExamples: { type: 'boolean', default: false }, - errorIfForcedExampleNotFound: { type: 'boolean', default: false }, - description: { type: 'string' }, - }, - }, - apis: { - type: 'object', - additionalProperties: apiConfigSchema, - }, - ssoOnPrem: ssoOnPremConfigSchema, - sso: ssoConfigSchema, - residency: { type: 'string' }, - developerOnboarding: devOnboardingConfigSchema, - i18n: i18ConfigSchema, - metadata: metadataConfigSchema, - ignore: { - type: 'array', - items: { - type: 'string', - }, - }, - theme: themeConfigSchema, - }, - default: { redirects: {} }, - additionalProperties: true, -} as const; - -const environmentSchema = { - ...redoclyConfigSchema, - additionalProperties: false, -} as const; - -export const rootRedoclyConfigSchema = { - ...redoclyConfigSchema, - properties: { - plugins: { - type: 'array', - items: { type: 'string' }, - }, - ...redoclyConfigSchema.properties, - env: { - type: 'object', - additionalProperties: environmentSchema, // TODO: if we want full validation we need to override apis, theme and the root - }, - }, - default: {}, - additionalProperties: false, -} as const; - -export type RedoclyConfig = FromSchema & { - theme?: T; -}; -export type RedirectConfig = FromSchema; -export type RedirectsConfig = FromSchema; - -export type AuthProviderConfig = FromSchema; -export type BasicAuthProviderConfig = FromSchema; -export type OidcProviderConfig = FromSchema; -export type Saml2ProviderConfig = FromSchema; -export type SeoConfig = FromSchema; -export type RbacConfig = FromSchema; -export type RbacScopeItems = FromSchema; -export type OidcIssuerMetadata = FromSchema; - -export type DevOnboardingAdapterConfig = FromSchema; -export type GraviteeAdapterConfig = FromSchema; -export type ApigeeAdapterConfig = FromSchema< - typeof apigeeXAdapterConfigSchema | typeof apigeeEdgeAdapterConfigSchema ->; -export type ApigeeAdapterAuthOauth2 = FromSchema; -export type ApigeeAdapterAuthServiceAccount = FromSchema< - typeof apigeeAdapterAuthServiceAccountSchema ->; -export type SsoConfig = FromSchema; -export type I18nConfig = FromSchema; - -export type ApiConfig = FromSchema; diff --git a/packages/core/src/types/redocly-yaml.ts b/packages/core/src/types/redocly-yaml.ts index 9ef36780..da65072d 100644 --- a/packages/core/src/types/redocly-yaml.ts +++ b/packages/core/src/types/redocly-yaml.ts @@ -1,4 +1,4 @@ -import { rootRedoclyConfigSchema } from './portal-config-schema'; +import { rootRedoclyConfigSchema } from '@redocly/config'; import { listOf } from '.'; import { omitObjectProps, pickObjectProps, isCustomRuleId } from '../utils'; import { getNodeTypesFromJSONSchema } from './json-schema-adapter'; diff --git a/packages/core/src/types/theme-config.ts b/packages/core/src/types/theme-config.ts deleted file mode 100644 index ab35a954..00000000 --- a/packages/core/src/types/theme-config.ts +++ /dev/null @@ -1,781 +0,0 @@ -import type { FromSchema } from 'json-schema-to-ts'; - -const logoConfigSchema = { - type: 'object', - properties: { - image: { type: 'string' }, - srcSet: { type: 'string' }, - altText: { type: 'string' }, - link: { type: 'string' }, - favicon: { type: 'string' }, - }, - additionalProperties: false, -} as const; - -const hideConfigSchema = { - type: 'object', - properties: { - hide: { type: 'boolean' }, - }, - additionalProperties: false, -} as const; - -const scriptConfigSchema = { - type: 'object', - properties: { - src: { type: 'string' }, - async: { type: 'boolean' }, - crossorigin: { type: 'string' }, - defer: { type: 'boolean' }, - fetchpriority: { type: 'string' }, - integrity: { type: 'string' }, - module: { type: 'boolean' }, - nomodule: { type: 'boolean' }, - nonce: { type: 'string' }, - referrerpolicy: { type: 'string' }, - type: { type: 'string' }, - }, - required: ['src'], - additionalProperties: true, -} as const; - -const linksConfigSchema = { - type: 'object', - properties: { - href: { type: 'string' }, - as: { type: 'string' }, - crossorigin: { type: 'string' }, - fetchpriority: { type: 'string' }, - hreflang: { type: 'string' }, - imagesizes: { type: 'string' }, - imagesrcset: { type: 'string' }, - integrity: { type: 'string' }, - media: { type: 'string' }, - prefetch: { type: 'string' }, - referrerpolicy: { type: 'string' }, - rel: { type: 'string' }, - sizes: { type: 'string' }, - title: { type: 'string' }, - type: { type: 'string' }, - }, - required: ['href'], - additionalProperties: true, -} as const; - -const markdownConfigSchema = { - type: 'object', - properties: { - frontMatterKeysToResolve: { - type: 'array', - items: { type: 'string' }, - default: ['image', 'links'], - }, - partialsFolders: { - type: 'array', - items: { type: 'string' }, - default: ['_partials'], - }, - lastUpdatedBlock: { - type: 'object', - properties: { - format: { - type: 'string', - enum: ['timeago', 'iso', 'long', 'short'], - default: 'timeago', - }, - locale: { type: 'string' }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - toc: { - type: 'object', - properties: { - header: { type: 'string', default: 'On this page' }, - depth: { type: 'integer', default: 3, minimum: 1 }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - editPage: { - type: 'object', - properties: { - baseUrl: { type: 'string' }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - }, - additionalProperties: false, - default: {}, -} as const; - -const amplitudeAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - apiKey: { type: 'string' }, - head: { type: 'boolean' }, - respectDNT: { type: 'boolean' }, - exclude: { type: 'array', items: { type: 'string' } }, - outboundClickEventName: { type: 'string' }, - pageViewEventName: { type: 'string' }, - amplitudeConfig: { type: 'object', additionalProperties: true }, - }, - additionalProperties: false, - required: ['apiKey'], -} as const; - -const fullstoryAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - orgId: { type: 'string' }, - }, - additionalProperties: false, - required: ['orgId'], -} as const; - -const heapAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - appId: { type: 'string' }, - }, - additionalProperties: false, - required: ['appId'], -} as const; - -const rudderstackAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - writeKey: { type: 'string', minLength: 10 }, - trackPage: { type: 'boolean' }, - dataPlaneUrl: { type: 'string' }, - controlPlaneUrl: { type: 'string' }, - sdkUrl: { type: 'string' }, - loadOptions: { type: 'object', additionalProperties: true }, - }, - additionalProperties: false, - required: ['writeKey'], -} as const; - -const segmentAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - writeKey: { type: 'string', minLength: 10 }, - trackPage: { type: 'boolean' }, - includeTitleInPageCall: { type: 'boolean' }, - host: { type: 'string' }, - }, - additionalProperties: false, - required: ['writeKey'], -} as const; - -const gtmAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - trackingId: { type: 'string' }, - gtmAuth: { type: 'string' }, - gtmPreview: { type: 'string' }, - defaultDataLayer: {}, - dataLayerName: { type: 'string' }, - enableWebVitalsTracking: { type: 'boolean' }, - selfHostedOrigin: { type: 'string' }, - pageViewEventName: { type: 'string' }, - }, - additionalProperties: false, - required: ['trackingId'], -} as const; - -const productGoogleAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - trackingId: { type: 'string' }, - - conversionId: { type: 'string' }, - floodlightId: { type: 'string' }, - optimizeId: { type: 'string' }, - exclude: { type: 'array', items: { type: 'string' } }, - }, - additionalProperties: false, - required: ['trackingId'], -} as const; - -const googleAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - trackingId: { type: 'string' }, - - conversionId: { type: 'string' }, - floodlightId: { type: 'string' }, - - head: { type: 'boolean' }, - respectDNT: { type: 'boolean' }, - exclude: { type: 'array', items: { type: 'string' } }, - - optimizeId: { type: 'string' }, - anonymizeIp: { type: 'boolean' }, - cookieExpires: { type: 'number' }, - - // All enabled tracking configs - trackers: { - type: 'object', - additionalProperties: productGoogleAnalyticsConfigSchema, - }, - }, - additionalProperties: false, - required: ['trackingId'], -} as const; - -const adobeAnalyticsConfigSchema = { - type: 'object', - properties: { - includeInDevelopment: { type: 'boolean' }, - scriptUrl: { type: 'string' }, - pageViewEventName: { type: 'string' }, - }, - additionalProperties: false, - required: ['scriptUrl'], -} as const; - -const navItemSchema = { - type: 'object', - properties: { - page: { type: 'string' }, - directory: { type: 'string' }, - disconnect: { type: 'boolean', default: false }, - group: { type: 'string' }, - label: { type: 'string' }, - separator: { type: 'string' }, - separatorLine: { type: 'boolean' }, - linePosition: { - type: 'string', - enum: ['top', 'bottom'], - default: 'top', - }, - version: { type: 'string' }, - menuStyle: { type: 'string', enum: ['drilldown'] }, - expanded: { type: 'string', const: 'always' }, - selectFirstItemOnExpand: { type: 'boolean' }, - flatten: { type: 'boolean' }, - linkedSidebars: { - type: 'array', - items: { type: 'string' }, - }, - }, -} as const; - -const navItemsSchema = { - type: 'array', - items: { - ...navItemSchema, - properties: { - ...navItemSchema.properties, - items: { type: 'array', items: navItemSchema }, - }, - }, -} as const; - -const productConfigSchema = { - type: 'object', - properties: { - name: { type: 'string' }, - icon: { type: 'string' }, - folder: { type: 'string' }, - }, - additionalProperties: false, - required: ['name', 'folder'], -} as const; - -const suggestedPageSchema = { - type: 'object', - properties: { - page: { type: 'string' }, - label: { type: 'string' }, - labelTranslationKey: { type: 'string' }, - }, - required: ['page'], -} as const; - -const catalogFilterSchema = { - type: 'object', - additionalProperties: false, - required: ['title', 'property'], - properties: { - type: { type: 'string', enum: ['select', 'checkboxes', 'date-range'] }, - title: { type: 'string' }, - titleTranslationKey: { type: 'string' }, - property: { type: 'string' }, - parentFilter: { type: 'string' }, - valuesMapping: { type: 'object', additionalProperties: { type: 'string' } }, - missingCategoryName: { type: 'string' }, - missingCategoryNameTranslationKey: { type: 'string' }, - options: { type: 'array', items: { type: 'string' } }, - }, -} as const; - -const scorecardConfigSchema = { - type: 'object', - additionalProperties: true, - required: [], - properties: { - ignoreNonCompliant: { type: 'boolean', default: false }, - teamMetadataProperty: { - type: 'object', - properties: { - property: { type: 'string' }, - label: { type: 'string' }, - default: { type: 'string' }, - }, - }, - levels: { - type: 'array', - items: { - type: 'object', - required: ['name'], - properties: { - name: { type: 'string' }, - color: { type: 'string' }, - extends: { type: 'array', items: { type: 'string' } }, - rules: { - type: 'object', - additionalProperties: { - oneOf: [{ type: 'string' }, { type: 'object' }], - }, - }, - }, - additionalProperties: false, - }, - }, - targets: { - type: 'array', - items: { - type: 'object', - required: ['where'], - properties: { - minimumLevel: { type: 'string' }, - where: { - type: 'object', - required: ['metadata'], - properties: { - metadata: { type: 'object', additionalProperties: { type: 'string' } }, - }, - additionalProperties: false, - }, - }, - additionalProperties: false, - }, - }, - }, -} as const; - -const catalogSchema = { - type: 'object', - additionalProperties: true, - required: ['slug', 'items'], - properties: { - slug: { type: 'string' }, - filters: { type: 'array', items: catalogFilterSchema }, - groupByFirstFilter: { type: 'boolean' }, - filterValuesCasing: { - type: 'string', - enum: ['sentence', 'original', 'lowercase', 'uppercase'], - }, - items: navItemsSchema, - requiredPermission: { type: 'string' }, - separateVersions: { type: 'boolean' }, - title: { type: 'string' }, - titleTranslationKey: { type: 'string' }, - description: { type: 'string' }, - descriptionTranslationKey: { type: 'string' }, - }, -} as const; - -const catalogsConfigSchema = { - type: 'object', - patternProperties: { - '.*': catalogSchema, - }, -} as const; - -export const themeConfigSchema = { - type: 'object', - properties: { - imports: { - type: 'array', - items: { type: 'string' }, - default: [], - }, - logo: logoConfigSchema, - navbar: { - type: 'object', - properties: { - items: navItemsSchema, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - }, - products: { - type: 'object', - additionalProperties: productConfigSchema, - }, - footer: { - type: 'object', - properties: { - items: navItemsSchema, - copyrightText: { type: 'string' }, - logo: hideConfigSchema, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - }, - sidebar: { - type: 'object', - properties: { - separatorLine: { type: 'boolean' }, - linePosition: { - type: 'string', - enum: ['top', 'bottom'], - default: 'bottom', - }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - }, - scripts: { - type: 'object', - properties: { - head: { type: 'array', items: scriptConfigSchema }, - body: { type: 'array', items: scriptConfigSchema }, - }, - additionalProperties: false, - }, - links: { type: 'array', items: linksConfigSchema }, - feedback: { - type: 'object', - properties: { - hide: { - type: 'boolean', - default: false, - }, - type: { - type: 'string', - enum: ['rating', 'sentiment', 'comment', 'reasons', 'mood', 'scale'], - default: 'sentiment', - }, - settings: { - type: 'object', - properties: { - label: { type: 'string' }, - submitText: { type: 'string' }, - buttonText: { type: 'string' }, - component: { - type: 'string', - enum: ['radio', 'checkbox'], - default: 'checkbox', - }, - items: { type: 'array', items: { type: 'string' }, minItems: 1 }, - leftScaleLabel: { type: 'string' }, - rightScaleLabel: { type: 'string' }, - reasons: { - type: 'object', - properties: { - hide: { - type: 'boolean', - default: false, - }, - component: { - type: 'string', - enum: ['radio', 'checkbox'], - default: 'checkbox', - }, - label: { type: 'string' }, - items: { type: 'array', items: { type: 'string' } }, - }, - additionalProperties: false, - }, - comment: { - type: 'object', - properties: { - hide: { - type: 'boolean', - default: false, - }, - label: { type: 'string' }, - likeLabel: { type: 'string' }, - dislikeLabel: { type: 'string' }, - satisfiedLabel: { type: 'string' }, - neutralLabel: { type: 'string' }, - dissatisfiedLabel: { type: 'string' }, - }, - additionalProperties: false, - }, - }, - additionalProperties: false, - ...hideConfigSchema.properties, - }, - }, - additionalProperties: false, - default: {}, - }, - search: { - type: 'object', - properties: { - placement: { - type: 'string', - default: 'navbar', - }, - shortcuts: { - type: 'array', - items: { type: 'string' }, - default: ['/'], - }, - suggestedPages: { - type: 'array', - items: suggestedPageSchema, - }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - colorMode: { - type: 'object', - properties: { - ignoreDetection: { type: 'boolean' }, - modes: { - type: 'array', - items: { type: 'string' }, - default: ['light', 'dark'], - }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - navigation: { - type: 'object', - properties: { - nextButton: { - type: 'object', - properties: { - text: { type: 'string', default: 'Next to {{label}}' }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - previousButton: { - type: 'object', - properties: { - text: { type: 'string', default: 'Back to {{label}}' }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - }, - additionalProperties: false, - default: {}, - }, - codeSnippet: { - type: 'object', - properties: { - elementFormat: { type: 'string', default: 'icon' }, - copy: { - type: 'object', - properties: { - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: { hide: false }, - }, - report: { - type: 'object', - properties: { - tooltipText: { type: 'string' }, - buttonText: { type: 'string' }, - label: { type: 'string' }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: { hide: false }, - }, - expand: { - type: 'object', - properties: { - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: { hide: false }, - }, - collapse: { - type: 'object', - properties: { - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: { hide: false }, - }, - }, - additionalProperties: false, - default: {}, - }, - markdown: markdownConfigSchema, - openapi: { type: 'object', additionalProperties: true }, // TODO: put the real schema here @Viacheslav - graphql: { type: 'object', additionalProperties: true }, - analytics: { - type: 'object', - properties: { - adobe: adobeAnalyticsConfigSchema, - amplitude: amplitudeAnalyticsConfigSchema, - fullstory: fullstoryAnalyticsConfigSchema, - heap: heapAnalyticsConfigSchema, - rudderstack: rudderstackAnalyticsConfigSchema, - segment: segmentAnalyticsConfigSchema, - gtm: gtmAnalyticsConfigSchema, - ga: googleAnalyticsConfigSchema, - }, - }, - userProfile: { - type: 'object', - properties: { - loginLabel: { type: 'string', default: 'Login' }, - logoutLabel: { type: 'string', default: 'Logout' }, - menu: { - type: 'array', - items: { - type: 'object', - properties: { - label: { type: 'string' }, - external: { type: 'boolean' }, - link: { type: 'string' }, - separatorLine: { type: 'boolean' }, - }, - additionalProperties: true, - }, - default: [], - }, - ...hideConfigSchema.properties, - }, - additionalProperties: false, - default: {}, - }, - versionPicker: { - type: 'object', - properties: { - hide: { type: 'boolean' }, - showForUnversioned: { - type: 'boolean', - }, - }, - }, - breadcrumbs: { - type: 'object', - properties: { - hide: { type: 'boolean' }, - prefixItems: { - type: 'array', - items: { - type: 'object', - properties: { - label: { type: 'string' }, - labelTranslationKey: { type: 'string' }, - page: { type: 'string' }, - }, - additionalProperties: false, - default: {}, - }, - }, - }, - additionalProperties: false, - default: {}, - }, - catalog: catalogsConfigSchema, - scorecard: scorecardConfigSchema, - }, - additionalProperties: true, - default: {}, -} as const; - -export const productThemeOverrideSchema = { - type: 'object', - properties: { - logo: themeConfigSchema.properties.logo, - navbar: themeConfigSchema.properties.navbar, - footer: themeConfigSchema.properties.footer, - sidebar: themeConfigSchema.properties.sidebar, - search: themeConfigSchema.properties.search, - codeSnippet: themeConfigSchema.properties.codeSnippet, - breadcrumbs: themeConfigSchema.properties.breadcrumbs, - analytics: { - type: 'object', - properties: { - ga: productGoogleAnalyticsConfigSchema, - }, - }, - }, - additionalProperties: true, - default: {}, -} as const; - -export type ThemeConfig = FromSchema; - -// TODO: cannot export as it relies on external types -// export type ThemeUIConfig = ThemeConfig & { -// auth?: { -// // used by portal dev login emulator -// idpsInfo?: { -// idpId: string; -// type: string; // AuthProviderType -// title: string | undefined; -// }[]; -// devLogin?: boolean; -// loginUrls?: Record; -// }; -// search?: { -// shortcuts?: string[]; -// suggestedPages?: any[]; -// }; -// breadcrumbs?: { -// prefixItems?: ResolvedNavLinkItem[]; -// }; -// products?: { -// [key: string]: ProductUiConfig; -// }; -// }; - -export type ProductConfig = FromSchema; - -export type ProductGoogleAnalyticsConfig = FromSchema; -// TODO: cannot export as it relies on external types -// export type ProductThemeOverrideConfig = Pick< -// ThemeUIConfig, -// 'logo' | 'navbar' | 'footer' | 'sidebar' | 'search' | 'codeSnippet' | 'breadcrumbs' -// > & { analytics?: { ga?: ProductGoogleAnalyticsConfig } }; -// export type ProductUiConfig = ProductConfig & { -// slug: string; -// link: string; -// [REDOCLY_TEAMS_RBAC]?: { [key: string]: string }; -// themeOverride?: ProductThemeOverrideConfig; -// }; - -export type MarkdownConfig = FromSchema; - -export type AmplitudeAnalyticsConfig = FromSchema; -export type RudderstackAnalyticsConfig = FromSchema; -export type SegmentAnalyticsConfig = FromSchema; -export type GtmAnalyticsConfig = FromSchema; -export type GoogleAnalyticsConfig = FromSchema; -export type CatalogConfig = FromSchema; -export type CatalogFilterConfig = FromSchema; -export type ScorecardConfig = FromSchema;